Python3描述——10個很棒的Python特性
特性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小工具。
英文原文:http://asmeurer.github.io/python3-presentation/python3-presentation.pdf