1. 程式人生 > 其它 >Python面向物件小備忘

Python面向物件小備忘

最近學到面向物件了,感覺到Python這方面的語法也有點神奇,這裡專門歸納一下Python面向物件中我覺得比較重要的筆記。

  • 本文目前有的內容:例項屬性和類屬性的訪問,使用@property修飾器

例項屬性和類屬性的訪問

展開閱讀

Python裡面向物件程式設計的類屬性和例項屬性與普通情況下全域性變數和區域性變數還是有相似之處的:

  1. 我們可以通過例項名訪問例項屬性和類屬性,就像上面例子中的new_instance.test_varnew_instance.pub_var。就像區域性作用域能訪問區域性變數和全域性變數。

  2. 我們可以通過建立賦值讓例項物件有 與類屬性同名 的屬性,比如new_instance.pub_var = 'own property'

    就會在new_instance本身建立一個屬性,從而遮蔽 通過例項名對於類屬性的訪問。而在沒有global關鍵字的情況下,區域性變數在區域性作用域被建立賦值後也會遮蔽同名的全域性變數。

對於第2點可以試試通過例項名來刪除類屬性:

class Test:
    pub_var = 'Hello' # 類屬性

    def __init__(self):
        pass


new_instance = Test()
print(new_instance.pub_var)  # Hello
del new_instance.pub_var  # AttributeError: pub_var

很明顯通過例項名是無法刪除類屬性pub_var的,但如果我們給例項建立賦值一個同名屬性呢?

# 緊接上面例子
new_instance = Test()
print(new_instance.pub_var)  # 此時訪問了類屬性,輸出:Hello 
new_instance.pub_var = 'Hello World'
print(new_instance.pub_var)  # 此時訪問的是例項自身屬性,輸出:Hello World
del new_instance.pub_var  # 刪除了例項自身屬性,一切正常
print(new_instance.pub_var)  # 例項在自身找不到同名屬性了,就又指向了類屬性,輸出:Hello
del Test.pub_var # 可以通過類名刪除類屬性
print(new_instance.pub_var) # 在例項自身和類裡都找不到pub_var屬性了,返回no attribute異常

可以看出通過例項名可以刪除例項自身的屬性,當例項在自身上找不到屬性時,就會轉而尋找類屬性。類比區域性變數和全域性變數,區域性變數也是先在區域性作用域找,如果沒找到就去找同名的全域性變數。

通過類名,可以在很多地方訪問到類屬性,並可以進行修改(比如在例項的方法函式裡就可以直接通過類名訪問。

使用@property修飾器

展開閱讀
class Test:
    def __init__(self, val):
        self.__secret_value = val

    def my_value(self):
        return self.__secret_value


new_instance = Test(233)
print(new_instance.my_value())

上面例子中我們將類例項化為物件 new_instance (用類建立物件),該物件得到了my_value()方法,同時Python自動呼叫了__init__new_instance 綁定了屬性__value並進行賦值。

當我們要獲得值的時候就要呼叫例項物件new_instancemy_value()方法:

print(new_instance.my_value())

如果 使用了@property修飾器 呢?

class Test:
    def __init__(self, val):
        self.__secret1value = val

    @property
    def my_value(self):
        return self.__secret1value


new_instance = Test(233)
print(new_instance.my_value) # 末尾不用再加()了,因為這不是一個可呼叫的方法,而是一個屬性

@property的作用正如其名,將例項的方法轉換為了屬性,上面例子中原本的方法my_value()被修飾後只用訪問對應的屬性名my_value我們就能獲得同樣的返回值。

這個修飾器本質上其實仍然是對方法的呼叫,咱改一下上面的例子:

class Test:
    def __init__(self, val):
        self.__value = val

    @property
    def my_value(self):
        print('Here I am.') # 呼叫方法的時候輸出'Here I am.' 
        return self.__value
new_instance = Test(233) # 例項化的時候沒有任何輸出
print(new_instance.my_value) # 訪問這個屬性時實際上內部呼叫了my_value()的方法,因為輸出了 'Here I am.' 和 233 

再進一步想想,new_instance.my_value這個屬性取的其實就是原本my_value()方法的return返回值。

接著再鑽一下,原本my_value()這個方法 只是讀取了屬性__value並返回 ,並沒有進行修改。沒錯,這也意味著:

被@property修飾後產生的屬性是隻讀的

可以試試修改這個屬性:

new_instance.my_value = 450
# AttributeError: can't set attribute

很明顯,my_value現在對於new_instance而言是只讀屬性。由此,在使用者不知道原方法my_value()操作的私有屬性時能起一定的保護作用


  • 作為例項物件的一個屬性,其和方法有一定的區別,我們呼叫例項物件的方法時候是可以傳參的,但屬性不行,這意味著@property修飾的方法只能有self一個引數(否則訪問屬性的時候會報引數缺少的異常)。

  • 另外一個例項物件有其他屬性的,@property等修飾器修飾的方法也好,普通的例項方法也好,一定不要和已有的屬性重名。舉個例子:

    class Test:
        def __init__(self, val):
            self.__secret1value = val
            self.my_value = 'pre'
    
        @property
        def my_value(self):
            print('Here I am.')
            return self.__secret1value
    
    
    new_instance = Test(233)
    # self.my_value='pre' -> AttributeError: can't set attribute
    # 其實從這裡還能看出來,@property修飾先於例項初始化進行,導致丟擲的異常是無法修改屬性值
    

上面我們嘗試修改了@property修飾而成的屬性,但返回了can't set attribute。其實是因為咱沒有定義這個屬性的寫入(setter)方法.

需要修改這個@property屬性的話,我們就需要請出附贈的修飾器@已被修飾的方法名.setter了:

class Test:
    def __init__(self, val):
        self.__secret1value = val

    @property
    def my_value(self):
        return self.__secret1value

    @my_value.setter # [被@property修飾的方法名].setter
    def my_value(self, val2set): # 這裡的方法仍然是my_value
        self.__secret1value = val2set


new_instance = Test(233)
print(new_instance.my_value) # 233
new_instance.my_value = 450 # 此時這個屬性有修改(setter)的方法了,我們可以修改它
print(new_instance.my_value) # 450

@property修飾的方法不同,@已被修飾的方法名.setter修飾的方法除了self還可以接受第二個引數,接收的是修改的值。在上面例子中我將這個形參命名為了val2set

有了讀和寫,還差什麼呢——

和setter類似,@property修飾器還贈有@已被修飾的方法名.deleter修飾器,其修飾的方法和@property修飾的一樣都只接受一個引數self

class Test:
    def __init__(self, val):
        self.__secret1value = val

    @property
    def my_value(self):
        return self.__secret1value

    @my_value.deleter # [被@property修飾的方法名].deleter
    def my_value(self):  # 注意這裡只接受一個self引數
        del self.__secret1value


new_instance = Test(233)
print(new_instance.my_value)  # 233
try:
    new_instance.my_value = 450
except:
    print('Set failed.')  # Set failed.
del new_instance.my_value
print(new_instance.my_value)
# AttributeError: 'Test' object has no attribute '_Test__secret1value'

這個例子中咱沒有定義my_value屬性的setter方法,所以其無法被修改。但因為定義了deleter方法,在用del對屬性進行移除的時候會通過deleter呼叫原方法,原方法中用del去刪掉例項物件自己的私有屬性,達成刪除的目的。


總結一下修飾器@property相關的著重點:

  1. @property讓例項方法作為屬性被訪問。

  2. 這一類修飾器能在一定程度上保護例項的私有屬性不被隨意修改(之所以是說一定程度上,是因為一旦使用者知道了私有屬性名就可以用_類名__私有屬性名進行訪問,Python,很神奇吧 ( ̄ε(# ̄)☆╰╮o( ̄皿 ̄///)) 。

  3. 例項的方法名不要和自身其他方法或屬性重名。

  4. @property@已被修飾的方法名.deleter修飾的方法只能接受self一個引數;而@已被修飾的方法名.setter修飾的方法除了self外可以接受第二個引數作為被修改的值。


除了@property這種修飾器寫法外,Python還提供了內建方法 property(getter,setter,deleter,doc) 來達成相同的效果:

class Test:
    pub_var = 'Hello'

    def __init__(self, val):
        self.__secret1value = val
        self.test_val = 'World'

    def __getter(self):
        return self.__secret1value

    def __deleter(self):
        del self.__secret1value

    my_value = property(__getter, None, __deleter)


new_instance = Test(233)
print(new_instance.test_var) # World (通過例項名訪問例項屬性)
print(Test.pub_var)  # Hello (嘗試通過類名訪問類屬性)
print(new_instance.pub_var)  # Hello (嘗試通過例項訪問類屬性)
print(Test.my_value) # <property object at 0x0000025990BC5770> (這個其實也是類屬性,通過類名能訪問到)
print(new_instance.my_value)  # 233 (通過例項名訪問類屬性,間接呼叫了__getter,繫結上了self

property(getter,setter,deleter,doc)接受的四個引數分別為讀方法寫方法刪方法描述資訊,這四個引數都是可以留空的,當getter也留空時訪問這個屬性會提示unreadable attribute

通過上面的例子可以看出,property方法返回的是類屬性,而例項物件是可以訪問到類屬性的,所以當我們訪問new_instance.my_value的時候就是在繫結例項的基礎上訪問getter方法,其他的寫、刪操作原理一致。


再回去看例項屬性和類屬性的訪問,加上這個內建方法property(),於是就有了奇妙的騷操作:

class Test:
    def __init__(self, val):
        Test.test_var = property(lambda self: val) # 閉包寫法


new_instance = Test(233)
print(new_instance.test_var) # 233
  1. 這個操作中首先利用了一個匿名函式充當getter方法,傳入property第一個引數,然後property會返回一個類屬性。

  2. 因為在例項方法裡我們也能訪問到類名,於是我們將這個property類屬性賦值給Test.test_vartest_var便是一個名副其實的類屬性了。

  3. 通過例項名new_instance能訪問到類屬性test_var

  4. 之前的這個例子可以看出,當我們通過類名訪問property屬性時只會返回一個property object,但是通過已建立的例項物件來訪問就能間接呼叫getter方法。

  5. 在上面過程中,始終沒有new_instance自身屬性出現,取而代之我們利用閉包機制保護了建立例項時傳入的值,我們完全無法通過例項名修改或者刪除test_var這個屬性,真正將其保護起來了。

當然,別讓使用者知道了類名,不然一句Test.test_var = xxx直接破防(,,#゚Д゚)。

To be updated......