python3-cookbook中每個小節以問題、解決方案和討論三個部分探討了Python3在某類問題中的最優解決方式,或者說是探討Python3本身的數據結構、函數、類等特性在某類問題上如何更好地使用。這本書對於加深Python3的理解和提升Python編程能力的都有顯著幫助,特別是對怎麼提高Py ...
python3-cookbook中每個小節以問題、解決方案和討論三個部分探討了Python3在某類問題中的最優解決方式,或者說是探討Python3本身的數據結構、函數、類等特性在某類問題上如何更好地使用。這本書對於加深Python3的理解和提升Python編程能力的都有顯著幫助,特別是對怎麼提高Python程式的性能會有很好的幫助,如果有時間的話強烈建議看一下。
本文為學習筆記,文中的內容只是根據自己的工作需要和平時使用寫了書中的部分內容,並且文中的示例代碼大多直接貼的原文代碼,當然,代碼多數都在Python3.6的環境上都驗證過了的。不同領域的編程關註點也會有所不同,有興趣的可以去看全文。
python3-cookbook:https://python3-cookbook.readthedocs.io/zh_CN/latest/index.html
9.2 創建裝飾器時保留函數元信息
通常,在定義裝飾器函數時,總是應該記得使用functools.wraps來註解被包裝的函數,雖然在平時不加這個wraps裝飾器感覺也工作的很好,但其實不加的話被裝飾函數的元信息是被丟失了的,比如__name__、__doc__等信息,為了被裝飾函數的元信息不被丟失,就需要加上這個wraps裝飾器。
需要註意的是wraps應該放在包裝原始函數的那個函數定義之上,即參數為func的函數的返回值函數定義之上,特別是帶參數的裝飾器定義之中更要註意。
import time from functools import wraps def timethis(func): """普通的裝飾器""" def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(func.__name__, end - start) return result return wrapper def timethis_wraps(func): """有wraps的裝飾器""" @wraps(func) def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(func.__name__, end - start) return result return wrapper @timethis def countdown(n): """timethis裝飾函數""" while n > 0: n -= 1 @timethis_wraps def countdown_wraps(n): """timethis_wraps裝飾函數""" while n > 0: n -= 1 countdown(1000) print(countdown.__name__) print(countdown.__doc__) countdown_wraps(1000) print(countdown_wraps.__name__) print(countdown_wraps.__doc__)
countdown 0.0 wrapper None countdown_wraps 0.0 countdown_wraps timethis_wraps裝飾函數
9.6 帶可選參數的裝飾器
你想定義一個裝飾器,但使用的時候既可以傳遞參數給它,也可以不傳任何參數給它,那麼可以參考以下示例,利用functools.partial來實現。
import logging from functools import wraps, partial # 星號*的作用是迫使其後面的參數,即level,name,message三個參數,都必須使用關鍵字參數的形式傳參 def logged(func=None, *, level=logging.DEBUG, name=None, message=None): if func is None: # 此處partial的作用為返回一個函數,但它的level,name,message三個參數是已經指定好了的 # 使用的時候只需要傳入剩下的參數即可,相當於:def logged(func=None):... return partial(logged, level=level, name=name, message=message) logname = name if name else func.__module__ log = logging.getLogger(logname) logmsg = message if message else func.__name__ # 使用wraps裝飾器保證func函數的元信息是正確完整的 @wraps(func) def wrapper(*args, **kwargs): log.log(level, logmsg) return func(*args, **kwargs) return wrapper # 不傳參示例 @logged def add(x, y): return x + y # 傳參示例 @logged(level=logging.CRITICAL, name='example') def spam(): print('Spam!') print(add(2, 3)) # 輸出:5 spam() # 輸出:Spam!
9.8 將裝飾器定義為類的一部分
如果需要在裝飾器中記錄信息或綁定信息時,那麼可以考慮將裝飾器定義在類中,需要註意的是裝飾器方法為類方法和實例方法時使用的也是對應的類或者實例,而且functools.wraps包裝的函數是不用傳遞額外的cls或self參數的。
from functools import wraps class A: # 實例方法 def decorator1(self, func): # wrapper函數不用定義參數self @wraps(func) def wrapper(*args, **kwargs): print('Decorator 1') return func(*args, **kwargs) return wrapper # 類方法 @classmethod def decorator2(cls, func): # wrapper函數不用定義參數cls @wraps(func) def wrapper(*args, **kwargs): print('Decorator 2') return func(*args, **kwargs) return wrapper a = A() # 使用實例進行調用 @a.decorator1 def spam(): pass # 使用類進行調用 @A.decorator2 def grok(): pass
9.21 避免重覆的屬性方法
如果在類中定義許多重覆的對於屬性的邏輯代碼,可以考慮如下示例進行簡化代碼:需要檢查name屬性是否為str類型,檢查age屬性是否為int類型。
普通方法定義屬性的檢查:
class Person: def __init__(self, name ,age): self.name = name self.age = age @property def name(self): return self._name @name.setter def name(self, value): if not isinstance(value, str): raise TypeError('name must be a string') self._name = value @property def age(self): return self._age @age.setter def age(self, value): if not isinstance(value, int): raise TypeError('age must be an int') self._age = value
簡化後的代碼:
# 此函數返回一個屬性對象,用於檢查賦的值是否為指定的類型 # 在類中使用這個函數時,跟把這個函數中的代碼放到類中定義是一樣的,作用就只是把重覆使用的代碼提出來了 def typed_property(name, expected_type): storage_name = '_' + name @property def prop(self): return getattr(self, storage_name) @prop.setter def prop(self, value): if not isinstance(value, expected_type): raise TypeError('{} must be a {}'.format(name, expected_type)) setattr(self, storage_name, value) return prop class Person: name = typed_property('name', str) age = typed_property('age', int) def __init__(self, name, age): self.name = name self.age = age
使用functools.partial改良的代碼:
from functools import partial String = partial(typed_property, expected_type=str) Integer = partial(typed_property, expected_type=int) class Person: name = String('name') age = Integer('age') def __init__(self, name, age): self.name = name self.age = age
9.22 定義上下文管理器的簡單方法
自定義上下文管理器有兩種方式,一種是使用contextlib.contextmanager裝飾器,但是這種方式應該只用來定義函數的上下文管理器,如果是對象,比如文件、網路連接或者鎖等,就應該使用另一種方式,即給對應的類實現__enter__()方法和__exit__()方法,在進入with語句時會調用__enter__()方法,退出時調用__exit__()方法,對象的方式容易懂一點,所以這裡就只給出函數方式的示例。
from contextlib import contextmanager @contextmanager def list_transaction(orig_list): working = list(orig_list) yield working # 如果沒有報錯,對列表orig_list的任何修改才會生效 orig_list[:] = working items = [1, 2, 3] with list_transaction(items) as working: working.append(4) working.append(5) print(items) items = [1, 2, 3] with list_transaction(items) as working: working.append(4) working.append(5) raise RuntimeError('oops!') print(items)
[1, 2, 3, 4, 5] Traceback (most recent call last): File "Z:/Projects/Daily Test/test.py", line 120, in <module> raise RuntimeError('oops!') RuntimeError: oops!
9.23 在局部變數域中執行代碼
在使用exec()時,都應該想一下是否有更好的方案或者可以替代的方案。當然,如果是必須要使用exec(),那麼需要註意exec()執行時,它使用的局部變數域是拷貝自真實局部變數域,而不是直接使用的真實局部變數域,如果需要獲取exec局部變數域中的值,可以使用locals(),locals()返回一個真實局部變數域的拷貝,也是指向的exec()拷貝的局部變數域。
參考以下測試代碼和示例:
>>> a = 13 >>> exec('b = a + 1') >>> b 14
>>> def test(): a = 13 exec('b = a + 1') print(b) >>> # b並沒有在真實局部變數域中,所以會報錯 >>> test() Traceback (most recent call last): File "<pyshell#3>", line 1, in <module> test() File "<pyshell#1>", line 4, in test print(b) NameError: name 'b' is not defined >>>
>>> def test1(): x = 0 exec('x += 1') print(x) >>> test1() # x的值並沒有被修改 0 >>>
>>> def test2(): x = 0 loc = locals() print('before:', loc) exec('x += 1') print('after:', loc) print('x =', x) # 想要獲取修改後的值,可以從locals()中獲取 x = loc['x'] print('x =', x) # 註意,再次運行locals()時,原來的值會被覆蓋掉 x = 444 loc = locals() print('new:', loc) >>> test2() before: {'x': 0} after: {'x': 1, 'loc': {...}} x = 0 x = 1 new: {'x': 444, 'loc': {...}} >>>