背景
學習了Javascript才知道原來屬性的取值和賦值操作訪問的“位置”可能不同、還有詞法作用域這個東西,這也是我學習任何一門語言會注意的兩個知識點,Python的作用域和Javascript幾乎一致,這里就不做解釋,本文重點介紹一下三個概念:
- 屬性取值和賦值過程
- 屬性描述符
- 裝飾器
本文最好會利用這些知識介紹:如何實現自定義的@staticmethod和@classmethod。
屬性取值和賦值過程
- 一切皆是對象,類型也是對象。
- 對象包含一個__class__屬性指向其所屬類型。
- 對象包含一個__dict__屬性指向其所包含的成員(屬性和方法)。
取值過程(下面是偽代碼)
1 __getattribute__(property) logic: 2 3 descripter = find first descripter in class and bases's dict(property) 4 if descripter: 5 return descripter.__get__(instance, instance.__class__) 6 else: 7 if value in instance.__dict__ 8 return value 9 10 value = find first value in class and bases's dict(property) 11 if value is a function: 12 return bounded function(value) 13 else: 14 return value 15 16 raise AttributeNotFundedException
賦值過程(下面是偽代碼)
1 __setattr__(property, value)logic: 2 3 descripter = find first descripter in class and bases's dict(property) 4 if descripter: 5 descripter.__set__(instance, value) 6 else: 7 instance.__dict__[property] = value
根據取值和賦值過程的邏輯可以得出以下結論
- 賦值和取值的位置可能不同。
- 取值過程的偽代碼第11行會返回綁定方法(我們在Javascript經常會讓某個函數綁定到指定的作用域,和這個概念是一樣的)。
- 取值和賦值過程都會先查找屬性描述符,也就是說:屬性描述的優先級最高,下文會介紹屬性描述符。
屬性描述符
什么是屬性描述符?屬性描述符就是一個類型,實現了三個魔法方法而已:__set__、__get__和__del__,屬性描述符一般不會獨立使用,必須存儲在類型的__dict__中才有意義,這樣才會參與到屬性的取值和賦值過程,具體參見上文。
屬性描述符
1 #屬性描述符 2 class Descripter: 3 def __get__(self, instance, owner): 4 print(self, instance, owner) 5 6 def __set__(self, instance, value): 7 print(self, instance, value)
測試代碼
1 class TestClass: 2 Des = Descripter() 3 4 5 def __getattribute__(self, name): 6 print("before __getattribute__") 7 return super(TestClass, self).__getattribute__(name) 8 print("after __getattribute__") 9 10 def __setattr__(self, name, value): 11 print("before __setattr__") 12 super(TestClass, self).__setattr__(name, value) 13 print("after __setattr__") 14 15 test1 = TestClass() 16 test2 = TestClass() 17 18 test1.Des = None 19 test2.Des
輸出結果
1 before __setattr__ 2 <__main__.Descripter object at 0x01D9A030> <__main__.TestClass object at 0x01D9A090> None 3 after __setattr__ 4 before __getattribute__ 5 <__main__.Descripter object at 0x01D9A030> <__main__.TestClass object at 0x01D9A0B0> <class '__main__.TestClass'>
結論
類型的多個實例共享同一個屬性描述符實例,屬性描述符的優先級高於實例的__dict__,具體自己可以測試一下。
裝飾器(AOP)
最基本的函數裝飾器
1 print("\n最基本的函數裝飾器\n") 2 def log(fun): 3 def return_fun(*args, **kargs): 4 print("開始輸出日志") 5 6 fun(*args, **kargs) 7 8 print("結束輸出日志") 9 10 return return_fun 11 12 @log 13 def say(message): 14 print(message) 15 16 say("段光偉") 17 18 print("\n等價方法\n") 19 20 def say(message): 21 print(message) 22 23 say = log(say) 24 say("段光偉")
帶參數的函數裝飾器
1 print("\n帶參數的函數裝飾器\n") 2 def log(header, footer): 3 def log_to_return(fun): 4 def return_fun(*args, **kargs): 5 print(header) 6 7 fun(*args, **kargs) 8 9 print(footer) 10 11 return return_fun 12 return log_to_return 13 14 @log("開始輸出日志", "結束輸出日志") 15 def say(message): 16 print(message) 17 18 say("段光偉") 19 20 print("\n等價方法\n") 21 22 def say(message): 23 print(message) 24 25 say = log("開始輸出日志", "結束輸出日志")(say) 26 say("段光偉")
最基本的類型裝飾器
1 print("\n最基本的類型裝飾器\n") 2 def flyable(cls): 3 def fly(self): 4 print("我要飛的更高") 5 cls.fly = fly 6 7 return cls 8 9 @flyable 10 class Man: 11 pass 12 13 man = Man() 14 man.fly() 15 16 print("\n等價方法\n") 17 18 class Man: 19 pass 20 21 Man = flyable(Man) 22 23 man = Man() 24 man.fly()
帶參數的類型裝飾器
1 print("\n帶參數的類型裝飾器\n") 2 def flyable(message): 3 def flyable_to_return(cls): 4 def fly(self): 5 print(message) 6 cls.fly = fly 7 8 return cls 9 return flyable_to_return 10 11 @flyable("我要飛的更高") 12 class Man: 13 pass 14 15 man = Man() 16 man.fly() 17 18 print("\n等價方法\n") 19 20 class Man: 21 pass 22 23 Man = flyable("我要飛的更高")(Man) 24 25 man = Man() 26 man.fly()
備注:可以使用多個裝飾器,不過要保證簽名的裝飾器也是返回的一個方法或類型。
自己實現@staticmethod和@classmethod
理解了屬性的取值和賦值過程,開發自定義@staticmethod和@classmethod就不成問題了,let up do it!
代碼
1 class MyStaticObject: 2 def __init__(self, fun): 3 self.fun = fun; 4 5 def __get__(self, instance, owner): 6 return self.fun 7 8 def my_static_method(fun): 9 return MyStaticObject(fun) 10 11 class MyClassObject: 12 def __init__(self, fun): 13 self.fun = fun; 14 15 def __get__(self, instance, owner): 16 def class_method(*args, **kargs): 17 return self.fun(owner, *args, **kargs) 18 19 return class_method 20 21 def my_class_method(fun): 22 return MyClassObject(fun) 23 24 class C(object): 25 """docstring for C""" 26 27 def test_instance_method(self): 28 print(self) 29 30 @staticmethod 31 def test_static_method(message): 32 print(message) 33 34 @my_static_method 35 def test_my_static_method(message): 36 print(message) 37 38 @classmethod 39 def test_class_method(cls): 40 print(cls) 41 42 @my_class_method 43 def test_my_class_method(cls): 44 print(cls) 45 46 print("\n實例方法測試") 47 c = C() 48 print(C.test_instance_method) 49 print(C.__dict__["test_instance_method"]) 50 print(c.test_instance_method) 51 C.test_instance_method(c) 52 c.test_instance_method() 53 54 print("\n靜態方法測試") 55 print(C.test_static_method) 56 print(C.__dict__["test_static_method"]) 57 print(c.test_static_method) 58 C.test_static_method("靜態方法測試") 59 c.test_static_method("靜態方法測試") 60 61 print("\n自定義靜態方法測試") 62 print(C.test_my_static_method) 63 print(C.__dict__["test_my_static_method"]) 64 print(c.test_my_static_method) 65 C.test_my_static_method("自定義靜態方法測試") 66 c.test_my_static_method("自定義靜態方法測試") 67 68 print("\n類方法測試") 69 print(C.test_class_method) 70 print(C.__dict__["test_class_method"]) 71 print(c.test_class_method) 72 C.test_class_method() 73 c.test_class_method() 74 75 print("\n自定義類方法測試") 76 print(C.test_my_class_method) 77 print(C.__dict__["test_my_class_method"]) 78 print(c.test_my_class_method) 79 80 C.test_my_class_method() 81 c.test_my_class_method() 82 83 print("\n對象上的方法不會返回綁定方法,對象描述符也不會起作用") 84 def test(self): 85 print(self) 86 87 c.test = test 88 89 c.test("測試")
結果
1 實例方法測試 2 <function C.test_instance_method at 0x01D3D8A0> 3 <function C.test_instance_method at 0x01D3D8A0> 4 <bound method C.test_instance_method of <__main__.C object at 0x01D8B5B0>> 5 <__main__.C object at 0x01D8B5B0> 6 <__main__.C object at 0x01D8B5B0> 7 8 靜態方法測試 9 <function C.test_static_method at 0x01D69108> 10 <staticmethod object at 0x01D8B4F0> 11 <function C.test_static_method at 0x01D69108> 12 靜態方法測試 13 靜態方法測試 14 15 自定義靜態方法測試 16 <function C.test_my_static_method at 0x01D69078> 17 <__main__.MyStaticObject object at 0x01D8B510> 18 <function C.test_my_static_method at 0x01D69078> 19 自定義靜態方法測試 20 自定義靜態方法測試 21 22 類方法測試 23 <bound method type.test_class_method of <class '__main__.C'>> 24 <classmethod object at 0x01D8B530> 25 <bound method type.test_class_method of <class '__main__.C'>> 26 <class '__main__.C'> 27 <class '__main__.C'> 28 29 自定義類方法測試 30 <function MyClassObject.__get__.<locals>.class_method at 0x01D5EDF8> 31 <__main__.MyClassObject object at 0x01D8B550> 32 <function MyClassObject.__get__.<locals>.class_method at 0x01D5EDF8> 33 <class '__main__.C'> 34 <class '__main__.C'> 35 36 對象上的方法不會返回綁定方法,對象描述符也不會起作用 37 測試
備注
Python的學習和總結就到一段落了,繼續弄PHP,不過還會寫一篇如何用Python開發Sublime插件的教程,開發一個方便PHP開發的插件。