pycharm如何配置python环境

Python笔记
npm install的时候出现node-gyp的错误,该如何解决
在使用 npm install
时遇到 node-gyp
错误,通常是由于编译原生模块时出现问题。以下是一些常见的解决方法:
node-gyp
需要 Python 和 C++ 编译工具。确保已安装以下工具:
Python(建议使用 2.7.x 或 3.x 版本)
C++ 编译器(如 Visual Studio Build Tools 或 Xcode)
安装 Visual Studio Build Tools,并选择“C++ 生成工具”工作负载。
安装 Xcode 命令行工具:
xcode-select --install
安装构建工具:
sudo apt-get install build-essential
如果系统中有多个 Python 版本,确保 node-gyp
使用正确的版本:
npm config set python /path/to/python
有时缓存可能导致问题,清理缓存后重试:
npm cache clean --force
确保 npm
和 node-gyp
是最新版本:
npm install -g npm npm install -g node-gyp
某些依赖可能需要特定版本的 Node.js 或 npm,检查 package.json
中的依赖是否兼容当前环境。
--verbose
查看详细日志运行 npm install
时添加 --verbose
参数,查看更详细的错误信息:
npm install --verbose
如果某个模块反复出错,尝试手动编译:
cd node_modules/<problematic-module> node-gyp rebuild
某些模块提供预编译二进制文件,避免手动编译。可以尝试:
npm install --global windows-build-tools
确保环境变量(如 PATH
)正确配置,特别是 Python 和 C++ 编译器的路径。
如果问题持续,考虑使用 Docker 容器,确保一致的构建环境。
通过以上步骤,通常可以解决 node-gyp
错误。如果问题仍未解决,建议查看模块的 GitHub Issues 或社区讨论。
npm install时指定python版本
部分npm组件在安装时只支持python2版本,如果本地python环境变量是设置的python3,则会导致错误。
解决方法,安装时执行,指定python版本
npm install --python=python2.7
或者直接修改npm的python版本设置
npm config set python python2.7
这样npm install时,如果需要调用node-gyp编译,会使用指定的python执行。
Python:使用aiohttp
asyncio
可以实现单线程并发IO操作。如果仅用在客户端,发挥的威力不大。如果把asyncio
用在服务器端,例如Web服务器,由于HTTP连接就是IO操作,因此可以用单线程+async
函数实现多用户的高并发支持。
asyncio
实现了TCP、UDP、SSL等协议,aiohttp
则是基于asyncio
实现的HTTP框架。
我们先安装aiohttp
:
$ pip install aiohttp
然后编写一个HTTP服务器,分别处理以下URL:
/
- 首页返回Index Page
;/{name}
- 根据URL参数返回文本Hello, {name}!
。代码如下:
# app.py
from aiohttp import web
async def index(request):
text = "<h1>Index Page</h1>"
return web.Response(text=text, content_type="text/html")
async def hello(request):
name = request.match_info.get("name", "World")
text = f"<h1>Hello, {name}</h1>"
return web.Response(text=text, content_type="text/html")
app = web.Application()
# 添加路由:
app.add_routes([web.get("/", index), web.get("/{name}", hello)])
if __name__ == "__main__":
web.run_app(app)
直接运行app.py
,访问首页:
访问http://localhost:8080/Bob
:
使用aiohttp时,定义处理不同URL的async
函数,然后通过app.add_routes()
添加映射,最后通过run_app()
以asyncio的机制启动整个处理流程。
Python协程详解
在学习异步IO模型前,我们先来了解协程。
协程,又称微线程,纤程。英文名Coroutine。
协程的概念很早就提出来了,但直到最近几年才在某些语言(如Lua)中得到广泛应用。
子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。
所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。
子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。
协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。
注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断。比如子程序A、B:
def A():
print('1')
print('2')
print('3')
def B():
print('x')
print('y')
print('z')
假设由协程执行,在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A,结果可能是:
1
2
x
y
3
z
但是在A中是没有调用B的,所以协程的调用比函数调用理解起来要难一些。
看起来A、B的执行有点像多线程,但协程的特点在于是一个线程执行,那和多线程比,协程有何优势?
最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
Python对协程的支持是通过generator实现的。
在generator中,我们不但可以通过for
循环来迭代,还可以不断调用next()
函数获取由yield
语句返回的下一个值。
但是Python的yield
不但可以返回一个值,它还可以接收调用者发出的参数。
来看例子:
传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。
如果改用协程,生产者生产消息后,直接通过yield
跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高:
def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('[CONSUMER] Consuming %s...' % n)
r = '200 OK'
def produce(c):
c.send(None)
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s...' % n)
r = c.send(n)
print('[PRODUCER] Consumer return: %s' % r)
c.close()
c = consumer()
produce(c)
执行结果:
[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 4...
[CONSUMER] Consuming 4...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 5...
[CONSUMER] Consuming 5...
[PRODUCER] Consumer return: 200 OK
注意到consumer
函数是一个generator
,把一个consumer
传入produce
后:
c.send(None)
启动生成器;c.send(n)
切换到consumer
执行;consumer
通过yield
拿到消息,处理,又通过yield
把结果传回;produce
拿到consumer
处理的结果,继续生产下一条消息;produce
决定不生产了,通过c.close()
关闭consumer
,整个过程结束。整个流程无锁,由一个线程执行,produce
和consumer
协作完成任务,所以称为“协程”,而非线程的抢占式多任务。
最后套用Donald Knuth的一句话总结协程的特点:
“子程序就是协程的一种特例。”
Python asyncio 使用
asyncio
是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。
asyncio
的编程模型就是一个消息循环。asyncio
模块内部实现了EventLoop
,把需要执行的协程扔到EventLoop
中执行,就实现了异步IO。
用asyncio
提供的@asyncio.coroutine
可以把一个generator
标记为coroutine
类型,然后在coroutine
内部用yield from
调用另一个coroutine
实现异步操作。
为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法async
和await
,可以让coroutine
的代码更简洁易读。
用asyncio
实现Hello world
代码如下:
import asyncio
async def hello():
print("Hello world!")
# 异步调用asyncio.sleep(1):
await asyncio.sleep(1)
print("Hello again!")
asyncio.run(hello())
async
把一个函数变成coroutine
类型,然后,我们就把这个async
函数扔到asyncio.run()
中执行。执行结果如下:
Hello!
(等待约1秒)
Hello again!
hello()
会首先打印出Hello world!
,然后,await
语法可以让我们方便地调用另一个async
函数。由于asyncio.sleep()
也是一个async
函数,所以线程不会等待asyncio.sleep()
,而是直接中断并执行下一个消息循环。当asyncio.sleep()
返回时,就接着执行下一行语句。
把asyncio.sleep(1)
看成是一个耗时1秒的IO操作,在此期间,主线程并未等待,而是去执行EventLoop
中其他可以执行的async
函数了,因此可以实现并发执行。
上述hello()
还没有看出并发执行的特点,我们改写一下,让两个hello()
同时并发执行:
# 传入name参数:
async def hello(name):
# 打印name和当前线程:
print("Hello %s! (%s)" % (name, threading.current_thread))
# 异步调用asyncio.sleep(1):
await asyncio.sleep(1)
print("Hello %s again! (%s)" % (name, threading.current_thread))
return name
用asyncio.gather()
同时调度多个async
函数:
async def main():
L = await asyncio.gather(hello("Bob"), hello("Alice"))
print(L)
asyncio.run(main())
执行结果如下:
Hello Bob! (<function current_thread at 0x10387d260>)
Hello Alice! (<function current_thread at 0x10387d260>)
(等待约1秒)
Hello Bob again! (<function current_thread at 0x10387d260>)
Hello Alice again! (<function current_thread at 0x10387d260>)
['Bob', 'Alice']
从结果可知,用asyncio.run()
执行async
函数,所有函数均由同一个线程执行。两个hello()
是并发执行的,并且可以拿到async
函数执行的结果(即return
的返回值)。
如果把asyncio.sleep()
换成真正的IO操作,则多个并发的IO操作实际上可以由一个线程并发执行。
我们用asyncio
的异步网络连接来获取sina、sohu和163的网站首页:
import asyncio
async def wget(host):
print(f"wget {host}...")
# 连接80端口:
reader, writer = await asyncio.open_connection(host, 80)
# 发送HTTP请求:
header = f"GET / HTTP/1.0\r\nHost: {host}\r\n\r\n"
writer.write(header.encode("utf-8"))
await writer.drain()
# 读取HTTP响应:
while True:
line = await reader.readline()
if line == b"\r\n":
break
print("%s header > %s" % (host, line.decode("utf-8").rstrip()))
# Ignore the body, close the socket
writer.close()
await writer.wait_closed()
print(f"Done {host}.")
async def main():
await asyncio.gather(wget("www.sina.com.cn"), wget("www.sohu.com"), wget("www.163.com"))
asyncio.run(main())
执行结果如下:
wget www.sohu.com...
wget www.sina.com.cn...
wget www.163.com...
(等待一段时间)
(打印出sohu的header)
www.sohu.com header > HTTP/1.1 200 OK
www.sohu.com header > Content-Type: text/html
...
(打印出sina的header)
www.sina.com.cn header > HTTP/1.1 200 OK
www.sina.com.cn header > Date: Wed, 20 May 2015 04:56:33 GMT
...
(打印出163的header)
www.163.com header > HTTP/1.0 302 Moved Temporarily
www.163.com header > Server: Cdn Cache Server V2.0
...
可见3个连接由一个线程并发执行3个async
函数完成。
asyncio
提供了完善的异步IO支持,用asyncio.run()
调度一个coroutine
;
在一个async
函数内部,通过await
可以调用另一个async
函数,这个调用看起来是串行执行的,但实际上是由asyncio
内部的消息循环控制;
在一个async
函数内部,通过await asyncio.gather()
可以并发执行若干个async
函数。
使用Asyncio控制任务
Asyncio是用来处理事件循环中的异步进程和并发任务执行的。它还提供了 asyncio.Task()
类,可以在任务中使用协程。它的作用是,在同一事件循环中,运行某一个任务的同时可以并发地运行多个任务。当协程被包在任务中,它会自动将任务和事件循环连接起来,当事件循环启动的时候,任务自动运行。这样就提供了一个可以自动驱动协程的机制。
Asyncio模块为我们提供了 asyncio.Task(coroutine)
方法来处理计算任务,它可以调度协程的执行。任务对协程对象在事件循环的执行负责。如果被包裹的协程要从future yield,那么任务会被挂起,等待future的计算结果。
当future计算完成,被包裹的协程将会拿到future返回的结果或异常(exception)继续执行。另外,需要注意的是,事件循环一次只能运行一个任务,除非还有其它事件循环在不同的线程并行运行,此任务才有可能和其他任务并行。当一个任务在等待future执行的期间,事件循环会运行一个新的任务。
"""
Asyncio using Asyncio.Task to execute three math function in parallel
"""
import asyncio
@asyncio.coroutine
def factorial(number):
f = 1
for i in range(2, number + 1):
print("Asyncio.Task: Compute factorial(%s)" % (i))
yield from asyncio.sleep(1)
f *= i
print("Asyncio.Task - factorial(%s) = %s" % (number, f))
@asyncio.coroutine
def fibonacci(number):
a, b = 0, 1
for i in range(number):
print("Asyncio.Task: Compute fibonacci (%s)" % (i))
yield from asyncio.sleep(1)
a, b = b, a + b
print("Asyncio.Task - fibonacci(%s) = %s" % (number, a))
@asyncio.coroutine
def binomialCoeff(n, k):
result = 1
for i in range(1, k+1):
result = result * (n-i+1) / i
print("Asyncio.Task: Compute binomialCoeff (%s)" % (i))
yield from asyncio.sleep(1)
print("Asyncio.Task - binomialCoeff(%s , %s) = %s" % (n, k, result))
if __name__ == "__main__":
tasks = [asyncio.Task(factorial(10)),
asyncio.Task(fibonacci(10)),
asyncio.Task(binomialCoeff(20, 10))]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
在下面的代码中,我们展示了三个可以被 Asyncio.Task()
并发执行的数学函数。
运行的结果如下:
python3 task.py
Asyncio.Task: Compute factorial(2)
Asyncio.Task: Compute fibonacci (0)
Asyncio.Task: Compute binomialCoeff (1)
Asyncio.Task: Compute factorial(3)
Asyncio.Task: Compute fibonacci (1)
Asyncio.Task: Compute binomialCoeff (2)
Asyncio.Task: Compute factorial(4)
Asyncio.Task: Compute fibonacci (2)
Asyncio.Task: Compute binomialCoeff (3)
Asyncio.Task: Compute factorial(5)
Asyncio.Task: Compute fibonacci (3)
Asyncio.Task: Compute binomialCoeff (4)
Asyncio.Task: Compute factorial(6)
Asyncio.Task: Compute fibonacci (4)
Asyncio.Task: Compute binomialCoeff (5)
Asyncio.Task: Compute factorial(7)
Asyncio.Task: Compute fibonacci (5)
Asyncio.Task: Compute binomialCoeff (6)
Asyncio.Task: Compute factorial(8)
Asyncio.Task: Compute fibonacci (6)
Asyncio.Task: Compute binomialCoeff (7)
Asyncio.Task: Compute factorial(9)
Asyncio.Task: Compute fibonacci (7)
Asyncio.Task: Compute binomialCoeff (8)
Asyncio.Task: Compute factorial(10)
Asyncio.Task: Compute fibonacci (8)
Asyncio.Task: Compute binomialCoeff (9)
Asyncio.Task - factorial(10) = 3628800
Asyncio.Task: Compute fibonacci (9)
Asyncio.Task: Compute binomialCoeff (10)
Asyncio.Task - fibonacci(10) = 55
Asyncio.Task - binomialCoeff(20 , 10) = 184756.0
在这个例子中,我们定义了三个协程, factorial
, fibonacci
和 binomialCoeff
,每一个都带有 asyncio.coroutine
装饰器:
@asyncio.coroutine
def factorial(number):
do Something
@asyncio.coroutine
def fibonacci(number):
do Something
@asyncio.coroutine
def binomialCoeff(n, k):
do Something
为了能并行执行这三个任务,我们将其放到一个task的list中:
if __name__ == "__main__":
tasks = [asyncio.Task(factorial(10)),
asyncio.Task(fibonacci(10)),
asyncio.Task(binomialCoeff(20, 10))]
得到事件循环:
loop = asyncio.get_event_loop()
然后运行任务:
loop.run_until_complete(asyncio.wait(tasks))
这里, asyncio.wait(tasks)
表示运行直到所有给定的协程都完成。
最后,关闭事件循环:
loop.close()
python中的yield、yield from、async/await中的区别与联系
yield
可以将一个函数转换成 generator(生成器)
,和普通函数不同得是,生成一个 generator
看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()
/send()
才开始执行,虽然执行流程仍按函数得流程执行,但是执行到每一个yield
语句就会中断,交出执行权,并返回一个迭代值,下次执行从yield
中断处可以通过send()
接收一个参数并继续执行。看起来就像一个函数在正常执行得过程中被yield
中断了数次,每次中断都会交出执行权,通过 yield
返回当前的迭代值。
yield
的好处是显而易见的,把一个函数改写为一个 generator
就获得了迭代能力,这也是python中协程的基础。
next()
的作用是每次获取一个yield出来的值。
def generator():
yield 1
yield 2
yield 3
return 4
if __name__ == "__main__":
gen = generator()
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen)) # 因为不是yield,这里抛出一个StopIteration的错误,但错误信息中会返回 4。 错误详情:StopIteration: 4
gen.close()
send()
和next()
类似,但是比next()多一个功能,可以往生成器中传入参数。
def generator2():
num = yield 1
print (num)
yield 2
yield 3
return 4
if __name__ == "__main__":
gen = generator2()
print(gen.send(None)) # 如果使用send()的时候,第一个传入的值必须是None,因为当调用send()的时候,才执行到第一个yield,只能输出一个值,不能接收值。
print(gen.send(101))
print(gen.send(None))
gen.close()
注:关于StopIteration:1. 在一个生成器中,如果没有return,则默认执行到函数完毕时返回StopIteration;2. 如果遇到return,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。3.在close()之后,如果再执行相关的操作,会抛出StopIteration;
throw()
用来向生成器函数送入一个异常,可以结束系统定义的异常,或者自定义的异常。我的理解是相当于send()进去一个错误类型,然后抛出异常。我的理解可能不准确,仅供参考。
def generator3():
num = yield 1
print(num)
try:
yield 2
except ValueError:
print("捕获异常:ValueError")
yield 3
if __name__ == "__main__":
gen = generator3()
print(gen.send(None))
print(gen.send(101))
gen.throw(ValueError)
gen.close()
我们先来看个例子:
def test03(iterabld):
yield iterabld
def test04(iterable):
yield from iterable
if __name__ == "__main__":
for iterabld in test03(range(5)):
print(iterabld)
for value in test04(range(5)):
print (value)
返回值:
# range(0, 5)
# 0
# 1
# 2
# 3
# 4
这两个的返回值是一样的,可以看的出来,yield form 将可迭代对象中的值直接迭代出来了。下边再看一个例子:
def generator01():
num = yield 1
print(num)
yield 2
yield 3
return 4
def test01(gen):
ll = yield from gen()
print(ll)
def main():
gen = test01(generator01)
print(gen.send(None))
print(gen.send(101))
print(gen.throw(ValueError))
if __name__ == "__main__":
main()
执行下例子,可以发现,再main()中的send()和throw()都是直接作用
到生成器generator01中的。这其中main()函数被称作调用方
,test01()被称作委托生成器
(包含yield from表达式的生成器函数),generator01()被称作子生成器
(yield from后面加的生成器函数)。
委托生成器的作用是:在调用方与子生成器之间建立一个双向通道
,拿例子来讲,就是main()中的send()等直接作用到generator01到,而generator01中yield产生的值直接返回到main()中。
委托生成器中:ll = yield from gen()
为啥会赋值? 这是接收的return的值。生成器没有yield,有return的时候,会抛出StopIteration异常,在抛出StopIteration的异常的时候,会将return的值赋给ll。
注:1. yield from 是在Python3.3才出现的语法。所以这个特性在Python2中是没有的。 2.yield from 后面需要加的是可迭代对象。它可以是普通的可迭代对象,也可以是迭代器、生成器。
async/await 是用是python3.5后出来的协程异步编程的API, 是为了区分yield,yield from生成器,而使语义更加明确。以下示例代码只是为了展示await与yield from对比,实际开发中,不要这么做!不要这么做!!!
# 示例代码一
import requests
async def request(url):
return requests.get(url)
async def spider(url):
return await request(url)
if __name__ == '__main__':
sp = spider("http://www.baidu.com")
try:
print(sp.send(None))
except StopIteration as e:
print (e.value)
# 示例代码二
import requests
import types
@types.coroutine
def request(url):
yield requests.get(url)
async def spider(url):
return await request(url)
if __name__ == '__main__':
sp = spider("http://www.baidu.com")
print(sp.send(None))
# 以上代码的打印结果都是 <Response [200]>
async def
语法定义协程函数
,在之前这个功能是通过装饰器实现的。但是这样定义的协程函数中不能使用yield语句
,只允许使用return或await语句返回数据。
await
的使用场景与yield from
类似,但是await
接收的对象不同。yield from
可以是任意的可迭代对象。而await
接收的对象必须是可等待对象(awaitable object)
注:1. async/await是在python3.5版么以及之后的版本中才能使用。2. async不能和yield同时使用。3.await只能作用于可等待对象
推荐阅读:https://www.cnblogs.com/harelion/p/8496360.html
个人的感想:async/await的出现是为了协程,是为了区分生成器使编程更加明确,来提升Python中的异步编程体验。具体的使用将在接下来的asyncio的介绍中,将大量使用async/await。
Python 2与Python 3主要区别
在Python 2中,print
被视为语句而不是函数,这是一个典型的易引起混淆的地方,因为Python中的许多其他操作都需要括号内的参数来执行。如果你想在命令行中用Python 2打印Sammy the Shark is my favorite sea creature
,可以使用以下print
语句:
现在,在Python 3中print()
被明确地定义为一个函数,因此想打印上面相同的字符串,可以使用函数的语法简单容易地执行此操作:
这一变化使得Python的语法更加一致,也更易于在不同的打印函数之间进行更改。print()
句法同样向后支持Python 2.7,因此 Python 3的print()
函数可以方便地在任一版本中运行。
在Python2中,任何不带小数的数字都被视为整数(integer)。乍一看这似乎是处理编程类型的一种简单方法,但当整数相除时,有时你希望得到一个带有小数位的答案,被称为浮点数(float),比如:
5 / 2 = 2.5
然而,在Python 2中整数是强类型,即使在凭直觉应当转换为带小数点的“浮点数”(float)的情况下,也不会转换。
当除法/
符号两边的两个数字是整数时,Python 2执行向下取整(floor division)。因此对于商x
,返回的数字是小于或等于x
的最大整数。这意味着使用5/2
进行这两个数字的除法时,Python 2.7将返回小于或等于2.5的最大整数,在这个例子中为2
:
Output2
若想强制覆盖这种行为,你可以添加小数位5.0/2.0
,以获得预期的答案2.5
。
在Python 3中, 整数除法 变得直观,比如:
Output2.5
你依然可以使用5.0 / 2.0
去得到2.5
,但是如果想向下取整运算,应该使用Python 3的语法//
,如下所示:
Output2
Python 3中的这个修改使得整数除法更加直观,这个句法不向后兼容Python 2.7。
当编程语言处理字符串类型 字符串 (也就是一串字符)时,他们可以用几种不同的方法来实现,这样计算机可以把数字转换成字母和其他符号。
Python 2默认使用ASCII字母表, 因此当你输入 "Hello, Sammy!"
时Python 2会将字符串作为ASCII进行处理。ASCII在各种扩展形式中,也最多只有几百个字符。因此ASCII不是一种非常灵活的字符编码方式,尤其是非英语字符。
若是想使用更加通用和健全的Unicode字符编码(它支持超过128000个字符,跨越当代和历史上的各类脚本和符号集),你必须键入u"Hello, Sammy!"
,其中u
前缀表示Unicode。
Python 3默认使用Unicode,这为程序员节省了额外的开发时间。而且你可以轻松地在程序中直接输入和显示更多字符。Unicode支持更大的语言字符多样性,以及emojis(表情符号)的显示。因此使用它作为默认字符编码,可以确保你的开发项目能无缝支持世界各地的移动设备。
不过若希望你的Python 3代码向后兼容Python 2,你可你以在字符串之前保留u
。
Python异步库asyncio使用方法
异步编程是一种编写能够在单线程中同时处理多个任务的编程方式。与传统的同步编程相比,异步编程的主要优势在于能够提高程序的并发性和响应性,尤其适用于IO密集型任务,如网络通信、数据库访问等。
asyncio
是Python 3.4版本引入的标准库,用于实现异步编程。它基于事件循环(Event Loop)模型,通过协程(Coroutines)来实现任务的切换和调度。在asyncio
中,我们可以使用async
关键字定义协程函数,并使用await
关键字挂起协程的执行。
异步编程的核心思想是避免阻塞,即当一个任务在等待某个操作完成时,可以让出CPU执行权,让其他任务继续执行。这样可以充分利用CPU的时间片,提高程序的整体效率。
import asyncio
async def hello():
print("Hello")
await asyncio.sleep(1) # 模拟耗时操作,挂起协程执行
print("World")
async def main():
await asyncio.gather(
hello(),
hello()
)
asyncio.run(main())
定义了两个协程函数hello()
,每个函数打印一条消息,并通过await asyncio.sleep(1)
实现了一个模拟的耗时操作。在main()
函数中,使用asyncio.gather()
方法并发运行两个协程任务。最后,通过asyncio.run()
来运行主函数。
这个示例展示了异步编程的基本特点:协程函数能够在等待耗时操作时挂起执行,让其他任务继续执行,从而实现并发执行多个任务。
协程(Coroutines)是异步编程中的一种特殊函数,它可以在运行过程中暂停并记录当前状态,然后在需要时恢复执行。协程非常适合处理那些可能会被中断和恢复的任务,如IO操作、计算密集型任务等。
async
和await
关键字的用法在Python中,可以通过使用async
关键字定义协程函数,使用await
关键字挂起协程的执行,等待耗时操作完成后再恢复执行。
import asyncio
async def hello():
print("Hello")
await asyncio.sleep(1) # 模拟耗时操作,挂起协程执行
print("World")
asyncio.run(hello())
在上述示例中,我们定义了一个协程函数hello()
,它打印一条消息,然后通过await asyncio.sleep(1)
实现了一个模拟的耗时操作。最后,通过asyncio.run()
来运行协程函数。
Hello
# 等待1秒后
World
可以看到,在协程函数中,通过await
关键字挂起协程的执行,等待耗时操作完成。这样,协程函数可以在等待期间让出CPU执行权,让其他任务继续执行。
需要注意的是,协程函数必须通过await
关键字与具体的异步操作进行配合使用。await
后面可以跟一个协程对象、一个实现了__await__()
方法的对象(如asyncio.sleep()
方法),或者一些其他的异步操作。
协程函数的定义方式与普通函数相同,只需在函数声明处使用async
关键字即可。在协程函数中,可以使用return
返回结果,也可以不返回任何内容(即不使用return
语句)。
事件循环是异步编程的核心机制,它负责协调和调度协程的执行,以及处理IO操作和定时器等事件。它会循环监听事件的发生,并根据事件的类型选择适当的协程进行调度。
在asyncio
库中,可以通过asyncio.get_event_loop()
方法获取默认的事件循环对象,也可以使用asyncio.new_event_loop()
方法创建新的事件循环对象。
import asyncio
async def hello():
print("Hello")
await asyncio.sleep(1) # 模拟耗时操作,挂起协程执行
print("World")
loop = asyncio.get_event_loop()
loop.run_until_complete(hello())
loop.close()
在上述示例中,我们首先通过asyncio.get_event_loop()
方法获取默认的事件循环对象,然后使用loop.run_until_complete()
方法运行协程函数。最后,通过loop.close()
关闭事件循环。
在事件循环中,协程函数会按照调度规则进行执行。当遇到await
关键字时,协程函数会暂时挂起,并将控制权让给其他协程。当await
后面的耗时操作完成后,事件循环会恢复被挂起的协程的执行。
需要注意的是,run_until_complete()
方法接受一个可等待对象作为参数,可以是协程对象、任务对象或者Future对象。它会持续运行事件循环,直到可等待对象完成。
另外,使用asyncio
库创建和运行事件循环还有其他的方式,例如使用asyncio.run()
方法来简化上述代码:
import asyncio
async def hello():
print("Hello")
await asyncio.sleep(1) # 模拟耗时操作,挂起协程执行
print("World")
asyncio.run(hello())
在异步编程中,IO操作(如网络请求、文件读写等)通常是耗时的操作,阻塞式的IO操作会导致程序在等待IO完成时无法做其他事情。而异步IO操作则可以在等待IO完成时让出CPU执行权,以便同时处理其他任务。
在Python中,可以通过asyncio
库来实现异步IO操作。asyncio
提供了一系列的函数和类来管理异步IO操作,例如使用asyncio.open()
来异步打开文件、使用asyncio.wait()
来等待多个异步任务完成等。
import asyncio
async def read_file(file_path):
async with asyncio.open(file_path, 'r') as file:
data = await file.read()
print(data)
asyncio.run(read_file('example.txt'))
在上述示例中,我们定义了一个协程函数read_file()
,它使用asyncio.open()
来异步打开文件,并使用await file.read()
来异步读取文件内容。最后,通过asyncio.run()
运行协程函数。
需要注意的是,asyncio.open()
返回一个AsyncIOModeReader
对象,可以使用await
关键字来读取文件内容。另外,在使用asyncio.open()
时,也可以通过指定文件的编码方式和其他参数来进行相关配置。
除了使用asyncio.open()
来进行异步文件操作外,asyncio
还提供了许多其他的异步IO操作函数和类,如asyncio.create_connection()
用于异步创建网络连接、asyncio.start_server()
用于异步启动服务器等。可以根据具体的需求选择合适的方法来进行异步IO操作。
除了读取文件,我们还可以使用异步的文件对象执行异步写入操作。
import asyncio
async def write_file(file_path, content):
try:
async with asyncio.open_file(file_path, 'w') as file:
await file.write(content)
print("文件写入成功")
except OSError:
print("写入文件失败")
async def main():
await write_file("myfile.txt", "Hello, world!")
asyncio.run(main())
在上述示例中,我们定义了一个write_file()
函数,该函数用于异步写入文件内容。我们使用asyncio.open_file()
函数来打开文件,并指定文件路径和打开模式(例如'w'表示写入模式)。
然后,我们使用await file.write(content)
来异步将内容写入文件。最后,我们打印输出写入文件成功的消息。
在异步编程中,我们常常需要在一定的时间间隔内执行某些任务,或者延迟一段时间后执行某些操作。asyncio
库提供了定时器和延迟执行的功能,使我们能够方便地实现这些需求。
要创建一个定时器,可以使用asyncio.sleep()
函数。该函数会暂停当前任务的执行,并在指定的时间间隔之后恢复执行。
import asyncio
async def task():
print("开始任务")
await asyncio.sleep(3)
print("任务完成")
async def main():
print("程序开始")
await task()
print("程序结束")
asyncio.run(main())
在上述示例中,我们定义了一个task()
函数,其中使用await asyncio.sleep(3)
来创建一个定时器,指定了3秒的时间间隔。
当我们运行main()
函数时,会输出"程序开始",然后调用task()
函数。在调用await asyncio.sleep(3)
后,程序会暂停3秒钟,然后才会继续执行后续的代码。
因此,会输出"开始任务",然后等待3秒后输出"任务完成",最后输出"程序结束"。
除了使用定时器外,我们还可以使用asyncio.ensure_future()
函数和asyncio.gather()
函数来实现延迟执行任务。
asyncio.ensure_future()
函数接受一个协程对象作为参数,并将其封装为一个Task
对象,表示一个可等待的任务。
import asyncio
async def task():
print("执行任务")
async def main():
print("程序开始")
future = asyncio.ensure_future(task())
await asyncio.sleep(3)
await future
print("程序结束")
asyncio.run(main())
在上述示例中,我们使用asyncio.ensure_future()
函数将task()
函数封装为一个Task
对象,并将其赋值给变量future
。
然后,我们使用await asyncio.sleep(3)
创建一个定时器来延迟执行任务,等待3秒钟。
最后,使用await future
来等待任务的完成,即延迟执行的任务。
另外,asyncio.gather()
函数可以同时运行多个协程,并等待它们全部完成。
import asyncio
async def task1():
print("任务1开始")
await asyncio.sleep(2)
print("任务1完成")
async def task2():
print("任务2开始")
await asyncio.sleep(3)
print("任务2完成")
async def main():
print("程序开始")
await asyncio.gather(task1(), task2())
print("程序结束")
asyncio.run(main())
在上述示例中,我们定义了两个task1()
和task2()
函数,分别表示两个需要执行的任务。
在main()
函数中,使用await asyncio.gather(task1(), task2())
来同时运行这两个任务,并等待它们全部完成。
在异步编程中,异步网络请求是一种常见的任务。它允许我们以非阻塞的方式发送网络请求并同时执行其他任务,而无需等待响应返回。
异步网络请求的概念是通过在发送请求时立即返回一个可等待对象,在后台进行网络通信,并在响应可用时进行处理。
在Python中,我们可以使用asyncio
库来实现异步网络请求。asyncio
提供了一些函数和类来进行异步的网络通信,如aiohttp
库。
import asyncio
import aiohttp
async def fetch_data(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
data = await response.text()
print(data)
async def main():
await fetch_data("https://www.example.com")
asyncio.run(main())
在上述示例中,我们定义了一个fetch_data()
函数,用于异步获取指定URL的数据。
首先,我们创建一个异步的HTTP客户端会话,使用aiohttp.ClientSession()
。然后,我们使用session.get()
方法发送GET请求并获取响应对象。
接下来,我们使用await response.text()
来异步获取响应的文本数据,并将其存储在变量data
中。
最后,我们可以根据需要对获取到的数据进行处理,这里只是简单地将其打印输出。
需要注意的是,在使用完异步的HTTP客户端会话后,我们使用async with
语句来自动关闭会话,以释放相关资源。
当我们运行main()
函数时,会执行异步获取数据的操作。而在等待数据返回的过程中,程序可以继续执行其他任务。
在异步编程中,与数据库的交互也是一项常见的任务。asyncio
库提供了一些函数和类来实现异步的数据库操作。
使用asyncio
封装一个关于Oracle操作的类时,你可以创建一个OracleHandler
类,并在其中定义增删改查等操作的方法。下面是一个示例代码,其中包含了插入、删除、更新和查询方法的实现:
import asyncio
import cx_Oracle
class OracleHandler:
def __init__(self, username, password, hostname, port, sid):
self.username = username
self.password = password
self.hostname = hostname
self.port = port
self.sid = sid
async def connect(self):
"""建立与Oracle数据库的连接"""
connection = await cx_Oracle.connect(f"{self.username}/{self.password}@{self.hostname}:{self.port}/{self.sid}")
return connection
async def disconnect(self, connection):
"""断开与Oracle数据库的连接"""
await connection.close()
async def insert(self, table_name, data):
"""插入数据"""
connection = await self.connect()
try:
cursor = await connection.cursor()
# 构造插入语句
query = f"INSERT INTO {table_name} (column1, column2, ...) VALUES (:value1, :value2, ...)"
await cursor.execute(query, data)
await connection.commit()
finally:
await self.disconnect(connection)
async def delete(self, table_name, condition):
"""删除数据"""
connection = await self.connect()
try:
cursor = await connection.cursor()
# 构造删除语句
query = f"DELETE FROM {table_name} WHERE {condition}"
await cursor.execute(query)
await connection.commit()
finally:
await self.disconnect(connection)
async def update(self, table_name, data, condition):
"""更新数据"""
connection = await self.connect()
try:
cursor = await connection.cursor()
# 构造更新语句
query = f"UPDATE {table_name} SET column1 = :value1, column2 = :value2, ... WHERE {condition}"
await cursor.execute(query, data)
await connection.commit()
finally:
await self.disconnect(connection)
async def select(self, table_name, columns, condition):
"""查询数据"""
connection = await self.connect()
try:
cursor = await connection.cursor()
# 构造查询语句
query = f"SELECT {columns} FROM {table_name} WHERE {condition}"
await cursor.execute(query)
result = await cursor.fetchall()
return result
finally:
await self.disconnect(connection)
# 示例用法
async def main():
handler = OracleHandler("username", "password", "hostname", "port", "sid")
# 插入数据
data = {"value1": "foo", "value2": "bar"} # 替换为具体的数据
await handler.insert("table_name", data)
# 删除数据
condition = "column1 = 'foo'" # 替换为具体的条件
await handler.delete("table_name", condition)
# 更新数据
data = {"value1": "new_value"} # 替换为具体的数据
condition = "column2 = 'bar'" # 替换为具体的条件
await handler.update("table_name", data, condition)
# 查询数据
columns = "column1, column2" # 替换为具体的列名
condition = "column1 = 'foo'" # 替换为具体的条件
result = await handler.select("table_name", columns, condition)
print(result)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
在上述代码中,你需要根据实际情况替换OracleHandler
类的构造函数中的用户名、密码、主机名、端口和SID等参数。然后,你可以在main()
函数中调用OracleHandler
类的方法来执行对应的操作。
Python使用Asyncio和Futures
Asyncio 模块的另一个重要的组件是 Future
类。它和 concurrent.futures.Futures
很像,但是针对Asyncio的事件循环做了很多定制。 asyncio.Futures
类代表还未完成的结果(有可能是一个Exception)。所以综合来说,它是一种抽象,代表还没有做完的事情。
实际上,必须处理一些结果的回调函数被加入到了这个类的实例中。
要操作Asyncio中的 Future
,必须进行以下声明:
import asyncio
future = asyncio.Future()
基本的方法有:
cancel()
: 取消future的执行,调度回调函数result()
: 返回future代表的结果exception()
: 返回future中的Exceptionadd_done_callback(fn)
: 添加一个回调函数,当future执行的时候会调用这个回调函数remove_done_callback(fn)
: 从“call whten done”列表中移除所有callback的实例set_result(result)
: 将future标为执行完成,并且设置result的值set_exception(exception)
: 将future标为执行完成,并设置Exception下面的例子展示了 Future
类是如何管理两个协程的,第一个协程 first_coroutine
计算前n个数的和, 第二个协程 second_coroutine
计算n的阶乘,代码如下:
# -*- coding: utf-8 -*-
"""
Asyncio.Futures - Chapter 4 Asynchronous Programming
"""
import asyncio
import sys
@asyncio.coroutine
def first_coroutine(future, N):
"""前n个数的和"""
count = 0
for i in range(1, N + 1):
count = count + i
yield from asyncio.sleep(3)
future.set_result("first coroutine (sum of N integers) result = " + str(count))
@asyncio.coroutine
def second_coroutine(future, N):
count = 1
for i in range(2, N + 1):
count *= i
yield from asyncio.sleep(4)
future.set_result("second coroutine (factorial) result = " + str(count))
def got_result(future):
print(future.result())
if __name__ == "__main__":
N1 = int(sys.argv[1])
N2 = int(sys.argv[2])
loop = asyncio.get_event_loop()
future1 = asyncio.Future()
future2 = asyncio.Future()
tasks = [
first_coroutine(future1, N1),
second_coroutine(future2, N2)]
future1.add_done_callback(got_result)
future2.add_done_callback(got_result)
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
输出如下:
$ python asy.py 1 1
first coroutine (sum of N integers) result = 1
second coroutine (factorial) result = 1
$ python asy.py 2 2
first coroutine (sum of N integers) result = 3
second coroutine (factorial) result = 2
$ python asy.py 3 3
first coroutine (sum of N integers) result = 6
second coroutine (factorial) result = 6
$ python asy.py 4 4
first coroutine (sum of N integers) result = 10
second coroutine (factorial) result = 24
在主程序中,我们通过定义future对象和协程联系在一起:
if __name__ == "__main__":
...
future1 = asyncio.Future()
future2 = asyncio.Future()
定义tasks的时候,将future对象作为变量传入协程中:
tasks = [
first_coroutine(future1, N1),
second_coroutine(future2, N2)]
最后,添加一个 future
执行时的回调函数:
def got_result(future):
print(future.result())
在我们传入future的协程中,在计算之后我们分别添加了3s、4s的睡眠时间:
yield from asyncio.sleep(4)
然后,我们将future标为完成,通过 future.set_result()
设置结果。
交换两个协程睡眠的时间,协程2会比1更早得到结果:
$ python asy.py 3 3
second coroutine (factorial) result = 6
first coroutine (sum of N integers) result = 6
$ python asy.py 4 4
second coroutine (factorial) result = 24
first coroutine (sum of N integers) result = 10
【Python 库】requests 详解超时和重试
网络请求不可避免会遇上请求超时的情况,在 requests 中,如果不设置你的程序可能会永远失去响应。
超时又可分为连接超时和读取超时。
连接超时指的是在你的客户端实现到远端机器端口的连接时(对应的是connect()
),Request 等待的秒数。
import time
import requests
url = 'http://www.google.com.hk'
print(time.strftime('%Y-%m-%d %H:%M:%S'))
try:
html = requests.get(url, timeout=5).text
print('success')
except requests.exceptions.RequestException as e:
print(e)
print(time.strftime('%Y-%m-%d %H:%M:%S'))
因为 google 被墙了,所以无法连接,错误信息显示 connect timeout(连接超时)。
2018-12-14 14:38:20
HTTPConnectionPool(host='www.google.com.hk', port=80): Max retries exceeded with url: / (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x00000000047F80F0>, 'Connection to www.google.com.hk timed out. (connect timeout=5)'))
2018-12-14 14:38:25
就算不设置,也会有一个默认的连接超时时间(我测试了下,大概是21秒)。
读取超时指的就是客户端等待服务器发送请求的时间。(特定地,它指的是客户端要等待服务器发送字节之间的时间。在 99.9% 的情况下这指的是服务器发送第一个字节之前的时间)。
简单的说,连接超时就是发起请求连接到连接建立之间的最大时长,读取超时就是连接成功开始到服务器返回响应之间等待的最大时长。
如果你设置了一个单一的值作为 timeout,如下所示:
r = requests.get('https://github.com', timeout=5)
这一 timeout 值将会用作 connect 和 read 二者的 timeout。如果要分别制定,就传入一个元组:
r = requests.get('https://github.com', timeout=(3.05, 27))
黑板课爬虫闯关的第四关正好网站人为设置了一个15秒的响应等待时间,拿来做说明最好不过了。
import time
import requests
url_login = 'http://www.heibanke.com/accounts/login/?next=/lesson/crawler_ex03/'
session = requests.Session()
session.get(url_login)
token = session.cookies['csrftoken']
session.post(url_login, data={'csrfmiddlewaretoken': token, 'username': 'xx', 'password': 'xx'})
print(time.strftime('%Y-%m-%d %H:%M:%S'))
url_pw = 'http://www.heibanke.com/lesson/crawler_ex03/pw_list/'
try:
html = session.get(url_pw, timeout=(5, 10)).text
print('success')
except requests.exceptions.RequestException as e:
print(e)
print(time.strftime('%Y-%m-%d %H:%M:%S'))
错误信息中显示的是 read timeout(读取超时)。
2018-12-14 15:20:47
HTTPConnectionPool(host='www.heibanke.com', port=80): Read timed out. (read timeout=10)
2018-12-14 15:20:57
读取超时是没有默认值的,如果不设置,程序将一直处于等待状态。我们的爬虫经常卡死又没有任何的报错信息,原因就在这里了。
一般超时我们不会立即返回,而会设置一个三次重连的机制。
def gethtml(url):
i = 0
while i < 3:
try:
html = requests.get(url, timeout=5).text
return html
except requests.exceptions.RequestException:
i += 1
其实 requests 已经帮我们封装好了。(但是代码好像变多了...)
import time
import requests
from requests.adapters import HTTPAdapter
s = requests.Session()
s.mount('http://', HTTPAdapter(max_retries=3))
s.mount('https://', HTTPAdapter(max_retries=3))
print(time.strftime('%Y-%m-%d %H:%M:%S'))
try:
r = s.get('http://www.google.com.hk', timeout=5)
return r.text
except requests.exceptions.RequestException as e:
print(e)
print(time.strftime('%Y-%m-%d %H:%M:%S'))
max_retries
为最大重试次数,重试3次,加上最初的一次请求,一共是4次,所以上述代码运行耗时是20秒而不是15秒
2018-12-14 15:34:03
HTTPConnectionPool(host='www.google.com.hk', port=80): Max retries exceeded with url: / (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x0000000013269630>, 'Connection to www.google.com.hk timed out. (connect timeout=5)'))
2018-12-14 15:34:23
【Python】多线程之可重入锁
在上一篇文章 《【Python】线程间同步的实现与代码》中,已经介绍了线程间同步常用的方法,及如何在 Python 中使用互斥锁 Lock
,在 threading
中,除了 Lock
,还有另一种锁 RLock
。
广义上的可重入锁指的是可重复可递归调用的锁。
也就是在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。
Lock
与 RLock
threading
中,有 Lock
和 RLock
两种锁。
Lock
被称为原始锁
acquire()
和 release()
acquire()
将状态改为锁定并立即返回acquire()
将阻塞至其他线程调用 release()
将其改为非锁定状态release()
只在锁定状态下调用,将状态改为非锁定并立即返回RuntimeError
异常with
语句RLock
被称为重入锁
acquire()
和 release()
acquire()
方法,一旦线程拥有了锁,方法将返回release()
方法acquire()
/release()
对可以嵌套,重入锁必须由获取它的线程释放with
语句threading
中的 Lock
是原始锁,线程在获取到锁后,如果试图再次获取,则会被阻塞。
举个栗子:
import threading
mutex = threading.Lock()
def do_something():
if mutex.acquire(): # 获取锁
print(threading.currentThread().name + " get lock")
if mutex.acquire(): # 再次获取锁
print(threading.currentThread().name + " get lock against")
mutex.release()
mutex.release()
t1 = threading.Thread(target= do_something, name= "t1")
t2 = threading.Thread(target= do_something, name= "t2")
t1.start()
t2.start()
t1.join()
t2.join()
代码输出为: t1 get lock
上述代码中,线程 t1
获取锁后,试图再次获取锁,此时锁无法被再次获取,线程被阻塞。由于线程 t1
获取了锁,线程 t2
无法获取锁,处于阻塞状态。
下面,将上述代码中锁改为可重入锁,既把 mutex = threading.Lock()
改为 mutex = threading.RLock()
运行代码,输出为:
get lock
t1 get lock against
t2 get lock
t2 get lock against
上述代码中,线程 t1
获取锁后,再次获取锁,此时锁可以被再次获取,线程得到锁后执行操作,完成操作后释放锁。线程 t1
释放锁后,线程 t2
获取锁,执行操作。
现在有一个方法,为了保证数据安全,其会申请锁:
def func():
if lock.acquire():
print(threading.currentThread().name + " do something")
lock.release()
现在有另一个方法,为了保证数据安全,同样会申请锁,并且会调用上面的方法:
def func2():
if lock.acquire():
print(threading.currentThread().name + " do something")
print(threading.currentThread().name + " call func")
func()
lock.release()
此时,变量 lock
应该是一个可重入锁,否则线程调用 func()
后,将会被阻塞。
pyenv常用命令
# 查看当前版本
pyenv version
# 查看已安装的所有版本
pyenv versions
# 查看所有可安装的版本
pyenv install --list
# 安装指定版本
pyenv install 3.6.5
# 安装新版本后rehash一下
pyenv rehash
# 删除指定版本
pyenv uninstall 3.5.2
# 指定全局版本
pyenv global 3.6.5
# 指定多个全局版本, 3版本优先
pyenv global 3.6.5 2.7.14
# 实际上当你切换版本后, 相应的pip和包仓库都是会自动切换过去的
Mac OSX下安装配置pyenv
$ brew update
$ brew install pyenv
2. 添加shell配置文件中追加下面脚本,保证切换环境后能找到python: (如zshrc)
export PYENV_ROOR="$HOME/.pyenv"
export PATH=$PYENV_ROOT/shims:$PATH
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
3. source一下配置文件, 输入pyenv --version测试一下