python屬性管理(1):基礎


管理屬性的幾種方式

在python中訪問、設置、刪除對象屬性的時候,有以下幾種方式:

  1. 使用內置函數getattr()、setattr()和delattr()
  2. 自己編寫getter()setter()deleter()方法
  3. 重載__getattr__()__setattr__()__delattr__()運算符,這決定了x.y的訪問、賦值方式以及del x.y的方式
  4. 使用__getattribute__()方法
  5. 使用描述符協議
  6. 使用property協議,它是一種特殊的描述符

本文簡單介紹其中的前4種作為基礎,后面使用單獨的文章解釋后2種。

內置函數XXXattr()管理屬性

通過內置函數getattr()、setattr()、delattr()能簡單訪問、設置、刪除對象上的屬性。

先看看它們的幫助文檔:

getattr(...)
    getattr(object, name[, default]) -> value
    Get a named attribute from an object;
    getattr(x, 'y') is equivalent to x.y.

setattr(obj, name, value, /)
    Sets the named attribute on the given object to the specified value.
    setattr(x, 'y', v) is equivalent to ``x.y = v''

delattr(obj, name, /)
    Deletes the named attribute from the given object.
    delattr(x, 'y') is equivalent to ``del x.y''

用法很簡單,給定要操作的對象obj以及要操作的屬性名稱name。對於getattr()來說,如果要操作的屬性不存在默認會報錯,可以給定一個default參數表示屬性不存在時返回該給定屬性值。

例如,下面是一個簡單的Person類和對象p:

class Person():
    def __init__(self, name):
        self.name = name

p = Person("malongshuai")

使用getattr()獲取name屬性和不存在的age屬性:

print(getattr(p, "name"))
print(getattr(p, "age", 23))

上面訪問age屬性時,如果把第三個參數"23"去掉,將拋出異常。

AttributeError: 'Person' object has no attribute 'age'

使用setattr()和delattr()設置和刪除屬性:

setattr(p, "age", 25)
print(p.__dict__)

delattr(p, "age")
print(p.__dict__)

返回結果:

{'name': 'malongshuai', 'age': 25}
{'name': 'malongshuai'}

自己編寫accessor方法

一般面向對象的語言都是自己寫setter、getter、deleter方法來管理屬性的,通用又安全,但是管理起來並不那么方便。

這里僅介紹一下,它們更好的寫法參考:python設置對象屬性

例如,在Person類中加上name、age這兩個屬性的accessor方法:

class Person():
    def __init__(self, name):
        self.name = name

    def set_name(self,name): self.name = name
    def get_name(self): return self.name
    def del_name(self): del self.name

    def set_age(self,age): self.age = age
    def get_age(self): return self.age
    def del_age(self): del self.age

缺點是很明顯的,對於想要管理的每個屬性,都得去定義這些屬性。也就是說,accessor方法是針對單個屬性的。

運算符重載管理屬性

通常可以直接使用點號運算來訪問、設置屬性。例如:

p.name          # (1)訪問p對象的name屬性
p.name = "abc"  # (2)為p對象的name屬性賦值
del p.name      # (3)刪除p對象的name屬性

先說對象的賦值和刪除操作,也就是上面的(2)和(3)。這兩種操作可以直接被__setattr__()__delattr__()這兩個方法攔截,或者說只要重寫了這兩個方法,每當對屬性賦值、刪除時,都會調用對應的這兩個方法。

再說訪問屬性的操作(1),python提供了兩個對應的方法__getattr__()__getattribute__()。前者是在訪問不存在的屬性時被自動調用的,后者則是訪問屬性時被調用的,它無視屬性是否存在

這里提前說一個稍后要遇到的問題總結:對於適用於所有屬性操作的__setattr____delattr____getattribute__方法,要避免它們的無限遞歸。參考后面的示例即可知。

__getattr__()

__getattr__()是通過點號訪問不存在屬性時被調用的。它有兩個使用標准:要么返回屬性值,要么拋出異常。

例如:

class Person():
    def __init__(self, name):
        self.name = name

    def __getattr__(self, attrname):
        if attrname == "name":
            print("in getattr1")
            return self.name
        elif attrname == "age":
            print("in getattr2")
            return 25
        else:
            print("in getattr3")
            raise AttributeError(attrname)

p = Person("malongshuai")

上面的Person類帶有屬性name,所以訪問name屬性的時候不會調用__getattr__(),而訪問age或其它屬性時會調用該方法,只不過age屬性有自定義的返回值,其它屬性則報錯。

print(p.name)
print(p.age)
print(p.job)

以下是輸出結果:

malongshuai
in getattr2
25
in getattr3
Traceback (most recent call last):
  File "g:/pycode/b.py", line 21, in <module>
    print(p.job)
  File "g:/pycode/b.py", line 14, in __getattr__
    raise AttributeError(attrname)
AttributeError: job

__getattribute__()

__getattribute__()__getattr__()類似,不同的是它前者適用於所有屬性的訪問,而不管目標屬性是否存在。

需要注意的是,__getattribute__()適用於所有屬性訪問操作,所以要避免無限遞歸。例如,下面是錯誤的寫法:

def __getattribute__(self, attr):
    return self.attr

因為這個方法中的self.attr會繼續觸發__getattribute__的調用,從而出現無限遞歸問題。

解決辦法是通過父類來訪問,比如super()或object類。

super().__getattribute__(attr)
object.__getattribute__(self, attr)

__getattribute__()的優先級高於__getattr__(),前者存在的時候不會調用到后者,除非前者的代碼中調用了后者,或者前者拋出了異常。

例如:

class Person():
    def __init__(self, name):
        self.name = name

    def __getattribute__(self, attr):
        print("in getattribute")
        return object.__getattribute__(self, attr)
        # return super.__getattribute__(attr)

    def __getattr__(self, attrname):
        if attrname == "name":
            print("in getattr1")
            return self.name
        elif attrname == "age":
            print("in getattr2")
            return 25
        else:
            print("in getattr3")
            raise AttributeError(attrname)


p = Person("malongshuai")

print(p.name)
print(p.age)

返回結果:

in getattribute
malongshuai
in getattribute
in getattr2
25

上面輸出了name和age兩個屬性,但是輸出"p.age"的時候該屬性不存在,於是__getattribute__拋出異常,然后觸發__getattr__

需要注意的是,在解決無限遞歸問題上,后面的__setattr____delattr__還會有一種訪問__dict__的方式,這不適合於這里的__getattribute__,因為訪問這個字典也會觸發__getattribute__從而繼續導致無限遞歸。

__setattr__()

__setattr__()用來攔截對象屬性賦值操作。例如:

p.name = "long"

會轉換為調用p.__setattr__(self,name,"long")

唯一需要注意的是避免賦值時的無限遞歸問題。因為在__setattr__()中的賦值語句self.attr = value會繼續調用該方法,最終導致無限遞歸。

所以在__setattr__()方法中,必須使用__dict__來獲取屬性並進行賦值,或者訪問父類同名屬性。所以,有下面幾種方式避免無限遞歸調用。

self.__dict__[attr] = value
super().__setattr__(attr, value)
object.__setattr__(self, attr, value)

參考下面的示例。

class Person():
    def __init__(self, name):
        self.name = name

    def __setattr__(self, attr, value):
        print("in setattr")
        #self.__dict__[attr] = value
        #super().__setattr__(attr, value)
        object.__setattr__(self, attr, value)

p = Person("malongshuai")

p.age = 33      # 自動調用__setattr__()
print(p.age)

執行結果:

in setattr
in setattr
33

可能已經發現問題所在了,上面輸出了兩次in setter,原因是__init__()中的賦值操作也會觸發__setattr__()

__delattr__()

當調用del x.y的時候會自動觸發__delattr__()的調用。

同樣需要注意的是避免賦值時的無限遞歸問題。因為在__delattr__()中的del語句可能會繼續調用該方法,最終導致無限遞歸。所以在__delattr__()方法中,必須使用__dict__來獲取屬性並進行賦值,或者訪問父類同名屬性。所以,有下面幾種方式避免無限遞歸調用。

del self.__dict__[attr]
super().__delattr__(attr)
object.__delattr__(self, attr)

例如:

class Person():
    def __init__(self, name):
        self.name = name

    def __delattr__(self, attr):
        print("%s deleting" % (attr))

        #del self.__dict__[attr]
        #super().__delattr__(attr)
        object.__delattr__(self, attr)

        print("%s deleted" % (attr))


p = Person("malongshuai")
p.age = 33
del p.age


免責聲明!

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



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