人妖在线一区,国产日韩欧美一区二区综合在线,国产啪精品视频网站免费,欧美内射深插日本少妇

新聞動(dòng)態(tài)

淺談Python的元編程

發(fā)布日期:2022-01-03 18:58 | 文章來(lái)源:gibhub

相應(yīng)的元編程就是描述代碼本身的代碼,元編程就是關(guān)于創(chuàng)建操作源代碼(比如修改、生成或包裝原來(lái)的代碼)的函數(shù)和類。主要技術(shù)是使用裝飾器、元類、描述符類。

一、裝飾器

裝飾器就是函數(shù)的函數(shù),它接受一個(gè)函數(shù)作為參數(shù)并返回一個(gè)新的函數(shù),在不改變?cè)瓉?lái)函數(shù)代碼的情況下為其增加新的功能,比如最常用的計(jì)時(shí)裝飾器:

from functools import wraps 
 
def timeit(logger=None): 
 """ 
 耗時(shí)統(tǒng)計(jì)裝飾器,單位是秒,保留 4 位小數(shù) 
 """ 
 
 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) 注解是很重要的, 它能保留原始函數(shù)的元數(shù)據(jù)) 只需要在原來(lái)的函數(shù)上面加上 @timeit() 即可為其增加新的功能:

@timeit() 
def test_timeit(): 
 time.sleep(1) 
 
test_timeit() 
#test_timeit cost 1.0026 seconds 

上面的代碼跟下面這樣寫的效果是一樣的:

test_timeit = timeit(test_timeit) 
test_timeit() 

二、裝飾器的執(zhí)行順序

當(dāng)有多個(gè)裝飾器的時(shí)候,他們的調(diào)用順序是怎么樣的?

假如有這樣的代碼,請(qǐng)問(wèn)是先打印 Decorator1 還是 Decorator2 ?

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 

回答這個(gè)問(wèn)題之前,我先給你打個(gè)形象的比喻,裝飾器就像函數(shù)在穿衣服,離它最近的最先穿,離得遠(yuǎn)的最后穿,上例中 decorator1 是外套,decorator2 是內(nèi)衣。

add = decorator1(decorator2(add)) 

在調(diào)用函數(shù)的時(shí)候,就像脫衣服,先解除最外面的 decorator1,也就是先打印 Decorator1,執(zhí)行到 return func(*args, **kwargs) 的時(shí)候會(huì)去解除 decorator2,然后打印 Decorator2,再次執(zhí)行到 return func(*args, **kwargs) 時(shí)會(huì)真正執(zhí)行 add() 函數(shù)。

需要注意的是打印的位置,如果打印字符串的代碼位于調(diào)用函數(shù)之后,像下面這樣,那輸出的結(jié)果正好相反:

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 

裝飾器不僅可以定義為函數(shù),也可以定義為類,只要你確保它實(shí)現(xiàn)了__call__() __get__() 方法。

關(guān)于裝飾器的其他用法,可以參考前文:

  • 我是裝飾器
  • 再談裝飾器

三、元類

Python 中所有類(object)的元類,就是 type 類,也就是說(shuō) Python 類的創(chuàng)建行為由默認(rèn)的 type 類控制,打個(gè)比喻,type 類是所有類的祖先。我們可以通過(guò)編程的方式來(lái)實(shí)現(xiàn)自定義的一些對(duì)象創(chuàng)建行為。

定一個(gè)類繼承 type 類 A,然后讓其他類的元類指向 A,就可以控制 A 的創(chuàng)建行為。典型的就是使用元類實(shí)現(xiàn)一個(gè)單例:

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__ 方法會(huì)在定義 Spam 的期間被執(zhí)行,而 __call__方法會(huì)在實(shí)例化 Spam 的時(shí)候執(zhí)行。

如果想更好的理解元類,可以閱讀Python黑魔法之metaclass詳情

四、descriptor 類(描述符類)

descriptor 就是任何一個(gè)定義了 __get__(),__set__() __delete__()的對(duì)象,描述器讓對(duì)象能夠自定義屬性查找、存儲(chǔ)和刪除的操作。這里舉官方文檔[1]一個(gè)自定義驗(yàn)證器的例子。

定義驗(yàn)證器類,它是一個(gè)描述符類,同時(shí)還是一個(gè)抽象類:

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 

自定義驗(yàn)證器需要從 Validator 繼承,并且必須提供 validate() 方法以根據(jù)需要測(cè)試各種約束。

這是三個(gè)實(shí)用的數(shù)據(jù)驗(yàn)證工具:

OneOf 驗(yàn)證值是一組受約束的選項(xiàng)之一

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 驗(yàn)證值是否為 int 或 float。根據(jù)可選參數(shù),它還可以驗(yàn)證值在給定的最小值或最大值之間。

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 驗(yàn)證值是否為 str。根據(jù)可選參數(shù),它可以驗(yàn)證給定的最小或最大長(zhǎng)度。它還可以驗(yàn)證用戶定義的 predicate

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}' 
) 

實(shí)際應(yīng)用時(shí)這樣寫:

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 

描述器阻止無(wú)效實(shí)例的創(chuàng)建:

>>> 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 

五、總結(jié)

關(guān)于 Python 的元編程,總結(jié)如下:

如果希望某些函數(shù)擁有相同的功能,希望不改變?cè)械恼{(diào)用方式、不寫重復(fù)代碼、易維護(hù),可以使用裝飾器來(lái)實(shí)現(xiàn)。

如果希望某一些類擁有某些相同的特性,或者在類定義實(shí)現(xiàn)對(duì)其的控制,我們可以自定義一個(gè)元類,然后讓它類的元類指向該類。

如果希望實(shí)例的屬性擁有某些共同的特點(diǎn),就可以自定義一個(gè)描述符類。

到此這篇關(guān)于淺談Python的元編程的文章就介紹到這了,更多相關(guān)Python的元編程內(nèi)容請(qǐng)搜索本站以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持本站!

版權(quán)聲明:本站文章來(lái)源標(biāo)注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請(qǐng)保持原文完整并注明來(lái)源及原文鏈接。禁止復(fù)制或仿造本網(wǎng)站,禁止在非www.sddonglingsh.com所屬的服務(wù)器上建立鏡像,否則將依法追究法律責(zé)任。本站部分內(nèi)容來(lái)源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來(lái),僅供學(xué)習(xí)參考,不代表本站立場(chǎng),如有內(nèi)容涉嫌侵權(quán),請(qǐng)聯(lián)系alex-e#qq.com處理。

相關(guān)文章

實(shí)時(shí)開通

自選配置、實(shí)時(shí)開通

免備案

全球線路精選!

全天候客戶服務(wù)

7x24全年不間斷在線

專屬顧問(wèn)服務(wù)

1對(duì)1客戶咨詢顧問(wèn)

在線
客服

在線客服:7*24小時(shí)在線

客服
熱線

400-630-3752
7*24小時(shí)客服服務(wù)熱線

關(guān)注
微信

關(guān)注官方微信
頂部