描述器

描述器

|[Descriptors]
描述器的表現
  • 用到3個魔術方法:__get__()、__set__()、__delete__()
  • 方法用法:
  • object.__get__(self,instance,owner)
  • object.__set__(self,instance,value)
  • object.__delete__(self,instance)
  • self指代當前實例,調用者
  • instance是owner的實例
  • owner是屬性所屬的類
  • 看下面代碼,思考執行流程。
class A:
def __init__(self):
self.a1 = ‘a1’
print(“A.init”)
class B:
x = A()
def __init__(self):
print(“B.init”)
print(‘-‘*30)
print(B.x.a1)
print(‘=’*30)
b = B()
print(b.x.a1)
—————————
A.init#先打印這個是因為在函數載入內存時已經完成初始化
——————————
a1
==============================
B.init
a1
  • 結論:
    • 類加載的時候,類變量需要先生成,而類B的x屬性是類A的實例,所以類A先初始化,所以打印A.init
    • 然后執行到B.x.a1.然后打印實例化并初始化B的實例b
    • 打印b.x.a1,會查找屬性b.x,指向A的實例,所以返回A實例的屬性a1的值
__get__方法舉例
class A:
def __init__(self):
self.a1 = ‘a1’
print(“A.init”)
def __get__(self, instance, owner):
print(“A.__get__ {}/{}/{}”.format(self,instance,owner))
class B:
x = A()
def __init__(self):
print(“B.init”)
print(‘-‘*30)
print(B.x)
#print(B.x.a1)#拋異常AttributeError:NoneType object has no attribute a1
print(‘=’*30)
b = B()
print(b.x)
+++++++++++++++++++++++++++++++++++++
A.init
——————————
A.__get__ <__main__.A object at 0x000000310E7387B8>/None/<class ‘__main__.B’>
None
==============================
B.init
A.__get__ <__main__.A object at 0x000000310E7387B8>/<__main__.B object at 0x000000310E738828>/<class ‘__main__.B’>
None
  • 因為定義了__get__方法,類A就是一個描述器,對類B或者類B的實例的x屬性讀取,成為對類A的實例的訪問,就會調用get方法
  • 解決上面方法中B.x.a1拋錯問題,報錯是因為添加get方法后造成的,get三個參數的的含義:
  • slef都是A的實例
  • owner都是B類
  • instance說明:
    • None表示沒有B類的實例,對應調用B.x
    • <__main__.B object at 0x000000310E738828>表示時B的實例,對應調用B().x
  • 使用返回值解決,返回self,就是A的實例,該實例有a1屬性,返回正常
class A:
def __init__(self):
self.a1 = ‘a1’
print(“A.init”)
def __get__(self, instance, owner):
print(“A.__get__ {}/{}/{}”.format(self,instance,owner))
return self
class B:
x = A()
def __init__(self):
print(“B.init”)
print(‘-‘*30)
print(B.x)
print(B.x.a1)#上一版因為B.x的return默認為None所以None.a1肯定會報錯
print(‘=’*30)
b = B()
print(b.x)
print(b.x.a1)
———————————-
A.init
——————————
A.__get__ <__main__.A object at 0x000000F4472A87B8>/None/<class ‘__main__.B’>
<__main__.A object at 0x000000F4472A87B8>
A.__get__ <__main__.A object at 0x000000F4472A87B8>/None/<class ‘__main__.B’>
a1
==============================
B.init
A.__get__ <__main__.A object at 0x000000F4472A87B8>/<__main__.B object at 0x000000F4472A8828>/<class ‘__main__.B’>
<__main__.A object at 0x000000F4472A87B8>
A.__get__ <__main__.A object at 0x000000F4472A87B8>/<__main__.B object at 0x000000F4472A8828>/<class ‘__main__.B’>
a1

class A:
def __init__(self):
self.a1 = ‘a1’
print(“A.init”)
def __get__(self, instance, owner):
print(“A.__get__ {}/{}/{}”.format(self,instance,owner))
return self
class B:
x = A()
def __init__(self):
print(“B.init”)
self.b = A()#實例屬性也指向一個A的實例
print(‘-‘*30)
print(B.x)
print(B.x.a1)
print(‘=’*30)
b = B()
print(b.x)
print(b.x.a1)
print(b.b)
———————-
A.init
——————————
A.__get__ <__main__.A object at 0x000000D7F03C87B8>/None/<class ‘__main__.B’>
<__main__.A object at 0x000000D7F03C87B8>
A.__get__ <__main__.A object at 0x000000D7F03C87B8>/None/<class ‘__main__.B’>
a1
==============================
B.init
A.init
A.__get__ <__main__.A object at 0x000000D7F03C87B8>/<__main__.B object at 0x000000D7F03C87F0>/<class ‘__main__.B’>
<__main__.A object at 0x000000D7F03C87B8>
A.__get__ <__main__.A object at 0x000000D7F03C87B8>/<__main__.B object at 0x000000D7F03C87F0>/<class ‘__main__.B’>
a1
<__main__.A object at 0x000000D7F03C8860>#這個結果并沒有出發get方法
結論:只有類屬性是類的實例才可以觸發get
描述器定義
  • Python中,一個類實現了__get__、__set__、__delete__三個方法中的任何一個方法,就是描述器
  • 如果僅實現了__get__,就是非數據描述符non-data descriptor
  • 同時實現了__get__、__set__就是數據描述符data descriptor
  • 屬性的訪問順序
class A:
def __init__(self):
self.a1 = ‘a1’
print(“A.init”)
def __get__(self, instance, owner):
print(“A.__get__ {}/{}/{}”.format(self,instance,owner))
return self
class B:
x = A()
def __init__(self):
print(“B.init”)
self.x = ‘b.x’
print(‘-‘*30)
print(B.x)
print(B.x.a1)
print(‘=’*30)
b = B()
print(b.x)
print(b.x.a1)#attributeerror:str object has no attribute a1
———————-
A.init
——————————
A.__get__ <__main__.A object at 0x000000BC91538860>/None/<class ‘__main__.B’>
<__main__.A object at 0x000000BC91538860>
A.__get__ <__main__.A object at 0x000000BC91538860>/None/<class ‘__main__.B’>
a1
==============================
B.init
b.x#用實例化的對象去調用x時,查看到本身字典中有key則直接返回b.x
  • b.x訪問到了實例的屬性,而不是描述器
  • 下面代碼為A類增加set方法
class A:
def __init__(self):
self.a1 = ‘a1’
print(“A.init”)
def __get__(self, instance, owner):
print(“A.__get__ {}/{}/{}”.format(self,instance,owner))
return self
def __set__(self, instance, value):
print(‘A.__set__ {}-{}-{}’.format(self,instance,value))
self.data = value
class B:
x = A()
def __init__(self):
print(“B.init”)
self.x = ‘b.x’
print(‘-‘*30)
print(B.x)
print(B.x.a1)
print(‘=’*30)
b = B()
print(b.x)
print(b.x.a1)
—————————
A.init
——————————
A.__get__ <__main__.A object at 0x00000034F8688860>/None/<class ‘__main__.B’>
<__main__.A object at 0x00000034F8688860>
A.__get__ <__main__.A object at 0x00000034F8688860>/None/<class ‘__main__.B’>
a1
==============================
B.init
A.__set__ <__main__.A object at 0x00000034F8688860>-<__main__.B object at 0x00000034F8688898>-b.x
A.__get__ <__main__.A object at 0x00000034F8688860>/<__main__.B object at 0x00000034F8688898>/<class ‘__main__.B’>
<__main__.A object at 0x00000034F8688860>
A.__get__ <__main__.A object at 0x00000034F8688860>/<__main__.B object at 0x00000034F8688898>/<class ‘__main__.B’>
a1#可以返回a1
結論:屬性查找順序:
實例的__dict__優先于非數據描述器
數據描述器 優先于實例的__dict__
__delete__方法有同樣的效果,有了這個方法,就是數據描述器
  • 增加b.x = 500,這是調用數據描述器的__set__方法,或調用那個非數據描述器的實例覆蓋
  • B.x = 600,賦值即定義,這是覆蓋類的屬性。類的字典更新
本質(進階)
  • python真的會做這么復雜嗎,再來一套屬性查找順序規則?看看非數據描述器和數據描述器,類B及其__dict__的變化
  • 屏蔽和不屏蔽__set__方法,看看變化
class A:
def __init__(self):
self.a1 = ‘a1’
print(“A.init”)
def __get__(self, instance, owner):
print(“A.__get__ {}/{}/{}”.format(self,instance,owner))
return self
# def __set__(self, instance, value):
# print(‘A.__set__ {}-{}-{}’.format(self,instance,value))
# self.data = value
class B:
x = A()
def __init__(self):
print(“B.init”)
self.x = ‘b.x’
self.y = ‘b.y’
print(‘-‘*30)
print(B.x)
print(B.x.a1)
—————————
#屏蔽set方法結果如下
A.init
——————————
A.__get__ <__main__.A object at 0x000000954E2F87B8>/None/<class ‘__main__.B’>
<__main__.A object at 0x000000954E2F87B8>
A.__get__ <__main__.A object at 0x000000954E2F87B8>/None/<class ‘__main__.B’>
a1
==============================
B.init
b.x
b.y
字典
{‘x’: ‘b.x’, ‘y’: ‘b.y’}
{‘x’: <__main__.A object at 0x000000954E2F87B8>, ‘__module__’: ‘__main__’, ‘__init__’: <function B.__init__ at 0x000000954E2EB840>, ‘__weakref__’: <attribute ‘__weakref__’ of ‘B’ objects>, ‘__doc__’: None, ‘__dict__’: <attribute ‘__dict__’ of ‘B’ objects>}
#不屏蔽set方法結果如下:
A.init
——————————
A.__get__ <__main__.A object at 0x0000006E4ED187B8>/None/<class ‘__main__.B’>
<__main__.A object at 0x0000006E4ED187B8>
A.__get__ <__main__.A object at 0x0000006E4ED187B8>/None/<class ‘__main__.B’>
a1
==============================
B.init
A.__set__ <__main__.A object at 0x0000006E4ED187B8>-<__main__.B object at 0x0000006E4ED18828>-b.x
A.__get__ <__main__.A object at 0x0000006E4ED187B8>/<__main__.B object at 0x0000006E4ED18828>/<class ‘__main__.B’>
<__main__.A object at 0x0000006E4ED187B8>
b.y
字典
{‘y’: ‘b.y’}###此處可見差別
{‘__weakref__’: <attribute ‘__weakref__’ of ‘B’ objects>, ‘x’: <__main__.A object at 0x0000006E4ED187B8>, ‘__dict__’: <attribute ‘__dict__’ of ‘B’ objects>, ‘__init__’: <function B.__init__ at 0x0000006E4ED0B8C8>, ‘__doc__’: None, ‘__module__’: ‘__main__’}
  • 原來不是什么數據描述器優先級高,而是把實例的屬性從__dict__中去掉,造成了該屬性如果是數據描述器優先訪問的假象
  • 說到底,屬性訪問順序從來沒有變過
Python中的描述器
  • 描述器在python中廣泛應用
  • python的方法(包括staticmethod()和classmethod())都實現為非數據描述器,因此,實例可以重新定義和覆蓋方法。這允許單個實例獲取與同一類的其他實例不同的行為?
  • property()函數實現為一個數據描述器,因此,實例不能覆蓋屬性的行為。
class A:
@classmethod#非數據類型
def foo(cls):
pass
@staticmethod#非數據類型
def bar():
pass
@property#數據類型
def z(self):
return 5
def getfoo(self):#非數據類型
return self.foo
def __init__(self):#非數據類型
self.foo = 100
self.bar = 200
#self.z = 300
a = A()
print(a.__dict__)
print(A.__dict__)
———————————-
{‘bar’: 200, ‘foo’: 100}
{‘__module__’: ‘__main__’, ‘getfoo’: <function A.getfoo at 0x000000C288C8B950>, ‘__doc__’: None, ‘__weakref__’: <attribute ‘__weakref__’ of ‘A’ objects>, ‘__init__’: <function A.__init__ at 0x000000C288C8B9D8>, ‘z’: <property object at 0x000000C288C96458>, ‘__dict__’: <attribute ‘__dict__’ of ‘A’ objects>, ‘bar’: <staticmethod object at 0x000000C288C98908>, ‘foo’: <classmethod object at 0x000000C288C988D0>}
  • foo、bar都可以在實例中覆蓋,z不可以
練習
1.實現StaticMethod裝飾器,完成staticmethod裝飾器的功能
class StaticMethod:
def __init__(self,fn):
self._fn = fn
def __get__(self, instance, owner):
print(‘get’)
return self._fn
class A:
@StaticMethod#stmd = Staticmethod(stmd)
def stmd():
print(‘static method’)
A.stmd()
A().stmd()
print(A.__dict__)
—————————-
get
static method
get
static method
{‘__dict__’: <attribute ‘__dict__’ of ‘A’ objects>, ‘__module__’: ‘__main__’, ‘stmd’: <__main__.StaticMethod object at 0x000000BD760B8320>, ‘__weakref__’: <attribute ‘__weakref__’ of ‘A’ objects>, ‘__doc__’: None}
2.實現ClassMethod裝飾器,完成classmethod裝飾器的功能
from functools import partial
#類classmethod裝飾器
class ClassMethod:
def __init__(self,fn):
self._fn = fn
def __get__(self, instance, owner):
ret = self._fn(owner)
return ret
class A:
@ClassMethod#clsmth = ClassMethod(clsmth)
def clsmth(cls):
print(cls.__name__)
print(A.__dict__)
A.clsmth
A.clsmth()
———————–
{‘clsmth’: <__main__.ClassMethod object at 0x000000E925388828>, ‘__dict__’: <attribute ‘__dict__’ of ‘A’ objects>, ‘__doc__’: None, ‘__weakref__’: <attribute ‘__weakref__’ of ‘A’ objects>, ‘__module__’: ‘__main__’}
A
File “F:/pycharm_product/python/1117/7.py”, line 37, in <module>
A
A.clsmth()
TypeError: ‘NoneType’ object is not callable
  • A.clsmtd()的意思就是None(),一定報錯,如何改?
  • A.clsmtd()其實應該是A.clsmtd(cls)(),應該怎么處理?
  • A.clsmetd = A.clsmtd(cls)
  • 用partial函數
# class StaticMethod:
# def __init__(self,fn):
# self._fn = fn
#
# def __get__(self, instance, owner):
# print(‘get’)
# return self._fn
#
# class A:
#
# @StaticMethod#stmd = Staticmethod(stmd)
# def stmd():
# print(‘static method’)
#
# A.stmd()
# A().stmd()
# print(A.__dict__)
#
from functools import partial
#類classmethod裝飾器
class ClassMethod:
def __init__(self,fn):
self._fn = fn
def __get__(self, instance, cls):
ret = partial(self._fn,cls)
return ret
class A:
@ClassMethod#clsmth = ClassMethod(clsmth)
def clsmth(cls):
print(cls.__name__)
print(A.__dict__)
print(A.clsmth)
A.clsmth()
—————————-
{‘__doc__’: None, ‘__module__’: ‘__main__’, ‘__dict__’: <attribute ‘__dict__’ of ‘A’ objects>, ‘__weakref__’: <attribute ‘__weakref__’ of ‘A’ objects>, ‘clsmth’: <__main__.ClassMethod object at 0x000000F5D31187F0>}
functools.partial(<function A.clsmth at 0x000000F5D31791E0>, <class ‘__main__.A’>)
A
  • 對實例的數據進行校驗
class Person:
def __init__(self,name:str,age:int):
self.name = name
self.age = age
  • 對上面類的實例的屬性,name、age進行數據校驗
  • 方法一:寫函數,在init中檢查如果不合格,直接拋異常
#缺點:耦合度高,
class Person:
def __init__(self,name:str,age:int):
params = ((name,str),(age,int))
if not self.checkdata(params):
raise TypeError()
self.name = name
self.age = age
def checkdata(self,params):
for p,t in params:
if not isinstance(p,t):
return False
return True
p = Person(‘tom’,’20’)#傳入類型錯誤
  • 方法二:裝飾器,使用inspect模塊完成
  • 方法三描述器
#缺點:有硬編碼,能否直接獲取形參類型,使用inspect模塊,如下個方法
class Type:
def __init__(self,name,type):
self.name = name
self.type = type
def __get__(self, instance, owner):
if instance is not None:
return instance.__dict__[self.name]
return self
def __set__(self, instance, value):
if not isinstance(value,self.type):
raise TypeError(value)
instance.__dict__[self.name] = value
class Person:
name = Type(‘name’,str)
age = Type(‘age’,int)
def __init__(self,name:str,age:int):
self.name = name
self.age = age
p = Person(‘tom’,’20’)
———————————–
Traceback (most recent call last):
File “F:/pycharm_product/python/1117/8.py”, line 40, in <module>
p = Person(‘tom’,’20’)
File “F:/pycharm_product/python/1117/8.py”, line 38, in __init__
self.age = age
File “F:/pycharm_product/python/1117/8.py”, line 29, in __set__
raise TypeError(value)
TypeError: 20
# class Person:
# def __init__(self,name:str,age:int):
# params = ((name,str),(age,int))
# if not self.checkdata(params):
# raise TypeError()
# self.name = name
# self.age = age
#
# def checkdata(self,params):
# for p,t in params:
# if not isinstance(p,t):
# return False
# return True
#
# p = Person(‘tom’,’20’)
class Typed:
def __init__(self,name,type):
self.name = name
self.type = type
def __get__(self, instance, owner):
if instance is not None:
return instance.__dict__[self.name]
return self
def __set__(self, instance, value):
if not isinstance(value,self.type):
raise TypeError(value)
instance.__dict__[self.name] = value
import inspect
def typeassert(cls):
params = inspect.signature(cls).parameters
print(params)
for name,param in params.items():
print(param.name,param.annotation)
if param.annotation != param.empty:#注入類型
setattr(cls,name,Typed(name,param.annotation))
return cls
@typeassert
class Person:
#name = Typed(‘name’,str)#裝飾器注入
#age = Typed(‘age’,int)
def __init__(self,name:str,age:int):
self.name = name
self.age = age
p = Person(‘tom’,20)
————————-
OrderedDict([(‘name’, <Parameter “name:str”>), (‘age’, <Parameter “age:int”>)])
name <class ‘str’>
age <class ‘int’>

本文來自投稿,不代表Linux運維部落立場,如若轉載,請注明出處:http://www.www58058.com/89076

(0)
Thunk_LeeThunk_Lee
上一篇 2017-11-28 10:04
下一篇 2017-11-29 12:52

相關推薦

  • wed服務基礎·httpd基礎配置詳解

    一、 Web Service基礎: service:計算機后臺提供的功能或計算機可以提供的某一種功能 Web Service本質:通過網絡調用其它網站的資源 根據來源的不同,分為兩種服務: 本地服務:使用同一臺機器提供的服務,不需要網絡 網絡服務:使用另一臺機器提供的服務,需要網絡   IANA互聯網地址授權機構(Internet Assigned…

    2017-06-09
  • VMware vSphere所需要開放的端口

        80 vCenter Server需要端口80用于直接HTTP連接。端口80會將請求重定向到HTTPS端口443。如果意外使用了http://server而不是https://server,此端口將非常有用。     389 此端口在vCenter Server的本地和所…

    Linux干貨 2016-07-07
  • Linux的終端類型

    一、了解終端   在早期的年代,主機不是很多,都是一系列的大型主機,簡單來說就是用戶很多,但主機很少,不可能做到人手一臺,但可以在主機上連接一個分屏器,在分屏器上可以連接鼠標鍵盤以及顯示器,這些東西是沒有計算能力的,僅僅擔任輸入和輸出的工作,運算和處理都是由主機來完成的。   簡單來說終端是用戶與主機交互,是必然用到的…

    Linux干貨 2016-10-14
  • 來兩道百度的shell開胃菜

    1、寫腳本實現,可以用shell、perl等。在目錄/tmp下找到100個以abc開頭的文件,然后把這些文件的第一行保存到文件new中。 方法1: #!/bin/sh for files in `find /tmp -type f -name "abc*"|h…

    Linux干貨 2016-09-19
  • 進程管理之工作管理詳解(job control)

    進程管理之工作管理詳解(job control) 1 什么是工作管理(job control)   我們知道linux是多任務多終端工作的操作系統。我們可以在多個終端進行工作,也可以在一個終端進行多個任務工作。那在一個終端同時進行多個工作任務,就稱為工作管理。比如這種情況,在一個終端,你想要復制文件,同時你還想壓縮打包文件,甚至你還想編輯文件,這個…

    Linux干貨 2017-05-14
  • Linux用戶管理相關(2)

    Q1:復制/etc/skel目錄為/home/tuser1,要求/home/tuser1及其內部文件的屬組和其他用戶均沒有任何訪問權限。 [root@CentOS7_2 home]# cp -r /etc/skel/ /home/tuser1 [root@CentOS7_2 home]# …

    Linux干貨 2016-11-16
欧美性久久久久