1. <dd id="erndk"></dd>
                1. 《Effective Python》筆記 第六章-元類與屬性

                  互聯網 2022/5/2 12:42:42

                  閱讀Effective Python(第二版)的一些筆記目錄第44條 用純屬性與修飾器取代舊式的setter與getter方法第45條 考慮用@property實現新的屬性訪問邏輯,不要急著重構原有的代碼第46條 用描述符來改寫需要復用的@property方法第47條 針對惰性屬性使用__getattr__、getatt…

                  閱讀Effective Python(第二版)的一些筆記


                  目錄
                  • 第44條 用純屬性與修飾器取代舊式的setter與getter方法
                  • 第45條 考慮用@property實現新的屬性訪問邏輯,不要急著重構原有的代碼
                  • 第46條 用描述符來改寫需要復用的@property方法
                  • 第47條 針對惰性屬性使用__getattr__、getattribute__及__setattr
                  • 第48條 用__init_subclass__驗證子類寫得是否正確
                  • 第49條 用__init_subclass__記錄現有的子類
                  • 第50條 用__set_name__給類屬性加注解
                  • 第51條 優先考慮通過類修飾器來提供可組合的擴充功能,不要使用元類


                  原文地址:https://www.cnblogs.com/-beyond/p/16214895.html


                  第44條 用純屬性與修飾器取代舊式的setter與getter方法

                  有一些編程語法,會為每個屬性定義對應的getter和setter,用來獲取屬性和設置屬性值,但是在Python中,不用這么復雜,直接使用對象.屬性名來訪問即可;

                  如果在訪問屬性時需要做特殊的處理,那么可以使用@property實現,并且在使用@property時,不要引發奇怪的副作用,比如進行io操作或者其他耗時的操作。

                  # coding:utf-8
                  
                  class Person(object):
                  
                      def __init__(self):
                          self.name = None
                          self._age = None
                  
                      @property
                      def age(self):
                          print("執行@property裝飾的age()")
                          return self._age
                  
                      @age.setter
                      def age(self, new_age):
                          print("執行@age.setter裝飾的age(), new_age:%s" % new_age)
                          if new_age < 0:
                              raise Exception("age必須大于等0")
                          self._age = new_age
                  
                  
                  p = Person()
                  p.name = "abc"
                  p.age = 10   # 執行@age.setter裝飾的age(), new_age:10
                  print(p.name)  # abc
                  print(p.age)
                  # 執行@property裝飾的age()
                  # 10
                  

                  第45條 考慮用@property實現新的屬性訪問邏輯,不要急著重構原有的代碼

                  如果一個類里面屬性訪問和設置的邏輯比較復雜,現在需要修改這些邏輯,建議不要重構已有的代碼,而是使用@property來改已有的實例屬性增加功能(將已有的邏輯遷移到@property中);

                  另外如果類中的@property比較多了,這時候應該考慮這種重構這個類了,因為每個屬性的訪問和設置時,都有邏輯,那么這個類就特別復雜了。

                  第46條 用描述符來改寫需要復用的@property方法

                  @property是很方便,但是還是有一些無法處理的問題,我們需要明確的為某個屬性定義加上@property和@property.setter``,如果我們給A屬性加了,但是沒有給B屬性加,那么B就不能像A那樣使用`@property的便捷。

                  那么如何做,才能實現B不加@property也能使用@property的便捷呢?可以使用描述符;

                  什么是描述符,可以先看下面這個例子:

                  class Person(object):
                      
                      def __init__(self):
                          self.name = None
                          
                  
                  person = Person()
                  

                  當為person的name屬性復制時,person.name = "abc",翻譯為Person.__dict__['name'].__set__(person, "abc");

                  當獲取person的name屬性值時,print(person.name),翻譯為print(Person.__dict__['name'].__get__(person, "abc"));

                  也就是說,當訪問對象的屬性時,Python會轉為在類的層面查找,查詢Person類里面有沒有這樣一個屬性。如果有,而且還是個實現了__get____set__方法的對象,那么系統就認定你想通過描述符協議定義這個屬性的訪問行為。

                  將上面的翻譯拆分一下Person.__dict__['name'].__set__(person, "abc"),首先是Person.__dict__['name'],這個結果肯定是個對象,然后調用這個對象的__set__(person, "abc")__get__(person, "abc"));前面我們提到類中的屬性不加@property就不能使用@property的便捷,那么這里看來,只要Person.__dict__['xxx']都支持__set____get__即可。

                  第47條 針對惰性屬性使用__getattr__、getattribute__及__setattr

                  • 如果訪問一個類中未定義的屬性(注意不是實例屬性),就會調用該類的__getattr__方法
                  • 當要給對象的屬性設置值時候,就會調用__setattr__方法。
                  • 訪問一下類中的屬性,不論該屬性是否存在,都會調用__getattr__方法

                  一般情況下,對象可能會包含很多數據,這些數據可以在類加載的時候被初始化,這種情況可能不是最優的,也許加載的數據很久用不到,甚至運行過程不會用到,此時就可以使用懶加載,實現的時候,可以自己定義懶加載方法,另外一種方式是使用__getattr__,這種方式動態的加載數據并動態為類增加一個屬性。

                  第48條 用__init_subclass__驗證子類寫得是否正確

                  當子類繼承了父類后,父類的一些接口如果要子類保證數據的某些規則,這個時候父類怎么對子類進行限制呢?

                  一種方式就是父類中定義check方法,子類繼承后,由子類主動調用check方法來校驗參數是否符合條件,如下所示

                  #coding:utf-8
                  
                  class BaseClass(object):
                  
                      DATA = None
                  
                      @classmethod
                      def check_data(cls):
                          if cls.DATA is None:
                              raise RuntimeError
                          elif not isinstance(cls.DATA, dict):
                              raise TypeError
                  
                  
                  class ChildClass(BaseClass):
                      # 設置屬性值
                      DATA = dict()
                  
                  
                  child = ChildClass()
                  # 手動調用check
                  child.check_data()
                  
                  

                  還有一種方式,就是使用__init_subclass__,簡單的示例如下

                  #coding:utf-8
                  
                  
                  class SuperClass(object):
                  
                      def __init_subclass__(cls, **kwargs):
                          print("run SuperClass.__init_subclass__")
                          print("cls:%s" % cls)
                          print("**kwargs:%s" % kwargs)
                          
                          # 類屬性demo_value設置為abc-xyz
                          cls.demo_value = "abc-xyz"
                  
                  
                  # 定義子類
                  class SubClass(SuperClass):
                      # 子類也定義了demo_value
                      demo_value = "qaq"
                  
                  
                  sub = SubClass()
                  print(sub.demo_value)
                  # run SuperClass.__init_subclass__
                  # cls:<class '__main__.SubClass'>
                  # **kwargs:{}
                  # abc-xyz
                  
                  # 定義另外一個子類
                  class SecondClass(SubClass):
                      demo_value = "second"
                  
                  second = SecondClass()
                  print(second.demo_value)
                  # run SuperClass.__init_subclass__
                  # cls:<class '__main__.SecondClass'>
                  # **kwargs:{}
                  # abc-xyz
                  

                  看了上面__init_subclass__的例子,就可以看出,父類是可以修改子類的數據;

                  現在需要的是父類中增加子類參數的校驗,肯定是辦不到的,因為父類始終覆蓋了子類的數據,其實在繼承的時候,還可以指定參數,傳給__init_subclass_的kwargs。示例如下:

                  class SuperClass(object):
                  
                      def __init_subclass__(cls, **kwargs):
                          print("run SuperClass.__init_subclass__")
                          print("cls:%s" % cls)
                          print("**kwargs:%s" % kwargs)
                          
                          # 進行校驗
                          if "demo_value" not in kwargs:
                              raise RuntimeError
                  
                          if len(kwargs['demo_value']) < 0:
                              raise RuntimeError
                  
                          cls.demo_value = kwargs['demo_value']
                  
                  
                  # 在繼承的時候,還可以指定參數,傳給`__init_subclass_`的kwargs
                  class ThirdClass(SuperClass, demo_value="abc", other_value="dfadfasf"):
                      pass
                  
                  
                  third = ThirdClass()
                  print(third.demo_value)
                  # run SuperClass.__init_subclass__
                  # cls:<class '__main__.ThirdClass'>
                  # **kwargs:{'demo_value': 'abc', 'other_value': 'dfadfasf'}
                  # abc
                  
                  

                  于是,這樣就在父類中定義了子類數據的check。

                  第49條 用__init_subclass__記錄現有的子類

                  如果定義了一個類后,怎么知道這個類有哪些子類呢?比較簡單的大概有以下幾種思路,首先肯定會有一個幾個來保存有哪些子類,每當有新的子類時,就加入到該集合:

                  方式1:子類初始化方法中,手動將自己加入到集合中

                  #coding:utf-8
                  class SuperClass(object):
                      # 保存繼承該類的子類,key為名稱,value為type
                      SUB_CLASS_DICT = {}
                  
                      
                  class OneClass(SuperClass):
                  
                      def __init__(self):
                          SuperClass.SUB_CLASS_DICT.setdefault(self.__class__.__name__, self.__class__)
                  
                  
                  class TwoClass(SuperClass):
                  
                      def __init__(self):
                          SuperClass.SUB_CLASS_DICT.setdefault(self.__class__.__name__, self.__class__)
                  
                  
                  one = OneClass()
                  two = TwoClass()
                  print(SuperClass.SUB_CLASS_DICT)
                  # {'OneClass': <class '__main__.OneClass'>, 'TwoClass': <class '__main__.TwoClass'>}
                  

                  方式2:父類擴展__new__方法,那么子類就不需要手動將自己加入到集合中;

                  class SuperClassV2(object):
                  
                      # key為名稱,value為type
                      SUB_CLASS_DICT = {}
                  
                      def __new__(cls, *args, **kwargs):
                          cls.SUB_CLASS_DICT.setdefault(cls.__name__, cls)
                  
                  
                  class OneClassV2(SuperClassV2):
                      pass
                  
                  class TwoClassV2(SuperClassV2):
                      pass
                  
                  
                  one = OneClassV2()
                  two = TwoClassV2()
                  print(SuperClassV2.SUB_CLASS_DICT)
                  # {'OneClassV2': <class '__main__.OneClassV2'>, 'TwoClassV2': <class '__main__.TwoClassV2'>}
                  

                  方式3:使用__init_subclass__來實現,功能就是創建對象后會回調該方法,該方法定義在父類中,這樣的話,子類也不需要手動將自己加入到集合中;

                  class SuperClassV3(object):
                  
                      # key為名稱,value為type
                      SUB_CLASS_DICT = {}
                  
                      def __init_subclass__(cls, **kwargs):
                          cls.SUB_CLASS_DICT.setdefault(cls.__name__, cls)
                  
                  
                  class OneClassV3(SuperClassV3):
                      pass
                  
                  class TwoClassV3(SuperClassV3):
                      pass
                  
                  
                  one = OneClassV3()
                  two = TwoClassV3()
                  print(SuperClassV3.SUB_CLASS_DICT)
                  # {'OneClassV3': <class '__main__.OneClassV3'>, 'TwoClassV3': <class '__main__.TwoClassV3'>}
                  

                  第50條 用__set_name__給類屬性加注解

                  todo

                  第51條 優先考慮通過類修飾器來提供可組合的擴充功能,不要使用元類

                  假設有一個裝飾器,可以記錄方法執行的入參和返回值,比如下面這樣:

                  # coding:utf-8
                  import time
                  
                  def log(func):
                      def wraper(*args, **kwargs):
                          print("func_name:%s, args:%s, kwargs:%s" % (func.__name__, args, kwargs))
                          result = func(*args, **kwargs)
                          print("func_name:%s, result:%s" % (func.__name__, result))
                          return result
                  
                      return wraper
                  
                  
                  @log
                  def do_repeat(msg, repeat_times):
                      time.sleep(1)
                      return msg * repeat_times
                  
                  
                  do_repeat("abc", 10)
                  # func_name:do_repeat, args:('abc', 10), kwargs:{}
                  # func_name:do_repeat, result:abcabcabcabcabcabcabcabcabcabc
                  

                  如果一個類中有10個方法,每個方法都需要有這個功能,怎么做呢?只需要給每個方法都加上@log裝飾器即,如果有20個呢?30個呢?

                  這個時候如果還手動添加,就不太好了,怎么做可以只加一次呢?

                  方式1:可以使用元類,在__new__中統一為所有方法添加該裝飾器(相當于裝飾后覆蓋)

                  # 類中需要裝飾的類型
                  handle_types = (
                      types.MethodType,
                      types.FunctionType,
                      types.BuiltinMethodType,
                      types.BuiltinFunctionType,
                      types.MethodDescriptorType,
                      types.ClassMethodDescriptorType
                  )
                  
                  
                  class SuperClass(object):
                  
                      def __new__(cls, *args, **kwargs):
                          clazz = super().__new__(cls, *args, **kwargs)
                          # 遍歷類的方法,
                          for key in dir(clazz):
                              value = getattr(clazz, key)
                  
                              # 如果是function上面的哪幾種類型,就裝飾后進行覆蓋
                              if isinstance(value, handle_types):
                                  wraped_func = log(value)
                                  setattr(clazz, key, wraped_func)
                  		# 返回方法被裝飾后的類
                          return clazz
                  
                  
                  
                  class SubClass(SuperClass):
                  
                      def say(self, msg):
                          return "say msg----------" + msg
                  
                      def show(self, msg):
                          return "show msg________" + msg
                  
                  
                  sub = SubClass()
                  sub.say("hello")
                  # func_name:say, args:('hello',), kwargs:{}
                  # func_name:say, result:say msg----------hello
                  
                  sub.show("yes")
                  # func_name:show, args:('yes',), kwargs:{}
                  # func_name:show, result:show msg________yes
                  

                  方式2:使用類裝飾器,前面都是介紹的函數裝飾器,其實也有類裝飾器,是對類進行增強,返回一個新的類,這樣的話,就可以將上面__new__中的邏輯提到類裝飾器中,示例如下:

                  def log_class(clazz):
                  
                      # 遍歷類的方法
                      for key in dir(clazz):
                          value = getattr(clazz, key)
                  
                          # 如果是function上面的哪幾種類型,就裝飾后進行覆蓋
                          if isinstance(value, handle_types):
                              wraped_func = log(value)
                              setattr(clazz, key, wraped_func)
                  
                      return clazz
                  
                  
                  @log_class
                  class TwoClass(object):
                      def say(self, msg):
                          return "say msg----------" + msg
                  
                      def show(self, msg):
                          return "show msg________" + msg
                  
                  two = TwoClass()
                  # func_name:__new__, args:(<class '__main__.TwoClass'>,), kwargs:{}
                  # func_name:__new__, result:<__main__.TwoClass object at 0x108962a00>
                  
                  two.say("dadfd")
                  # func_name:say, args:(<__main__.TwoClass object at 0x108962a00>, 'dadfd'), kwargs:{}
                  # func_name:say, result:say msg----------dadfd
                  
                  two.show("erwer")
                  # func_name:show, args:(<__main__.TwoClass object at 0x108962a00>, 'erwer'), kwargs:{}
                  # func_name:show, result:show msg________erwer
                  

                  推薦使用類裝飾器,這樣并不會修改需要使用該裝飾器的代碼;因為前兩種方式,要么需要修改類中的每個方法,也不需要修改類的繼承,對類的侵入性比較小。

                  隨時隨地學軟件編程-關注百度小程序和微信小程序
                  關于找一找教程網

                  本站文章僅代表作者觀點,不代表本站立場,所有文章非營利性免費分享。
                  本站提供了軟件編程、網站開發技術、服務器運維、人工智能等等IT技術文章,希望廣大程序員努力學習,讓我們用科技改變世界。
                  [《Effective Python》筆記 第六章-元類與屬性]http://www.yachtsalesaustralia.com/tech/detail-318730.html

                  贊(0)
                  關注微信小程序
                  程序員編程王-隨時隨地學編程

                  掃描二維碼或查找【程序員編程王】

                  可以隨時隨地學編程啦!

                  技術文章導航 更多>
                  国产在线拍揄自揄视频菠萝

                        1. <dd id="erndk"></dd>