相應的元編程就是描述代碼本身的代碼,元編程就是關于創建操作源代碼(比如修改、生成或包裝原來的代碼)的函數和類。主要技術是使用裝飾器、元類、描述符類。
一、裝飾器
裝飾器就是函數的函數,它接受一個函數作為參數并返回一個新的函數,在不改變原來函數代碼的情況下為其增加新的功能,比如最常用的計時裝飾器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
from functools import wraps def timeit(logger = None ): """ 耗時統計裝飾器,單位是秒,保留 4 位小數 """ def decorator(func): @wraps (func) def wrapper( * args, * * kwargs): start = time.time() result = func( * args, * * kwargs) end = time.time() if logger: logger.info(f "{func.__name__} cost {end - start :.4f} seconds" ) else : print (f "{func.__name__} cost {end - start :.4f} seconds" ) return result return wrapper return decorator |
(注:比如上面使用 @wraps(func
) 注解是很重要的, 它能保留原始函數的元數據) 只需要在原來的函數上面加上 @timeit()
即可為其增加新的功能:
1
2
3
4
5
6
|
@timeit () def test_timeit(): time.sleep( 1 ) test_timeit() #test_timeit cost 1.0026 seconds |
上面的代碼跟下面這樣寫的效果是一樣的:
1
2
|
test_timeit = timeit(test_timeit) test_timeit() |
二、裝飾器的執行順序
當有多個裝飾器的時候,他們的調用順序是怎么樣的?
假如有這樣的代碼,請問是先打印 Decorator1
還是 Decorator2
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
from functools import wraps def decorator1(func): @wraps (func) def wrapper( * args, * * kwargs): print ( 'Decorator 1' ) return func( * args, * * kwargs) return wrapper def decorator2(func): @wraps (func) def wrapper( * args, * * kwargs): print ( 'Decorator 2' ) return func( * args, * * kwargs) return wrapper @decorator1 @decorator2 def add(x, y): return x + y add( 1 , 2 ) # Decorator 1 # Decorator 2 |
回答這個問題之前,我先給你打個形象的比喻,裝飾器就像函數在穿衣服,離它最近的最先穿,離得遠的最后穿,上例中 decorator1
是外套,decorator2
是內衣。
1
|
add = decorator1(decorator2(add)) |
在調用函數的時候,就像脫衣服,先解除最外面的 decorator1
,也就是先打印 Decorator1
,執行到 return func(*args, **kwargs
) 的時候會去解除 decorator2
,然后打印 Decorator2
,再次執行到 return func(*args, **kwargs
) 時會真正執行 add()
函數。
需要注意的是打印的位置,如果打印字符串的代碼位于調用函數之后,像下面這樣,那輸出的結果正好相反:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
def decorator1(func): @wraps (func) def wrapper( * args, * * kwargs): result = func( * args, * * kwargs) print ( 'Decorator 1' ) return result return wrapper def decorator2(func): @wraps (func) def wrapper( * args, * * kwargs): result = func( * args, * * kwargs) print ( 'Decorator 2' ) return result return wrapper |
裝飾器不僅可以定義為函數,也可以定義為類,只要你確保它實現了__call__()
和 __get__()
方法。
關于裝飾器的其他用法,可以參考前文:
- 我是裝飾器
- 再談裝飾器
三、元類
Python
中所有類(object)
的元類,就是 type
類,也就是說 Python
類的創建行為由默認的 type
類控制,打個比喻,type 類是所有類的祖先。我們可以通過編程的方式來實現自定義的一些對象創建行為。
定一個類繼承 type
類 A,然后讓其他類的元類指向 A,就可以控制 A 的創建行為。典型的就是使用元類實現一個單例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
class Singleton( type ): def __init__( self , * args, * * kwargs): self ._instance = None super ().__init__( * args, * * kwargs) def __call__( self , * args, * * kwargs): if self ._instance is None : self ._instance = super ().__call__( * args, * * kwargs) return self ._instance else : return self ._instance class Spam(metaclass = Singleton): def __init__( self ): print ( "Spam!!!" ) |
元類 Singleton
的__init__
和__new__
方法會在定義 Spam 的期間被執行,而 __call__
方法會在實例化 Spam
的時候執行。
如果想更好的理解元類,可以閱讀Python黑魔法之metaclass詳情
四、descriptor 類(描述符類)
descriptor
就是任何一個定義了 __get__(),__set__()
或 __delete__()
的對象,描述器讓對象能夠自定義屬性查找、存儲和刪除的操作。這里舉官方文檔[1]一個自定義驗證器的例子。
定義驗證器類,它是一個描述符類,同時還是一個抽象類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
from abc import ABC, abstractmethod class Validator(ABC): def __set_name__( self , owner, name): self .private_name = '_' + name def __get__( self , obj, objtype = None ): return getattr (obj, self .private_name) def __set__( self , obj, value): self .validate(value) setattr (obj, self .private_name, value) @abstractmethod def validate( self , value): pass |
自定義驗證器需要從 Validator
繼承,并且必須提供 validate()
方法以根據需要測試各種約束。
這是三個實用的數據驗證工具:
OneOf
驗證值是一組受約束的選項之一
1
2
3
4
5
6
7
8
|
class OneOf(Validator): def __init__( self , * options): self .options = set (options) def validate( self , value): if value not in self .options: raise ValueError(f 'Expected {value!r} to be one of {self.options!r}' ) |
Number
驗證值是否為 int 或 float。根據可選參數,它還可以驗證值在給定的最小值或最大值之間。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class Number(Validator): def __init__( self , minvalue = None , maxvalue = None ): self .minvalue = minvalue self .maxvalue = maxvalue def validate( self , value): if not isinstance (value, ( int , float )): raise TypeError(f 'Expected {value!r} to be an int or float' ) if self .minvalue is not None and value < self .minvalue: raise ValueError( f 'Expected {value!r} to be at least {self.minvalue!r}' ) if self .maxvalue is not None and value > self .maxvalue: raise ValueError( f 'Expected {value!r} to be no more than {self.maxvalue!r}' ) |
String
驗證值是否為 str。根據可選參數,它可以驗證給定的最小或最大長度。它還可以驗證用戶定義的 predicate
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class String(Validator): def __init__( self , minsize = None , maxsize = None , predicate = None ): self .minsize = minsize self .maxsize = maxsize self .predicate = predicate def validate( self , value): if not isinstance (value, str ): raise TypeError(f 'Expected {value!r} to be an str' ) if self .minsize is not None and len (value) < self .minsize: raise ValueError( f 'Expected {value!r} to be no smaller than {self.minsize!r}' ) if self .maxsize is not None and len (value) > self .maxsize: raise ValueError( f 'Expected {value!r} to be no bigger than {self.maxsize!r}' ) if self .predicate is not None and not self .predicate(value): raise ValueError( f 'Expected {self.predicate} to be true for {value!r}' ) |
實際應用時這樣寫:
1
2
3
4
5
6
7
8
9
10
|
class Component: name = String(minsize = 3 , maxsize = 10 , predicate = str .isupper) kind = OneOf( 'wood' , 'metal' , 'plastic' ) quantity = Number(minvalue = 0 ) def __init__( self , name, kind, quantity): self .name = name self .kind = kind self .quantity = quantity |
描述器阻止無效實例的創建:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
>>> Component( 'Widget' , 'metal' , 5 ) # Blocked: 'Widget' is not all uppercase Traceback (most recent call last): ... ValueError: Expected <method 'isupper' of 'str' objects> to be true for 'Widget' >>> Component( 'WIDGET' , 'metle' , 5 ) # Blocked: 'metle' is misspelled Traceback (most recent call last): ... ValueError: Expected 'metle' to be one of { 'metal' , 'plastic' , 'wood' } >>> Component( 'WIDGET' , 'metal' , - 5 ) # Blocked: -5 is negative Traceback (most recent call last): ... ValueError: Expected - 5 to be at least 0 >>> Component( 'WIDGET' , 'metal' , 'V' ) # Blocked: 'V' isn't a number Traceback (most recent call last): ... TypeError: Expected 'V' to be an int or float >>> c = Component( 'WIDGET' , 'metal' , 5 ) # Allowed: The inputs are valid |
五、總結
關于 Python 的元編程,總結如下:
如果希望某些函數擁有相同的功能,希望不改變原有的調用方式、不寫重復代碼、易維護,可以使用裝飾器來實現。
如果希望某一些類擁有某些相同的特性,或者在類定義實現對其的控制,我們可以自定義一個元類,然后讓它類的元類指向該類。
如果希望實例的屬性擁有某些共同的特點,就可以自定義一個描述符類。
到此這篇關于淺談Python的元編程的文章就介紹到這了,更多相關Python的元編程內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://www.tuicool.com/articles/MVbiQv2