python類屬性的訪問、設置和刪除


類屬性和對象屬性

我們把定義在類中的屬性稱為類屬性,該類的所有對象共享類屬性,類屬性具有繼承性,可以為類動態地添加類屬性。

對象在創建完成后還可以為它添加額外的屬性,我們把這部分屬性稱為對象屬性,對象屬性僅屬於該對象,不具有繼承性。

類屬性和對象屬性都會被包含在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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM