《Effective Python》筆記 第六章-元類與屬性
互聯網 2022/5/2 12:42:42
閱讀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
- 2022-05-19PTA編程題(python):計算分段函數
- 2022-05-19Python入門
- 2022-05-19Python基礎:int()函數
- 2022-05-19【python自動化跳轉網址】and【切換彈窗窗口】
- 2022-05-19ajax與python后端交互
- 2022-05-19用python爬取B站視頻
- 2022-05-19OpenCV-python 圖像變換操作
- 2022-05-19Python計算最長不重復子串
- 2022-05-19python+appium實戰小例子
- 2022-05-19python os.walk遍歷文件樹