1. 程式人生 > >Python3描述——10個很棒的Python特性

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