1. 程式人生 > 其它 >Python:高階主題之(屬性取值和賦值過程、屬性描述符、裝飾器)

Python:高階主題之(屬性取值和賦值過程、屬性描述符、裝飾器)

背景

學習了Javascript才知道原來屬性的取值和賦值操作訪問的“位置”可能不同、還有詞法作用域這個東西,這也是我學習任何一門語言會注意的兩個知識點,Python的作用域和Javascript幾乎一致,這裡就不做解釋,本文重點介紹一下三個概念:

  1. 屬性取值和賦值過程
  2. 屬性描述符
  3. 裝飾器

本文最好會利用這些知識介紹:如何實現自定義的@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

根據取值和賦值過程的邏輯可以得出以下結論

  1. 賦值和取值的位置可能不同。
  2. 取值過程的虛擬碼第11行會返回繫結方法(我們在Javascript經常會讓某個函式繫結到指定的作用域,和這個概念是一樣的)。
  3. 取值和賦值過程都會先查詢屬性描述符,也就是說:屬性描述的優先順序最高,下文會介紹屬性描述符。

屬性描述符

什麼是屬性描述符?屬性描述符就是一個型別,實現了三個魔法方法而已:__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 測試