<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          說說 Python 的元編程

          共 6814字,需瀏覽 14分鐘

           ·

          2021-11-27 21:00

          提到元這個(gè)字,你也許會(huì)想到元數(shù)據(jù),元數(shù)據(jù)就是描述數(shù)據(jù)本身的數(shù)據(jù),元類就是類的類,相應(yīng)的元編程就是描述代碼本身的代碼,元編程就是關(guān)于創(chuàng)建操作源代碼(比如修改、生成或包裝原來的代碼)的函數(shù)和類。主要技術(shù)是使用裝飾器、元類、描述符類。

          本文的主要目的是向大家介紹這些元編程技術(shù),并且給出實(shí)例來演示它們是怎樣定制化源代碼的行為。

          裝飾器

          裝飾器就是函數(shù)的函數(shù),它接受一個(gè)函數(shù)作為參數(shù)并返回一個(gè)新的函數(shù),在不改變?cè)瓉砗瘮?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ù)) 只需要在原來的函數(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)問是先打印 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è)問題之前,我先給你打個(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 類,也就是說 Python 類的創(chuàng)建行為由默認(rèn)的 type 類控制,打個(gè)比喻,type 類是所有類的祖先。我們可以通過編程的方式來實(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?????????????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)?????????????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

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

          >>>?Component('Widget',?'metal',?5)??????#?Blocked:?'Widget'?is?not?all?uppercase
          Traceback?(most?recent?call?last):
          ????...
          ValueError:?Expected?'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

          最后的話

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

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

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

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

          參考資料

          [1]

          管方文檔: https://docs.python.org/zh-cn/3/howto/descriptor.html

          Python貓技術(shù)交流群開放啦!群里既有國內(nèi)一二線大廠在職員工,也有國內(nèi)外高校在讀學(xué)生,既有十多年碼齡的編程老鳥,也有中小學(xué)剛剛?cè)腴T的新人,學(xué)習(xí)氛圍良好!想入群的同學(xué),請(qǐng)?jiān)诠?hào)內(nèi)回復(fù)『交流群』,獲取貓哥的微信(謝絕廣告黨,非誠勿擾!)~


          還不過癮?試試它們




          趣漫畫:Java 對(duì) Python 的滲透能成功嗎?

          Python 神器 Celery 源碼解析(2)

          Python進(jìn)階:探秘描述符的工作原理

          Python 之父爆料:明年至少令 Python 提速 1 倍!

          Python 如何僅用 5000 行代碼,實(shí)現(xiàn)強(qiáng)大的 logging 模塊?

          Python 疑難問題:[] 與 list() 哪個(gè)快?為什么快?快多少呢?


          如果你覺得本文有幫助
          請(qǐng)慷慨分享點(diǎn)贊,感謝啦
          瀏覽 73
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲天堂7777 | 性爱 91| a网站免费在线观看 | 99久| 我想看操逼网站 |