描述器

描述器

|[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

相關推薦

  • N27_網絡班第八周作業

    1、寫一個腳本,使用ping命令探測172.16.250.1-172.16.250.254之間所有主機的在線狀態; 在線的主機使用綠色顯示; 不在線的主機用紅色表示; #!bin/bash # for i in `seq 254`;do if ping -c 1 192.168.68.$i &> /dev/null;then echo -e “…

    2017-09-28
  • 機器學習排序

     從使用的數據類型,以及相關的機器學習技術的觀點來看,互聯網搜索經歷了三代的發展歷程。        第一代技術,將互聯網網頁看作文本,主要采用傳統信息檢索的方法。        第二代技術,利用互聯網的超文本結構,有效…

    Linux干貨 2015-12-15
  • M20 – 1- 第三周博客(2):Linux用戶、組

    一、Linux用戶組詳解 Linux系統中的每個用戶都有一個用戶組,系統能對一個用戶組中的所有用戶進行集中管理。不同Linux系統對用戶組的規定有所不同,如Linux下的用戶屬于和他同名的用戶組,這個用戶組在創建用戶時同時創建。用戶組的管理涉及用戶組的添加、刪除和修改。組的增加、刪除和修改實際上就對/etc/group文件的更新。 用戶組(group)就是具…

    Linux干貨 2016-08-05
  • LVM 邏輯卷管理器

    1、什么是LVM:PV、PE、VG、LV的意義    LVM:Logical Volume Manager(邏輯卷管理器),可以將多個物理分區整合成看起來像一個磁盤一樣,并可隨意增加或減少邏輯卷大小 dm:device mapper,將一個或多個底層塊設備組織成一個邏輯設備的模塊; /dev/mapper/VG_NAME-LV_NAME …

    Linux干貨 2016-09-19
  • 用戶和組管理類命令

    用戶和組管理類命令 useradd useradd命令用于Linux中創建的新的系統用戶 語法 useradd(選項)(參數) 選項 -c<備注>:加上備注文字。備注文字會保存在passwd的備注欄位中; -d<登入目錄>:指定用戶登入時的啟始目錄; -D:變更預設值; -e<有效期限>:指定帳號的有效期限; -f<…

    Linux干貨 2018-03-18
  • 馬哥教育網絡班22期+第4周課程練習

    1、復制/etc/skel目錄為/home/tuser1,要求/home/tuser1及其內部文件的屬組和其它用戶均沒有任何訪問權限。 [root@localhost ~]# cp -r /etc/skel /home/tuser1 [root@localhost ~]# chmod&nb…

    Linux干貨 2016-09-05
欧美性久久久久