<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的元編程

          共 13284字,需瀏覽 27分鐘

           ·

          2021-09-19 15:15

          提到元這個(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 < 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

          描述器阻止無效實(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

          最后的話

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

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

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

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

          如果覺得有幫助,歡迎點(diǎn)贊、在看、轉(zhuǎn)發(fā),感謝老鐵。

          參考資料

          [1]

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


          瀏覽 33
          點(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>
                  亚洲欧美日本异类 | 国产成人无码AV在线专区 | 美国黑人大屌视频 | 传媒精品入口 | 国产美女被草 |