淺談python類屬性的訪問、設定和刪除方法

NO IMAGE

類屬性和物件屬性

我們把定義在類中的屬性稱為類屬性,該類的所有物件共享類屬性,類屬性具有繼承性,可以為類動態地新增類屬性。

物件在建立完成後還可以為它新增額外的屬性,我們把這部分屬性稱為物件屬性,物件屬性僅屬於該物件,不具有繼承性。

類屬性和物件屬性都會被包含在dir()中,而vars()是僅包含物件屬性。vars()跟__dict__是等同的。

類屬性和物件屬性可類比於Java中的static成員和非static成員,只不python中的類屬性和物件屬性都是可以動態新增(和刪除)的。


class A(object):
name='orisun'
def __init__(self):
self.age=10
class B(A):
city='bei jing'
def __init__(self):
self.tempurature=20
if __name__ == '__main__':
a=A()
print dir(A)
print dir(a)
print a.__dict__
print vars(a)
print 
b=B()
print dir(B)
print dir(b)
print b.__dict__
print vars(b)

輸出


['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name']
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'name']
{'age': 10}
{'age': 10}
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'city', 'name']
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'city', 'name', 'tempurature']
{'tempurature': 20}
{'tempurature': 20}

動態地為類新增類屬性後,該類的所有物件也都新增了該屬性(即使是動態新增類屬性之前建立的物件)。通過例項修改屬性,並不會影響其他例項的同名屬性和類上的同名屬性。


class A(object):
name='orisun'
def __init__(self):
self.age=10
if __name__ == '__main__':
a=A()
print dir(a)
A.city='BeiJing'  #動態新增類屬性,會反應到所有物件上
b=A()
A.name='zcy'    #動態修改類屬性,會反應到所有物件上
print dir(b)
print dir(a)
print a.name    
b.name='tom'    #通過例項修改屬性,並不會影響其他例項的同名屬性和類上的同名屬性
print a.name
print A.name
print b.name

輸出


['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'name']
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'city', 'name']
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'city', 'name']
zcy
zcy
zcy
tom

下文中討論的全部是類屬性,不涉及物件屬性。

對屬性的訪問、設定和刪除又分為2種情況:

1.通過物件(例項)訪問、設定和刪除屬性,即obj.attr、obj.attr=val、del obj.attr

2.通過類訪問、設定和刪除屬性,即Cls.attr、Cls.attr=val、del Cls.attr

本文將針對這2種情況分別討論。

Descriptor

一個Descriptor是指實現了__get__的類,實現__set__和__delete__是可選的。同時實現了__get__和__set__則稱為Data Descriptor,如果只實現了__get__則稱為Non-data Descriptor。


class Descriptor(object):
def __get__(self,instance,owner):
return 'Descriptor in ' owner.__name__
def __set__(self,obj,val):
pass
def __delete__(self,obj):
pass

先給一個Descriptor的示例,__get__、__set__、__delete__的作用後文再細講。

通過例項訪問屬性

__getattribute__、__getattr__、__get__和__dict__[attr]都是跟屬性訪問相關的方法,它們的優先順序:

1.當類中定義了__getattribute__方法時,則呼叫__getattribute__。

2.如果訪問的屬性存在,且

2.1  屬性是個Descriptor,是呼叫這個屬性的__get__

2.2 屬性不是Descriptor,則呼叫__dict__[attr]

3.如果類中沒有定義該屬性,則呼叫__getattr__

4.否則,丟擲異常AttributeError 

驗證4


class A(object):
pass
if __name__ == '__main__':
a=A()
print a.d

輸出:


AttributeError: 'A' object has no attribute 'd'

驗證3

 


class A(object):
def __getattr__(self,name):
return name " not found in " self.__class__.__name__ " object"
if __name__ == '__main__':
a=A()
print a.d

 

輸出:


d not found in A object

 驗證2.1

 


class Descriptor(object):
def __get__(self,instance,owner):
return 'Descriptor in ' owner.__name__
class A(object):
d=Descriptor()
def __getattr__(self,name):
return name " not found in " self.__class__.__name__ " object"
if __name__ == '__main__':
a=A()
print a.d

 

輸出:


Descriptor in A

__getattr__並沒有被呼叫。

驗證2.2


class A(object):
d=10
def __getattr__(self,name):
return name " not found in " self.__class__.__name__ " object"
if __name__ == '__main__':
a=A()
print a.d

輸出:


10

__getattr__並沒有被呼叫。

驗證1


class Descriptor(object):
def __get__(self,instance,owner):
return 'Descriptor in ' owner.__name__
class A(object):
d=Descriptor()
def __getattribute__(self,name):
return '__getattribute__ '
def __getattr__(self,name):
return name " not found in " self.__class__.__name__ " object"
if __name__ == '__main__':
a=A()

輸出:


__getattribute__ 

__get__和__getattr__並沒有被呼叫。

通過例項設定屬性

跟屬性設定相關的方法有3個:__setattr__、__set__和__dict__[attr]=val。它們的優先順序跟get正好反過來:

1.如果類中定義了__setattr__方法,則直接呼叫__setattr__

2.如果賦值的屬性是個Descriptor,且

2.1  該Descriptor中定義了__set__,則直接呼叫__set__

2.2  該Descriptor中沒有定義__set__,則呼叫__dict__[attr]=val

3.如果賦值的屬性不是Descriptor,則直接呼叫__dict__[attr]=val

4.如果該屬性不存在,則動態地新增該屬性,然後呼叫__dict__[attr]=val進行賦值

驗證4


class A(object):
pass
if __name__ == '__main__':
a=A()
a.d='hello'
print a.d

輸出:


hello

驗證3


class A(object):
d=10
if __name__ == '__main__':
a=A()
a.d=30
print a.d

輸出:


30

驗證2.2


class Descriptor(object):
def __get__(self,instance,owner):
return 'Descriptor in ' owner.__name__
class A(object):
d=Descriptor()
if __name__ == '__main__':
a=A()
a.d=30
print a.d

輸出:


30

驗證2.1


class Descriptor(object):
def __get__(self,instance,owner):
return 'Descriptor in ' owner.__name__
def __set__(self,instance,value):
pass
class A(object):
d=Descriptor()
if __name__ == '__main__':
a=A()
a.d=30
print a.d

輸出


Descriptor in A

因為程式碼“a.d=30”呼叫了__set__,而__set__又什麼都沒做,所以屬性d還是Descriptor物件(而非30),那麼在執行”print a.d”時自然就調到了__get__

驗證1


class Descriptor(object):
def __get__(self,instance,owner):
return 'Descriptor in ' owner.__name__
def __set__(self,instance,value):
print '__set__'
class A(object):
d=Descriptor()
def __setattr__(self,name,value):
print '__setattr__'
if __name__ == '__main__':
a=A()
a.d=30
print a.d

輸出


__setattr__
Descriptor in A

呼叫了__setattr__,而__set__並沒有被調到。

通過例項刪除屬性

呼叫del instance.attr進行屬性刪除時可能會調到__delattr__或__delete__,它們的優先順序跟set雷同。

1.如果類中定義了__delattr__方法,則直接呼叫__delattr__

2.如果賦值的屬性是個Descriptor,且該Descriptor中定義了__delete__,則直接呼叫__delete__

3.如果賦值的屬性是個Descriptor,且該Descriptor中沒有定義__delete__,則會報異常AttributeError:屬性是隻讀的

4.如果賦值的屬性不是Descriptor,也會報異常AttributeError:屬性是隻讀的

5.如果該屬性不存在,則報異常AttributeError

驗證5


class A(object):
pass
if __name__ == '__main__':
a=A()
del a.d

輸出


AttributeError: 'A' object has no attribute 'd'

驗證4


class A(object):
d=10
if __name__ == '__main__':
a=A()
del a.d

輸出


AttributeError: 'A' object attribute 'd' is read-only

驗證3


class Descriptor(object):
def __get__(self,instance,owner):
return 'Descriptor in ' owner.__name__
class A(object):
d=Descriptor()
if __name__ == '__main__':
a=A()
del a.d

輸出


AttributeError: 'A' object attribute 'd' is read-only

驗證2


class Descriptor(object):
def __get__(self,instance,owner):
return 'Descriptor in ' owner.__name__
def __delete__(self,instance):
print '__delete__'
class A(object):
d=Descriptor()
if __name__ == '__main__':
a=A()
del a.d

輸出


__delete__

驗證1


class Descriptor(object):
def __get__(self,instance,owner):
return 'Descriptor in ' owner.__name__
def __delete__(self,instance):
print '__delete__'
class A(object):
d=Descriptor()
def __delattr__(self,name):
print '__delattr__'
if __name__ == '__main__':
a=A()
del a.d

輸出


__delattr__

__delete__並沒有被呼叫。

__get__  __set__  __delete__引數說明


class Descriptor(object):
def __get__(self,obj,owner):
return '__get__',self,obj,owner
def __set__(self,obj,val):
print '__set__',self,obj,val
def __delete__(self,obj):
print '__delete__',self,obj
class A(object):
d=Descriptor()
if __name__ == '__main__':
a=A()
print a.d
a.d=3
del a.d

輸出


('__get__', <__main__.Descriptor object at 0x100481c10>, <__main__.A object at 0x1004a0fd0>, <class '__main__.A'>)
__set__ <__main__.Descriptor object at 0x100481c10> <__main__.A object at 0x1004a0fd0> 3
__delete__ <__main__.Descriptor object at 0x100481c10> <__main__.A object at 0x1004a0fd0>

可見,3個方法引數中的obj是Descriptor屬性所在的物件,而owner引數(__get__中的owner引數)是該物件所屬的類。

在上面的討論中我們是通過例項操作屬性,如果你作一下對應轉換:”例項轉換到類,類轉換到MetaClass”,那就是通過類操作屬性的規則。這種對應轉換也是容易理解的,應該類是用於建立物件的,而MetaClass是用於建立類的。


class MetaClass(object):
pass 
class A(object):
__metaclass__=MetaClass

通過類訪問屬性

通過A.attr訪問屬性的規則為:

1.如果MetaClass中有__getattribute__,則直接返回該__getattribute__的結果。

2.如果attr是個Descriptor,則直接返回Descriptor的__get__的結果。

3.如果attr是通過屬性,則直接返回attr的值

4.如果類中沒有attr,且MetaClass中定義了__getattr__,則呼叫MetaClass中的__getattr__

5.如果類中沒有attr,且MetaClass中沒有定義__getattr__,則丟擲異常AttributeError

通過類設定屬性

通過A.attr=val給屬性賦值時:

1.如果MetaClass中定義了__setattr__,則執行該__setattr__

2.如果該屬性是Descriptor,且定義了__set__,則執行Descriptor的__set__

3.如果是普通屬性或None-data Descriptor,則直接令attr=val

4.如果屬性不存在,則動態給類新增該屬性,然後進行賦值

通過類刪除屬性

通過del A.attr刪除屬性時:

1.如果MetaClass中定義了__delattr__,則執行該__delattr__

2.如果該屬性是Descriptor,且定義了__delete__,則執行Descriptor的__delete__

3.如果是普通屬性,或雖是Descriptor但是沒有定義__delete__,則直接從A.__dict__中刪除該屬性

4.如果屬性不存在,則丟擲異常AttributeError

以上這篇淺談python類屬性的訪問、設定和刪除方法就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援指令碼之家。

您可能感興趣的文章:

Python中如何獲取類屬性的列表從零學Python之引用和類屬性的初步理解Python類屬性與例項屬性用法分析Python實現子類呼叫父類的方法python類繼承與子類例項初始化用法分析python中子類繼承父類的__init__方法例項老生常談python之鴨子類和多型Python物件導向之繼承程式碼詳解Python物件導向程式設計之繼承與多型詳解Python物件導向特殊成員Python物件導向class類屬性及子類用法分析