2020年7月4日 星期六

Python神乎其技學習筆記

  這本由Dan Bader著、江良志譯的《Python神乎其技》(Python Tricks: A Buffet of Awesome Python Features),應該是最有名、最頗富盛名的Python進階書籍了,在我初學Python時即已知悉此書在圖書館借閱的熱門程度相當高,此書的每一篇文章都是獨立且言簡意賅的說明範例,建議各位一定要好好研讀並親自撰寫、執行程式碼以理解Python進階的程式邏輯,此學習筆記僅是依照我自己的程度,濃縮相關之精華,並無法從中學到程式邏輯的完整過程,但若做為進階功能的重點摘錄,應該是學習後很好的溫習材料。

一、序章

1-4免費贈禮-Python Tricks數位工具箱
l   Python Tricks數位工具箱:
https://realpython.com/products/python-tricks-toolkit/

二、寫出清楚明瞭的Python程式

2-1運用斷言(assert)偵測出錯狀況
l   陷阱一-別用斷言來驗證資料:
def delete_product(prod_id, user):
    assert user.is_admin()
    assert store.has_product(prod_id)
    store.get_product(prod_id).delete()
l   陷阱二-從不失敗的斷言:
tuple做為第一個參數傳給assert語句時,該斷言條件一定為真。
assert (1 == 2, '
應該要失敗')
l   斷言功能應只用於幫助開發人員找出臭蟲,不該做為處理執行期間錯誤的機制。
l   直譯器加以設定後便能全面關閉斷言功能。

2-2小逗點大學問
l   多個相鄰字串或位元組字面值,其意義等同於串連後的值:
>>> names = [
        'Alice',
        'Bob',
        'Dilbert'
        'Jane',
        ]
>>> names
['Alice', 'Bob', 'DilbertJane']
l   每一行的末端都加上逗點。

2-3文脈管理器與with語句
l   with語句能夠把try/finally的標準寫法封裝在所謂的文脈管理器裡,簡化異常的處理。
l   就大部分的情況而言,with語句被用來管理系統資源的取得與釋放動作,當程式流程進入with語句就會取得資源,離開時則會自動釋放。

2-4底線、雙底線
l   前單底線_var
命名慣例,代表僅供內部使用的名稱,只是做為給程式設計師的提示。
l   後單底線var_
命名慣例,用來避免與Python關鍵字起名稱衝突。
l   前雙底線__var
用在類別中,會啟動名稱修飾,這是Python直譯器強迫執行的行為,可防止變數被子類別覆寫。
>>> class Test:
        def __init__(self):
                self.foo = 11
                self._bar = 23
                self.__baz = 23


>>> dir(Test())
['_Test__baz', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_bar', 'foo']
l   前後雙底線__var__
代表由Python語言定義的特別方法,你自己的屬性項應避免採用此種名稱。
l   單底線_
有時做為暫時性或不重要的變數,在Python REPL交談模式下,代表上一道運算式的執行結果。
>>> 20+3
23
>>> _
23

2-5字串格式化之驚人事實
l   舊式字串格式化:
>>> errno = 50159747054
>>> name = 'Bob'
>>> 'Hey %s, there is a %x error!' % (name, errno)
'Hey Bob, there is a badc0ffee error!'
>>> 'Hey %(Name)s, there is a %(Errno)x error!' % {'Name': name, 'Errno': errno}
'Hey Bob, there is a badc0ffee error!'
l   新式字串格式化:
>>> errno = 50159747054
>>> name = 'Bob'
>>> 'Hey {}, there is a {:x} error!'.format(name, errno)
'Hey Bob, there is a badc0ffee error!'
>>> 'Hey {Name}, there is a {Errno:x} error!'.format(Name=name, Errno=errno)
'Hey Bob, there is a badc0ffee error!'
l   字串插值(Python 3.6+):
>>> errno = 50159747054
>>> name = 'Bob'
>>> f'Hey {name}, there is a {errno:x} error!'
'Hey Bob, there is a badc0ffee error!'
>>>
>>> a = 5
>>> b = 10
>>> f'Five plus ten is {a+b}.'
'Five plus ten is 15.'
l   樣板字串,可避免安全性問題:
>>> errno = 50159747054
>>> name = 'Bob'
>>> from string import Template
>>> t = Template('Hey $Name, there is a $Errno error!')
>>> t.substitute(Name=name, Errno=hex(errno))
'Hey Bob, there is a 0xbadc0ffee error!'

2-6Python之禪彩蛋
l   >>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

三、活用函式

3-1Python函式是頭等物件
l   Python程式裡所有東西都是物件,包括函式。可指派給變數、存放在資料結構中、做為參數傳入函式、也可被函式回傳。
l   具有頭等地位的函式可用來把程式裡的行為予以抽象化、抽取出來。
l   函式可擁有巢狀結構,內部函式能夠捕捉並帶有父函式的部分狀態;具備此能力的函式,我們稱之為閉包。
l   物件能夠成為可被呼叫者,在許多情況下你可把它們當作函式來用:
>>> def make_adder(n):
        def add(x):
                return x + n
        return add

>>> plus_3 = make_adder(3)
>>> plus_5 = make_adder(5)
>>> plus_3(4)
7
>>> plus_5(4)
9

3-2lambda是含有單一運算式的函式
l   lambda的使用時機:
>>> sorted(range(-5, 6), key=lambda x: x * x)
[0, -1, 1, -2, 2, -3, 3, -4, 4, -5, 5]
l   lambda函式不能使用一般的Python語句,裡頭一定會有個隱性的return語句。

3-3裝飾器的威力
l   裝飾器定義可重複使用的建構區塊,能夠套用到可被呼叫者身上,修改其行為,而又不會永久地變更可被呼叫者本身:
>>> def uppercase(func):
        def wrapper():
                original_result = func()
                modified_result = original_result.upper()
                return modified_result
        return wrapper

>>> @uppercase
def greet():
        return 'Hello!'

>>> greet()
'HELLO!'
>>> def greet2():
        return 'Hello!'

>>> uppercase(greet2)()
'HELLO!'
l   @語法僅是個較為便捷的寫法,代表的意義就是呼叫裝飾器、傳入欲被裝飾的函式。若套用多個裝飾器在一個函式,其順序是從下往上(裝飾器堆疊):
>>> def strong(func):
        def wrapper():
                return '<strong>' + func() + '</strong>'
        return wrapper

>>> def emphasis(func):
        def wrapper():
                return '<em>' + func() + '</em>'
        return wrapper

>>> @strong
@emphasis
def greet():
        return 'Hello!'

>>> greet()
'<strong><em>Hello!</em></strong>'
>>> greet.__name__
'wrapper'
l   你自己的裝飾器請務必加上functools.wraps,便可把後設資料,從未裝飾的可被呼叫者、帶到裝飾後的函式,此乃最佳除錯實務做法:
>>> import functools
>>> def strong(func):
        @functools.wraps(func)
        def wrapper():
                return '<strong>' + func() + '</strong>'
        return wrapper

>>> def emphasis(func):
        @functools.wraps(func)
        def wrapper():
                return '<em>' + func() + '</em>'
        return wrapper

>>> @strong
@emphasis
def greet():
        return 'Hello!'

>>> greet()
'<strong><em>Hello!</em></strong>'
>>> greet.__name__
'greet'

3-4玩一玩*args**kwargs
l   *args**kwargs,讓你撰寫Python函式時可接受數量不定的參數。
l   *args收集額外的位置型參數、放在tuple裡。**kwargs收集額外的關鍵字參數、放在dict裡:
>>> def foo(required, *args, **kwargs):
        print(required)
        if args:
                args += ('extra', )
                print(args)
        if kwargs:
                kwargs['name'] = 'Alice'
                print(kwargs)

>>> foo('hello', 1, 2, 3, key1='value', key2=999)
hello
(1, 2, 3, 'extra')
{'key1': 'value', 'key2': 999, 'name': 'Alice'}
l   實際上,Python語法僅要求***。命名為argskwargs僅是約定俗成的慣例,但我們應該採用並遵守。

3-5函式參數拆箱
l   ***運算子可用來拆箱序列與字典形式的函式參數:
>>> def print_vector(x, y, z):
        print('<%s, %s, %s>' % (x, y, z))

>>> list_vec = [1, 0, 1]
>>> tuple_vec = (1, 0, 1)
>>> dict_vec = {'x': 1, 'y': 0, 'z': 1}
>>> print_vector(*list_vec)
<1, 0, 1>
>>> print_vector(*tuple_vec)
<1, 0, 1>
>>> print_vector(*dict_vec)
<x, y, z>
>>> print_vector(**dict_vec)
<1, 0, 1>

3-6此處無物可回傳
l   函式若不指定回傳值,預設會回傳None。要不要明確寫出return None則屬於程式風格的判斷。
l   隱性回傳是Python的核心功能之一,雖可用,但有時候明確寫出return None能夠更清楚地表達你的意圖。

四、類別與物件導向程式設計

4-1物件比較-is==
l   is運算子是「相同」的概念,==運算子是「相等」的概念:
>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> a is b
False
>>> a == b
True
>>> c = a
>>> a is c
True
>>> a == c
True

4-2字串轉換(每個類別都需要__repr__
l   在你自己的類別中,使用__str____repr__這兩支dunderdouble underscore)方法來控制字串轉換功能:
>>> class Car:
        def __init__(self, color, mileage):
                self.color = color
                self.mileage = mileage
        def __str__(self):
                return f'(str) a {self.color} car'
        def __repr__(self):
                return f'(repr) a {self.color} car'

>>> my_car = Car('red', 37281)
>>> my_car
(repr) a red car
>>> [my_car]
[(repr) a red car]
>>> print(my_car)
(str) a red car
>>> str(my_car)
'(str) a red car'
l   __str__的結果應可供人類閱讀,__repr__的結果應明確不含糊。
l   撰寫新類別時,請一定要加入__repr____str__的預設實作行為僅是呼叫__repr__而已。

4-3定義你自己的異常類別
l   自行定義異常類別將能更清楚地描述程式意圖,更容易除錯:
>>> class NameTooShortError(ValueError):
        pass

>>> def validate(name):
        if len(name) < 10:
                raise NameTooShortError(name)

>>> validate('jane')
Traceback (most recent call last):
  File "<pyshell#65>", line 1, in <module>
    validate('jane')
  File "<pyshell#64>", line 3, in validate
    raise NameTooShortError(name)
NameTooShortError: jane
l   定義新異常類別時,請繼承自Python內建的Exception類別,或是繼承自特定的異常類別,如ValueErrorKeyError
l   運用繼承機制,按照邏輯功能把異常類別分門別類,形成有組織的階層架構。

4-4複製物件不僅有趣且有用
l   建立物件的淺拷貝不會複製子物件,因此,拷貝副本並非完全獨立於原物件:
>>> import copy
>>> xs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> ys = copy.copy(xs)
>>> xs[1][0] = 'X'
>>> xs
[[1, 2, 3], ['X', 5, 6], [7, 8, 9]]
>>> ys
[[1, 2, 3], ['X', 5, 6], [7, 8, 9]]
l   物件的深拷貝會遞迴深入、複製每個子物件,此種拷貝程序的結果,完全獨立於原物件,但是深拷貝的執行速度比較慢:
>>> import copy
>>> xs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> ys = copy.deepcopy(xs)
>>> xs[1][0] = 'X'
>>> xs
[[1, 2, 3], ['X', 5, 6], [7, 8, 9]]
>>> ys
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
l   使用copy模組,你便能拷貝任意物件,包括自訂類別。

4-5以抽象基礎類別來檢查繼承關係
l   運用抽象基礎類別(Abstract Base ClassABC)可在建立物件實體時,檢查子類別是否實作父類別的某些特定方法:
>>> from abc import ABCMeta, abstractmethod
>>> class Base(metaclass=ABCMeta):
        @abstractmethod
        def foo(self):
                pass
        @abstractmethod
        def bar(self):
                pass

>>> class Concrete(Base):
        def foo(self):
                pass

>>> c = Concrete()
Traceback (most recent call last):
  File "<pyshell#9>", line 1, in <module>
    c = Concrete()
TypeError: Can't instantiate abstract class Concrete with abstract methods bar

4-6何時該用namedtuple(具名元組)
l   collection.namedtuplePython手動定義不可變類別的捷徑,記憶體的用量較省。
l   採用namedtuple的話等於迫使你為資料設計一套容易了解的結構,幫助你清理程式的枝節:
>>> from collections import namedtuple
>>> Car = namedtuple('Car', ['color', 'mileage'])
>>> my_car = Car('red', 3812.4)
>>> my_car
Car(color='red', mileage=3812.4)
>>> my_car.color
'red'
>>> my_car.mileage
3812.4
l   namedtuple提供了幾支好用的輔助方法,雖以單底線字元開頭,但都是我們可以使用的公開介面:
>>> from collections import namedtuple
>>> import json
>>> Car = namedtuple('Car', 'color mileage')
>>> # _make()
建立相對應的namedtuple
>>> my_car = Car._make(['red', 999])
>>> # _asdict()
以字典形式回傳namedtuple的內容
>>> my_car._asdict()
OrderedDict([('color', 'red'), ('mileage', 999)])
>>> json.dumps(my_car._asdict())
'{"color": "red", "mileage": 999}'
>>> # _replace()
建立淺拷貝,選擇性置換掉其中部分欄位
>>> my_car._replace(color='blue')
Car(color='blue', mileage=999)
>>> # _fields
屬性建立層級架構
>>> ElectricCar = namedtuple('ElectricCar', Car._fields + ('charge', ))
>>> ElectricCar('red', 1234, 45.0)
ElectricCar(color='red', mileage=1234, charge=45.0)

4-7類別變數與實體變數的使用陷阱
l   類別變數的用途是做為所有實體共享的資料,屬於類別、而非屬於某一個實體,類別的所有實體都能存取。
l   實體變數的用途是專屬於特定實體,屬於個別的單一實體物件,不會與同類別的其它實體共同擁有,每個實體變數都在該實體物件裡擁有專屬的存放空間。
l   如果類別變數和實體變數的名稱相同,前者會被後者隱藏,這種錯誤很容易犯,不小心就會蓋掉類別變數,導致預期外的臭蟲與怪異行為:
>>> class CountedObject:
        #
類別變數
        num_instances = 0
        def __init__(self):
                #
物件實體共享類別變數
                self.__class__.num_instances += 1

>>> #
存取類別變數
>>> CountedObject.num_instances
0
>>> #
存取實體變數
>>> CountedObject().num_instances
1
>>> CountedObject().num_instances
2
>>> CountedObject().num_instances
3
>>> CountedObject.num_instances
3
>>>
>>> class BuggyCountedObject:
        #
類別變數
        num_instances = 0
        def __init__(self):
                #
實體變數
                self.num_instances += 1

>>> #
存取類別變數
>>> BuggyCountedObject.num_instances
0
>>> #
存取實體變數
>>> BuggyCountedObject().num_instances
1
>>> BuggyCountedObject().num_instances
1
>>> BuggyCountedObject().num_instances
1
>>> BuggyCountedObject.num_instances
0

4-8徹底搞懂實體方法、類別方法與靜態方法
l   實體方法不僅可以修改物件狀態,也能經由self.__class__屬性項來存取類別本身,實體方法也能修改類別的狀態。
l   類別方法仍可修改類別狀態,影響該類別所有的實體。
l   靜態方法無法修改物件狀態或類別狀態,僅能存取傳給它們的參數,主要用途是為方法加上一層命名空間。
l   實體方法、類別方法與靜態方法的具體範例:
>>> class MyClass:
        #
實體方法
        def method(self):
                return 'instance method called', self
        #
類別方法
        @classmethod
        def classmethod(cls):
                return 'class method called', cls
        #
靜態方法
        @staticmethod
        def staticmethod():
                return 'static method called'

>>> obj = MyClass()
>>> #
實體方法
>>> obj.method()
('instance method called', <__main__.MyClass object at 0x0000018F6D040788>)
>>> MyClass.method(obj)
('instance method called', <__main__.MyClass object at 0x0000018F6D040788>)
>>> #
類別方法
>>> obj.classmethod()
('class method called', <class '__main__.MyClass'>)
>>> MyClass.classmethod()
('class method called', <class '__main__.MyClass'>)
>>> #
靜態方法
>>> obj.staticmethod()
'static method called'
>>> MyClass.staticmethod()
'static method called'

五、Python常用資料結構

5-1字典、映射、雜湊表
l   collections.OrderedDict-記得鍵的插入順序:
>>> import collections
>>> d = collections.OrderedDict(one=1, two=2, three=3)
>>> d
OrderedDict([('one', 1), ('two', 2), ('three', 3)])
>>> d['four'] = 4
>>> d
OrderedDict([('one', 1), ('two', 2), ('three', 3), ('four', 4)])
>>> d.keys()
odict_keys(['one', 'two', 'three', 'four'])
l   collections.defaultdict-找不到鍵時會回傳預設值:
>>> import collections
>>> dd = collections.defaultdict(list)
>>> dd['dogs'].append('Rufus')
>>> dd['dogs'].append('Kathrin')
>>> dd['dogs'].append('Mr Sniffles')
>>> dd['dogs']
['Rufus', 'Kathrin', 'Mr Sniffles']
>>> dd['cats']
[]
>>> dd
defaultdict(<class 'list'>, {'dogs': ['Rufus', 'Kathrin', 'Mr Sniffles'], 'cats': []})
l   collections.ChainMap-把多個字典當作一個映射結構進行搜尋:
>>> from collections import ChainMap
>>> dict1 = {'one': 1, 'two': 2}
>>> dict2 = {'three': 3, 'four': 4}
>>> chain = ChainMap(dict1, dict2)
>>> chain
ChainMap({'one': 1, 'two': 2}, {'three': 3, 'four': 4})
>>> chain['three']
3
>>> chain['one']
1
l   types.MappingProxyType-製作唯讀字典的包裹層:
>>> from types import MappingProxyType
>>> writable = {'one': 1, 'two': 2}
>>> read_only = MappingProxyType(writable)
>>> read_only['one']
1
>>> read_only['one'] = 23
Traceback (most recent call last):
  File "<pyshell#25>", line 1, in <module>
    read_only['one'] = 23
TypeError: 'mappingproxy' object does not support item assignment
>>> writable['one'] = 42
>>> read_only
mappingproxy({'one': 42, 'two': 2})

5-2陣列資料結構
l   需要儲存任意物件、混合不同的資料型別:
使用listtuple,根據需不需要不可變的資料結構而定。
l   需要數值型(整數或浮點數)資料、儲存空間緊密排列且存取效能表現很重要:
試試array.array,評估是否要挑出標準程式庫的框框,嘗試諸如NumPyPandas等套件。
l   需要表示為Unicode字元的文字資料:
使用Python內建的str,若需要可變的字串,可使用儲存字元的list物件。
l   想要儲存連續區塊的位元組:
使用不可變的bytes型別,若需要可變版本,則用bytearray
l   array.array-固定型別的基本陣列:
>>> import array
>>> arr = array.array('f', (1.0, 1.5, 2.0, 2.5))
>>> arr
array('f', [1.0, 1.5, 2.0, 2.5])
>>> arr[1]
1.5
>>> arr[1] = 23.0
>>> arr
array('f', [1.0, 23.0, 2.0, 2.5])
>>> del arr[1]
>>> arr
array('f', [1.0, 2.0, 2.5])
>>> arr.append(42.0)
>>> arr
array('f', [1.0, 2.0, 2.5, 42.0])
>>> arr[1] = 'hello'
Traceback (most recent call last):
  File "<pyshell#38>", line 1, in <module>
    arr[1] = 'hello'
TypeError: must be real number, not str
l   bytes-不可變的位元組陣列:
>>> arr = bytes((0, 1, 2, 3))
>>> arr
b'\x00\x01\x02\x03'
>>> arr[1]
1
>>> bytes((0, 300))
Traceback (most recent call last):
  File "<pyshell#42>", line 1, in <module>
    bytes((0, 300))
ValueError: bytes must be in range(0, 256)
>>> arr[1] = 23
Traceback (most recent call last):
  File "<pyshell#43>", line 1, in <module>
    arr[1] = 23
TypeError: 'bytes' object does not support item assignment
>>> del arr[1]
Traceback (most recent call last):
  File "<pyshell#44>", line 1, in <module>
    del arr[1]
TypeError: 'bytes' object doesn't support item deletion
l   bytearray-可變的位元組陣列:
>>> arr = bytearray((0, 1, 2, 3))
>>> arr
bytearray(b'\x00\x01\x02\x03')
>>> arr[1]
1
>>> arr[1] = 23
>>> arr
bytearray(b'\x00\x17\x02\x03')
>>> del arr[1]
>>> arr
bytearray(b'\x00\x02\x03')
>>> arr.append(42)
>>> arr
bytearray(b'\x00\x02\x03*')

5-3紀錄、結構、資料轉移物件
l   只需要少數幾個(23個)欄位:
如果欄位的順序容易記憶,或者欄位名稱很清楚,例如3D空間的點座標(x, y, z),那麼應可採用一般的tuple物件。
l   需要不可變的欄位:
碰到這種狀況時,一般的tuplecollections.namedtupletyping.NamedTuple,都算是不錯的選擇,適合用來實作此種資料物件。
l   需要固定欄位名稱、避免打錯字:
此時collections.namedtupletyping.NamedTuple會是好幫手。
l   希望越簡單越好:
一般的字典物件或許是好選擇,因為它擁有相當便利的語法,與JSON格式非常相像。
l   需要完整控制資料結構的能力:
此時該自己動手寫自訂類別,使用@property來產生設值方法和取值方法。
l   需要增加物件的行為(方法):
應該撰寫自訂類別,不論是從頭開始定義,或是延伸自collections.namedtupletyping.NamedTuple
l   需要緊密地排列資料、序列化後存入磁碟或送到網路:
請好好研讀struct.Struct,因為這是它大顯身手的時刻。
l   typing.NamedTuple-更強的具名元組:
>>> from typing import NamedTuple
>>> class Car(NamedTuple):
        color: str
        mileage: float
        automatic: bool

>>> car = Car('red', 3812.4, True)
>>> car
Car(color='red', mileage=3812.4, automatic=True)
>>> car.mileage
3812.4
>>> car.mileage = 12
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    car.mileage = 12
AttributeError: can't set attribute
l   struct.Struct-序列化C結構,功能是在Python物件值與C結構之間進行轉換,其中C結構會被序列化成為Pythonbytes物件:
>>> from struct import Struct
>>> MyStruct = Struct('i?f')
>>> data = MyStruct.pack(23, False, 42.0)
>>> data
b'\x17\x00\x00\x00\x00\x00\x00\x00\x00\x00(B'
>>> MyStruct.unpack(data)
(23, False, 42.0)
l   types.SimpleNamespace-很潮的屬性項存取方式:
>>> from types import SimpleNamespace
>>> car = SimpleNamespace(color='red', mileage=3812.4, automatic=True)
>>> car
namespace(automatic=True, color='red', mileage=3812.4)
>>> car.mileage = 12
>>> car
namespace(automatic=True, color='red', mileage=12)
>>> car.windshield = 'broken'
>>> car
namespace(automatic=True, color='red', mileage=12, windshield='broken')
>>> del car.automatic
>>> car
namespace(color='red', mileage=12, windshield='broken')

5-4集合與多重集合
l   需要可變的集合時,可採用內建的set型別。
l   frozenset-不可變的集合,為可雜湊的物件,可做為字典或集合的鍵:
>>> vowels = frozenset({'a', 'e', 'i', 'o', 'u'})
>>> vowels.add('p')
Traceback (most recent call last):
  File "<pyshell#1>", line 1, in <module>
    vowels.add('p')
AttributeError: 'frozenset' object has no attribute 'add'
>>> d = {frozenset({1, 2, 3}): 'hello'}
>>> d[frozenset({1, 2, 3})]
'hello'
l   collections.Counter-實作了多重集合(或稱為袋子)資料結構:
>>> from collections import Counter
>>> inventory = Counter()
>>> loot = {'sword': 1, 'bread': 3}
>>> inventory.update(loot)
>>> inventory
Counter({'bread': 3, 'sword': 1})
>>> more_loot = {'sword': 1, 'apple': 1}
>>> inventory.update(more_loot)
>>> inventory
Counter({'bread': 3, 'sword': 2, 'apple': 1})
>>> len(inventory)
3
>>> sum(inventory.values())
6

5-5堆疊(後進先出)
l   佇列(first-in, first-outLIFO)、堆疊(last-in, first-outLIFO)
l   list-內建的簡單堆疊:
內建型別list可做為堆疊來用,但請小心,必須以append()pop()進行新增和移除元素的動作,方能避免緩慢的效能表現。
l   collections.deque-快速且穩定的堆疊:
>>> from collections import deque
>>> s = deque()
>>> s.append('eat')
>>> s.append('sleep')
>>> s.append('code')
>>> s
deque(['eat', 'sleep', 'code'])
>>> s.pop()
'code'
>>> s.pop()
'sleep'
>>> s.pop()
'eat'
l   queue.LifoQueue-用於平行運算的上鎖機制:
>>> from queue import LifoQueue
>>> s = LifoQueue()
>>> s.put('eat')
>>> s.put('sleep')
>>> s.put('code')
>>> s
<queue.LifoQueue object at 0x0000018EC9908F48>
>>> s.get_nowait()
'code'
>>> s.get_nowait()
'sleep'
>>> s.get_nowait()
'eat'
>>> s.get_nowait()
Traceback (most recent call last):
  File "<pyshell#9>", line 1, in <module>
    s.get_nowait()
_queue.Empty

5-6佇列(先進先出)
l   list-超級緩慢的佇列:
list
物件可被當作佇列來用,但效能表現很差,一般來說不會建議採用。
l   collections.deque-快速且穩定的佇列,若不需要平行處理的支援,那麼collections.deque提供的實作會是做為FIFO佇列資料結構的不錯選項,提供良好、預期中的效能表現,也可做為堆疊(LIFO佇列)來用:
>>> from collections import deque
>>> q = deque()
>>> q.append('eat')
>>> q.append('sleep')
>>> q.append('code')
>>> q
deque(['eat', 'sleep', 'code'])
>>> q.popleft()
'eat'
>>> q.popleft()
'sleep'
>>> q.popleft()
'code'
l   queue.Queue-用於平行運算的上鎖機制:
>>> from queue import Queue
>>> q = Queue()
>>> q.put('eat')
>>> q.put('sleep')
>>> q.put('code')
>>> q
<queue.Queue object at 0x0000016FA71CFC08>
>>> q.get_nowait()
'eat'
>>> q.get_nowait()
'sleep'
>>> q.get_nowait()
'code'
>>> q.get_nowait()
Traceback (most recent call last):
  File "<pyshell#19>", line 1, in <module>
    q.get_nowait()
_queue.Empty
l   multiprocessing.Queue-共享任務佇列,允許佇列裡頭的元素被多個並行工作者平行處理:
>>> from multiprocessing import Queue
>>> q = Queue()
>>> q.put('eat')
>>> q.put('sleep')
>>> q.put('code')
>>> q
<multiprocessing.queues.Queue object at 0x0000016FA71D4D48>
>>> q.get_nowait()
'eat'
>>> q.get_nowait()
'sleep'
>>> q.get_nowait()
'code'
>>> q.get_nowait()
Traceback (most recent call last):
  File "<pyshell#29>", line 1, in <module>
    q.get_nowait()
  File "C:\Users\Timmy\AppData\Local\Continuum\anaconda3\lib\multiprocessing\queues.py", line 126, in get_nowait
    return self.get(False)
  File "C:\Users\Timmy\AppData\Local\Continuum\anaconda3\lib\multiprocessing\queues.py", line 107, in get
    raise Empty
_queue.Empty

5-7優先權佇列
l   list-手動維持排序的佇列:
>>> q = []
>>> q.append((2, 'code'))
>>> q.append((1, 'eat'))
>>> q.append((3, 'sleep'))
>>> q.sort(reverse=True)
>>> q
[(3, 'sleep'), (2, 'code'), (1, 'eat')]
>>> while q:
        next_item = q.pop()
        print(next_item)

(1, 'eat')
(2, 'code')
(3, 'sleep')
l   heapq-以串列為基礎的二元堆積,若希望避免queue.PriorityQueue上鎖機制造成的負擔,直接採用heapq也算是不錯的做法:
>>> import heapq
>>> q = []
>>> heapq.heappush(q, (2, 'code'))
>>> heapq.heappush(q, (1, 'eat'))
>>> heapq.heappush(q, (3, 'sleep'))
>>> q
[(1, 'eat'), (2, 'code'), (3, 'sleep')]
>>> while q:
        next_item = heapq.heappop(q)
        print(next_item)

(1, 'eat')
(2, 'code')
(3, 'sleep')
l   queue.PriorityQueue-絕妙的優先權佇列,最為突出,提供良好的物件導向介面,其名稱也清楚標示它的功用,這應該是第一選擇:
>>> from queue import PriorityQueue
>>> q = PriorityQueue()
>>> q.put((2, 'code'))
>>> q.put((1, 'eat'))
>>> q.put((3, 'sleep'))
>>> q
<queue.PriorityQueue object at 0x0000016FA71D4788>
>>> while not q.empty():
        next_item = q.get()
        print(next_item)

(1, 'eat')
(2, 'code')
(3, 'sleep')

六、迴圈與迭代

6-1寫出符合Python精神的迴圈
l   Python程式裡寫出C風格的迴圈會被認為不符合Python精神。請盡可能避免手動管理迴圈索引值與停止條件。
l   Pythonfor迴圈實際上是個for-each迴圈,可直接迭代容器或序列裡的項目。

6-2完全掌握生成式
l   串列生成式(List Comprehension):
>>> even_squares = [x * x for x in range(10) if x % 2 == 0]
>>> even_squares
[0, 4, 16, 36, 64]
l   集合生成式:
>>> {x * x for x in range(-9, 10)}
{64, 1, 0, 36, 4, 9, 16, 81, 49, 25}
l   字典生成式:
>>> {x: x * x for x in range(5)}
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

6-3串列切片技巧與壽司運算子
l   :」壽司運算子不僅可用來選出串列裡的子串列,也可用來清除、逆轉、拷貝串列,非常好用。

6-4美麗的迭代器
l   迭代器為Python物件提供序列介面,既能有效運用記憶體,也符合Python精神。

6-5產生器是簡化版的迭代器
l   產生器函式是Python提供語法糖,用來撰寫支援迭代器協定的物件。採用產生器的話可省卻許多類別寫法會有的標準形式程式碼。
l   yield語句可暫時停止產生器函式的執行流程,把值傳回給呼叫方。
l   當產生器函式以yield語句之外方式、離開函式的執行流程,就會開始引發StopIteration異常。

6-6產生器運算式
l   產生器運算式類似於串列生成式,但是並不會建立串列物件,而是在有需要時產生出值,如同迭代器類別和產生器函式:
>>> listcomp = ['Hello' for i in range(3)]
>>> listcomp
['Hello', 'Hello', 'Hello']
>>> genexpr = ('Hello' for i in range(3))
>>> genexpr
<generator object <genexpr> at 0x00000148C17679C8>
>>> next(genexpr)
'Hello'
>>> next(genexpr)
'Hello'
>>> next(genexpr)
'Hello'
>>> next(genexpr)
Traceback (most recent call last):
  File "<pyshell#7>", line 1, in <module>
    next(genexpr)
StopIteration
l   產生器運算式一旦耗盡,就不能重新開始或重複使用。
l   產生器運算式的最佳用途,是用來實作簡單臨時性的迭代器。若迭代器需要較為複雜的功能,最好寫成產生器函式或迭代器類別。

6-7迭代器鏈結
l   產生器可被鏈結在一起,組成效率高且可維護的資料處理管線。產生器鏈結會逐一處理流過管線的每筆資料:
>>> integers = range(8)
>>> squared = (i * i for i in integers)
>>> negated = (-i for i in squared)
>>> list(negated)
[0, -1, -4, -9, -16, -25, -36, -49]
l   運用產生器運算式可寫出精簡的管線定義,但也可能降低可讀性。

七、字典技巧

7-1字典預設值
l   需要得知成員關係時,避免手動檢查某鍵在不在字典裡。
l   採用EAFPEasier to Ask for Forgiveness than Permission)風格來處理異常,或是使用內建的get()方法都是比較推薦的做法。
l   就某些情況而言,標準程式庫裡的collections.defaultdict類別或可派上用場。

7-2排序字典不僅有趣也有利
l   建立字典與其它群集物件的排序「檢視」時,可使用鍵函式來影響排序順序:
>>> xs = {'a': 4, 'c': 2, 'b': 3, 'd': 1}
>>> sorted(xs.items())
[('a', 4), ('b', 3), ('c', 2), ('d', 1)]
>>> sorted(xs.items(), key=lambda x: x[1], reverse=True)
[('a', 4), ('b', 3), ('c', 2), ('d', 1)]
l   鍵函式是很重要的Python觀念,最常用的鍵函式甚至被納入標準程式庫的operator模組:
>>> import operator
>>> xs = {'a': 4, 'c': 2, 'b': 3, 'd': 1}
>>> sorted(xs.items(), key=operator.itemgetter(1))
[('d', 1), ('c', 2), ('b', 3), ('a', 4)]

7-3以字典模擬switch/case語句
l   Python語言沒有switch/case語句。在某些時候可採用以字典為基礎的查詢表格,避免冗長的if語句:
>>> def dispatcch_dict(operator, x, y):
        return {
                'add': lambda: x + y,
                'sub': lambda: x - y,
                'mul': lambda: x * y,
                'div': lambda: x / y,
                }.get(operator, lambda: None)()

>>> dispatcch_dict('mul', 2, 8)
16
>>> dispatcch_dict('unknown', 2, 8)
>>>

7-4瘋狂的字典運算式
l   字典運算式{True: 'yes', 1: 'no', 1.0: 'maybe'}的執行結果會是{True: maybe},因為鍵True11.0的比較結果都是相等的,而且都擁有相同的雜湊值:
>>> {True: 'yes', 1: 'no', 1.0: 'maybe'}
{True: 'maybe'}

7-5字典合併方式多又多
l   字典合併方式一:
>>> xs = {'a': 1, 'b': 2}
>>> ys = {'b': 3, 'c': 4}
>>> zs = {}
>>> zs.update(xs)
>>> zs.update(ys)
>>> zs
{'a': 1, 'b': 3, 'c': 4}
l   字典合併方式二:
>>> xs = {'a': 1, 'b': 2}
>>> ys = {'b': 3, 'c': 4}
>>> zs = dict(xs, **ys)
>>> zs
{'a': 1, 'b': 3, 'c': 4}
l   字典合併方式三:
>>> xs = {'a': 1, 'b': 2}
>>> ys = {'b': 3, 'c': 4}
>>> zs = {**xs, **ys}
>>> zs
{'a': 1, 'b': 3, 'c': 4}

7-6漂亮地印出字典
l   pprintjson模組都是高傳真的選項,內建於Python標準程式庫。
l   使用json.dumps時請小心,若含有非基礎型別的鍵或值將引發TypeError異常。

八、Python生產力技巧

8-1探索Python模組與物件
l   在直譯器互動模式下,使用內建函式dir()隨時隨地探索Python模組和類別。
l   在直譯器裡頭使用內建函式help()可自由地瀏覽各個模組與類別的文件(按q可跳出)。

8-2virtualenv隔離專案相依性(Linux
l   虛擬環境可隔離各個專案的相依性,幫助你免除版本衝突,讓同一套件與Python執行環境可存在多個版本:
#
建立新的Python虛擬環境
python3 -m venv ./venv

#
啟動新的虛擬環境
source ./venv/bin/activate

#
離開虛擬環境
deactivate
l   最佳實務做法,所有Python專案都要使用虛擬環境來儲存相依模組。

8-3窺視位元組簾幕之後
l   CPython執行程式時,首先把原始碼轉成中介位元組碼,然後以堆疊為基礎的虛擬機器執行。
l   使用內建模組dis可揭開位元組碼的面紗並加以檢視。

九、臨別贈語

9-1每週一則Python程式技巧

9-2PythonistaCafePython開發人員社群

沒有留言:

張貼留言