特性1: 高级解包
你以前可以这么做:
>>> a, b = range(2)
>>> a
0
>>> b
1
现在可以这样:
>>> a, b, *rest = range(10)
>>> a
0
>>> b
1
>>> rest
[2, 3, 4, 5, 6, 7, 8, 9]
*rest可以出现在任何位置:
>>> a, *rest, b = range(10)
>>> a
0
>>> b
9
>>> rest
[1, 2, 3, 4, 5, 6, 7, 8]
>>> *rest, b = range(10)
>>> rest
[0, 1, 2, 3, 4, 5, 6, 7, 8]
>>> b
9
获取文件第一行和最后一行:
>>> with open("using_python_to_profit") as f:
... first, *_, last = f.readlines()
>>> first
'Step 1: Use Python 3\n'
>>> last
'Step 10: Profit!\n'
函数重构:
def f(a, b, *args):
stuff
def f(*args):
a, b, *args = args
stuff
特性2: 关键字唯一参数
def f(a, b, *args, option=True):
...
option 在*args后。
访问它的唯一方法是显式调用f(a, b, option=True)
可以只写一个 * 如果不想收集*args。
def f(a, b, *, option=True):
...
若不小心传递太多参数给函数,其中之一会被关键字参数接收。
def sum(a, b, biteme=False):
if biteme:
shutil.rmtree('/')
else:
return a + b
>>> sum(1, 2)
3
>>> sum(1, 2, 3)
替代写法。
def sum(a, b, *, biteme=False):
if biteme:
shutil.rmtree('/')
else:
return a + b
>>> sum(1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: sum() takes 2 positional arguments but 3 were given
重新排序函数的关键词参数,但是有些是隐式传递的,例如:
def maxall(iterable, key=None):
"""
A list of all max items from the iterable
"""
key = key or (lambda x: x)
m = max(iterable, key=key)
return [i for i in iterable if key(i) == key(m)]
>>> maxall(['a', 'ab', 'bc'], len)
['ab', 'bc']
max内建函数支持max(a, b, c)。我们是否也要那么做。
def maxall(*args, key=None):
"""
A list of all max items from the iterable
"""
if len(args) == 1:
iterable = args[0]
else:
iterable = args
key = key or (lambda x: x)
m = max(iterable, key=key)
return [i for i in iterable if key(i) == key(m)]
刚刚打破了以往代码不使用关键词作为第二个参数来给key传值。
>>> maxall(['a', 'ab', 'ac'], len)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 10, in maxall
TypeError: unorderable types: builtin_function_or_method() > list()
-
(事实上在Python2会返回['a', 'ab', 'ac'],见特性6)。
-
顺便说一句,max表明在Python2中已经可能,但只有当你用C写你的函数。
-
显然,应该使用maxall(iterable, *, key=None)来入门。
使你的API“与时俱进”。
不建议的做法:
def extendto(value, shorter, longer):
"""
Extend list `shorter` to the length of list `longer` with `value`
"""
if len(shorter) > len(longer):
raise ValueError('The `shorter` list is longer than the `longer` list')
shorter.extend([value]*(len(longer) - len(shorter)))
>>> a = [1, 2]
>>> b = [1, 2, 3, 4, 5]
>>> extendto(10, a, b)
>>> a
[1, 2, 10, 10, 10]
在Python3中,你可以这样用:
def extendto(value, *, shorter=None, longer=None):
"""
Extend list `shorter` to the length of list `longer` with `value`
"""
if shorter is None or longer is None:
raise TypeError('`shorter` and `longer` must be specified')
if len(shorter) > len(longer):
raise ValueError('The `shorter` list is longer than the `longer` list')
shorter.extend([value]*(len(longer) - len(shorter)))
-
现在,a和b必须像extendto(10, shorter=a, longer=b)这样传入。
-
如果你愿意,也可以像这样extendto(10, longer=b, shorter=a)。
-
不破坏API增加新的关键字参数。
-
Python3在标准库中做了这些。
-
举例,在os模块中的函数有follow_symlinks选项。
-
所以可以只使用os.stat(file, follow_symlinks=False)而不是os.lstat。
-
可以这样做:
-
s = os.stat(file, follow_symlinks=some_condition)
替代:
if some_condition:
s = os.stat(file)
else:
s = os.lstat(file)
-
但是,os.stat(file, some_condition)不可以。
-
不要想它是一个两个参数的函数。
-
在Python2中,你必须使用**kwargs并且自己做处理。
-
在你的函数头部会有许多丑陋的option = kwargs.pop(True)。
-
不再自我记录。
-
如果你正在写一个Python3代码库,我强烈建议你仅使用关键词参数,尤其关键词参数代表“选项(options)”。
特性3:连接异常
情境:你用except捕获了一个异常,做了一些事情,然后引发了一个不同的异常。
def mycopy(source, dest):
try:
shutil.copy2(source, dest)
except OSError: # We don't have permissions. More on this later
raise NotImplementedError("automatic sudo injection")
问题:您丢失了原始回溯
>>> mycopy('noway', 'noway2')
>>> mycopy(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in mycopy
NotImplementedError: automatic sudo injection
OSError发生了什么?
Python 3向您展示了整个异常链:
mycopy('noway', 'noway2')
Traceback (most recent call last):
File "<stdin>", line 3, in mycopy
File "/Users/aaronmeurer/anaconda3/lib/python3.3/shutil.py", line 243, in copy2
copyfile(src, dst, follow_symlinks=follow_symlinks)
File "/Users/aaronmeurer/anaconda3/lib/python3.3/shutil.py", line 109, in copyfile
with open(src, 'rb') as fsrc:
PermissionError: [Errno 13] Permission denied: 'noway'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in mycopy
NotImplementedError: automatic sudo injection
您还可以使用raise from手动执行此操作:
raise exception from e
>>> raise NotImplementedError from OSError
OSError
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NotImplementedError
特性4:细粒度的OSError子类
刚才给你们看的代码是错误的。
它捕获OSError并假设它是一个权限错误。
但是OSError可以是很多东西(文件没有找到,是一个目录,不是目录,断了管道,…)
但你真的必须这么做:
import errno
def mycopy(source, dest):
try:
shutil.copy2(source, dest)
except OSError as e:
if e.errno in [errno.EPERM, errno.EACCES]:
raise NotImplementedError("automatic sudo injection")
else:
raise
哇。这糟透了。
Python 3通过添加大量新异常来修复这个问题。
你可以这样做:
def mycopy(source, dest):
try:
shutil.copy2(source, dest)
except PermissionError:
raise NotImplementedError("automatic sudo injection")
(别担心,从OSError获得的PermissionError子类仍然有.errno。旧代码仍然可以工作)。
特性5:一切都是迭代器
这是最难的。
Python 2中也存在迭代器。
但你必须使用它们。不要写范围或zip或dict.values ....
如果你这样做:
def naivesum(N):
"""
Naively sum the first N integers
"""
A = 0
for i in range(N + 1):
A += i
return A
In [3]: timeit naivesum(1000000)
10 loops, best of 3: 61.4 ms per loop
In [4]: timeit naivesum(10000000)
1 loops, best of 3: 622 ms per loop
In [5]: timeit naivesum(100000000)
相反,取代一些变量(xrange, itertools)。izip dict.itervalues,……)。
不一致的API有人知道吗?
在Python 3中,range、zip、map、dict.values等都是迭代器。
如果您想要一个列表,只需将结果包装为list。
显性比隐性好。
编写不小心使用了太多内存的代码比较困难,因为输入比预期的要大。
特性6:不是一切都能比较
在python 2中,你可以这样做:
>>> max(['one', 2]) # One *is* the loneliest number
'one'
这是因为在python2里,你可以比较任何东西。
>>> 'abc' > 123
True
>>> None > all
False
在Python 3中,不能这么做。
>>> 'one' > 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: str() > int()
这可以避免一些微妙的Bug,比如,非强制转换的所有类型,从int转换成str或者反过来。
尤其当你隐式使用>时,像max或sorted。
在Python2中:
>>> sorted(['1', 2, '3'])
[2, '1', '3']
特性7:yield from
如果你使用生成器,那就太棒了
而不是写
for i in gen():
yield i
而是写成:
yield from gen()
简单地将generators重构成subgenerators。
将一切转换成生成器更简单了。看上面提到的“特性5: 一切皆迭代器”,了解为什么要这样做。
不要叠加生成一个列表,只要yield或yield from就可以了。
不会的做法:
def dup(n):
A = []
for i in range(n):
A.extend([i, i])
return A
好的做法:
def dup(n):
for i in range(n):
yield i
yield i
更好的做法:
def dup(n):
for i in range(n):
yield from [i, i]
以防你不知道,生成器是极好的,因为:
-
每次只有一个值被计算。低内存影响(见上面的range例子)。
-
可以在中间断开。不需要计算一切只是为了找到你需要的。
-
计算正是你需要的。如果你经常不需要它,你可以在这里获得很多性能。
-
如果你需要一个列表(比如,切片(slicing)),在生成器上调用list() 。
-
在yield期间,函数状态是“保存的”。
-
这导致有趣的可能性,协程式的。。。
特性8:asyncio
使用新的协同程序特性和保存的生成器状态来执行异步IO。
# Taken from Guido's slides from “Tulip: Async I/O for Python 3” by Guido
# van Rossum, at LinkedIn, Mountain View, Jan 23, 2014
@coroutine
def fetch(host, port):
r,w = yield from open_connection(host,port)
w.write(b'GET /HTTP/1.0\r\n\r\n ')
while (yield from r.readline()).decode('latin-1').strip():
pass
body=yield from r.read()
return body
@coroutine
def start():
data = yield from fetch('python.org', 80)
print(data.decode('utf-8'))
特性9:标准库添加
faulthandler
显示(有限的)回溯(tracebacks),即使当Python因某种艰难方式挂掉了。
使用kill -9时不起作用,但segfaults起作用。
import faulthandler
faulthandler.enable()
def killme():
# Taken from http://nbviewer.ipython.org/github/ipython/ipython/blob/1.x/examples/notebooks/Part%201%20-%20Running%20Code.ipynb
import sys
from ctypes import CDLL
# This will crash a Linux or Mac system; equivalent calls can be made on
# Windows
dll = 'dylib' if sys.platform == 'darwin' else 'so.6'
libc = CDLL("libc.%s" % dll)
libc.time(-1) # BOOM!!
killme()
$python test.py
Fatal Python error: Segmentation fault
Current thread 0x00007fff781b6310:
File "test.py", line 11 in killme
File "test.py", line 13 in <module>
Segmentation fault: 11
或使用kill -6(程序请求异常终止)
通过python -X faulthandler也可以激活。
ipaddress
正是如此。IP地址。
>>> ipaddress.ip_address('192.168.0.1')
IPv4Address('192.168.0.1')
>>> ipaddress.ip_address('2001:db8::')
IPv6Address('2001:db8::')
只是另一件你自己不想做的事。
functools.lru_cache
为你的函数提供一个LRU缓存装饰器。
来自文档。
@lru_cache(maxsize=32)
def get_pep(num):
'Retrieve text of a Python Enhancement Proposal'
resource = 'http://www.python.org/dev/peps/pep-%04d/' % num
try:
with urllib.request.urlopen(resource) as s:
return s.read()
except urllib.error.HTTPError:
return 'Not Found'
>>> for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991:
... pep = get_pep(n)
... print(n, len(pep))
>>> get_pep.cache_info()
CacheInfo(hits=3, misses=8, maxsize=32, currsize=8)
enum
最后,标准库中的枚举类型。
>>> from enum import Enum
>>> class Color(Enum):
... red = 1
... green = 2
... blue = 3
...
使用一些仅在Python3中有用的魔法(由于元类的改变):
>>> class Shape(Enum):
... square = 2
... square = 3
...
Traceback (most recent call last):
...
TypeError: Attempted to reuse key: 'square'
特性10:乐趣
Unicode变量名
>>> résumé = "knows Python"
>>> π = math.pi
函数注释
def f(a: stuff, b: stuff = 2) -> result:
...
注释可以是任意的Python对象。
Python对注释不做任何处理,除了把它们放在一个__annotations__字典中。
>>> def f(x: int) -> float:
... pass
...
>>> f.__annotations__
{'return': <class 'float'>, 'x': <class 'int'>}
但是它为库作者做有趣的事情打开了可能性。
举例,IPython 2.0小工具。
从IPython git中运行IPython手册(Python3中)并且打开http://127.0.0.1:8888/notebooks/examples/Interactive%20Widgets/Image%20Processing.ipynb
英文原文:http://asmeurer.github.io/python3-presentation/python3-presentation.pdf