深入理解python物件導向-類特殊成員

johnychen發表於2021-09-09

類成員的修飾符

類的所有成員在上一篇已經做了詳細的介紹,對於每一個類的成員都有兩種形式:公有成員私有成員。成員定義是以雙下劃線開頭,就是私有成員。除了一些特殊成員除外,例如:__init__、__call__、__dict__、__del__等,剩下的都是公有成員。

class  Base:
    def  __init__(self):
        self.name = '公有欄位'
        self.__foo = "私有欄位"

私有成員和公有成員的訪問級別不同:

圖片描述

普通欄位

  • 公有普通欄位:物件可以訪問;類內部可以訪問;派生類中可以訪問
  • 私有普通欄位:僅類內部可以訪問;

私有欄位其實不是不能訪問,只是Python直譯器對私有成員命名做了更改,物件._類名__私有欄位名,例如:obj._Base__foo不建議強制訪問私有成員。

普通公有欄位例子

class Base:
    def __init__(self):
        self.foo = '公有欄位'

    def func(self):
        print(self.foo) # 類內部訪問

class Device(Base):
    def show(self):
        print(self.foo) # 派生類中訪問

obj = Base()
print(obj.foo)      # 物件訪問
obj.func()          # 類內部訪問
obj_son = Device()
obj_son.show()      # 派生類訪問

普通私有欄位例子

class Base:
    def __init__(self):
        self.__foo = '私有欄位'

    def func(self):
        print(self.__foo) # 類內部訪問

class Device(Base):
    def show(self):
        print(self.__foo) # 派生類中訪問

obj = Base()
print(obj.__foo)    # 物件訪問   報錯:AttributeError: 'Base' object has no attribute '__foo'
obj.func()          # 類內部訪問
obj_son = Device()
obj_son.show()      # 派生類訪問  報錯:AttributeError: 'Device' object has no attribute '_Device__foo'

方法、屬性的訪問都是相似的,即:私有成員只能在類內部使用

靜態欄位

  • 公有靜態欄位:類可以訪問;類內部可以訪問;派生類中可以訪問
  • 私有靜態欄位:僅類內部可以訪問;

靜態公有欄位例子

class Base:
    name = "公有靜態欄位"
    def func(self):
        print(Base.name)

class Device(Base):
    def show(self):
        print(Base.name)

Base.name # 類訪問
obj = Base()
obj.func() # 類內部可以訪問
obj_son = Device()
obj_son.show() # 派生類中可以訪問

靜態私有欄位例子

class Base:
    __name = "公有靜態欄位"
    def func(self):
        print(Base.__name)

class Device(Base):
    def show(self):
        print(Base.__name)

Base.__name # 類訪問  報錯:AttributeError: type object 'Base' has no attribute '__name'
obj = Base()
obj.func() # 類內部訪問
obj_son = Device()
obj_son.show() # 派生類訪問  報錯:AttributeError: type object 'Base' has no attribute '_Device__name'

透過上面的例子,你應該發現了一個問題,在普通公有欄位中,子類可以透過self.foo訪問父類定義的變數;在靜態公有欄位中,使用的是Base.name訪問。那為什麼不能使用self訪問呢?上一篇我們其實講過的,靜態欄位屬於類所有,在類中只儲存一份,所以它與繼承無關,不管經過多少重繼承,靜態欄位只有一份,只能透過類自身來訪問。而普通欄位是屬於物件的,所以繼承以後,每一個繼承類的物件都會儲存一份。

類的特殊成員

上面我們講了類成員以及成員修飾符,知道了類中有欄位、方法和屬性,並且有公有和私有兩種訪問限制。但是還是存在著一些具有特殊含義的成員,詳情如下:

  1. __doc__
    表示類的描述資訊

庫函式:range

class range(object):
    """
    range(stop) -> range object
    range(start, stop[, step]) -> range object
    
    Return an object that produces a sequence of integers from start (inclusive)
    to stop (exclusive) by step.  range(i, j) produces i, i+1, i+2, ..., j-1.
    start defaults to 0, and stop is omitted!  range(4) produces 0, 1, 2, 3.
    These are exactly the valid indices for a list of 4 elements.
    When step is given, it specifies the increment (or decrement).
    """
    def count(self, value): # real signature unknown; restored from __doc__
        """ rangeobject.count(value) -> integer -- return number of occurrences of value """
        return 0

print(range.__doc__)

#輸出結果:
range(stop) -> range object
range(start, stop[, step]) -> range object

Return an object that produces a sequence of integers from start (inclusive)
to stop (exclusive) by step.  range(i, j) produces i, i+1, i+2, ..., j-1.
start defaults to 0, and stop is omitted!  range(4) produces 0, 1, 2, 3.
These are exactly the valid indices for a list of 4 elements.
When step is given, it specifies the increment (or decrement).

當我們在類上面加上一些說明,透過此方法可以進行檢視,同樣也可以檢視庫函式的說明

  1. __module__ 和 class
      __module__ 表示當前操作的物件在那個模組
      __class__ 表示當前操作的物件的類是什麼
class Foo:
    def func(self):
        pass

f = Foo()
print(f.__module__)    #輸出:__main__
print(f.__class__)       #輸出:<class '__main__.Foo'>

from multiprocessing import Process
p = Process()
print(p.__module__)    #輸出:multiprocessing.context
print(p.__class__)       #輸出:<class 'multiprocessing.context.Process'>
  1. __init__
      構造方法,透過類建立物件時,自動觸發執行。
class  Foo:
     def  __init__(self, name):
        self.name = name
        self.age = 18

obj = Foo('wupeiqi') # 自動執行類中的 __init__ 方法

注意:Python這裡的構造方法與C++不一樣

class Foo:
    instance = None
    def __init__(self):
        print("__init__")

    @classmethod
    def __new__(cls, *args, **kwargs):
        if not cls.instance:
            cls.instance = object.__new__(cls)
            print("__new__")
        return cls.instance

f1 = Foo()
f2 = Foo()
print(id(f1), id(f2))

#輸出:
__new__
__init__
__init__
4343767560 4343767560

可以看到,f1和f2是同一個物件,佔據同一塊記憶體,也就是說在記憶體中只建立了一個物件,但是建構函式呼叫了兩次。所以這裡的建立物件時,自動觸發並不是特別準確,使用的時候要多注意。

  1. __del__
      析構方法,當物件在記憶體中被釋放時,自動觸發執行。

此方法一般無須定義,因為Python是一門高階語言,程式設計師在使用時不需要關心記憶體的分配和釋放,因為都是交給Python直譯器來執行,所以解構函式的呼叫是由直譯器在進行垃圾回收時自動觸發執行的。

    def  __del__(self):
        print("del")

還是上面那個例子,加上__del__函式的定義
輸出:

__new__
__init__: 
__init__: 
4424543312 4424543312
del

可以看到del也是呼叫了一次,再次證實了,物件只建立了一次

  1. __call__
    物件後面加括號,觸發執行,相當於函式呼叫。

構造方法的執行是由建立物件觸發的,即:物件 = 類名() ;而對於 call 方法的執行是由物件後加括號觸發的,即:物件() 或者 類()()

class  Foo:
    def  __init__(self):
        pass

   def  __call__(self, *args, **kwargs):
        print  '__call__'

obj = Foo()  # 執行 __init__
obj()        # 執行 __call__
  1. __dict__
    類或物件中的所有成員

上文中我們知道:類的普通欄位屬於物件;類中的靜態欄位和方法等屬於類,即:

class Foo:
    instance = None
    def __init__(self, name):
        print("__init__: ", name)
        self.name = name

    def func(self):
        pass

f1 = Foo("name1")

# 獲取物件的成員,即:普通欄位
print(f1.__dict__)  # 輸出:{'name': 'name1', 'age': 123}

# 獲取類的成員,即:靜態欄位、方法、
print(Foo.__dict__) # 輸出:{'__module__': '__main__', 'instance': None, '__init__': <function Foo.__init__ at 0x10df509d8>, 'func': <function Foo.func at 0x1151b79d8>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}
  1. __str__
    如果一個類中定義了__str__方法,那麼在列印物件或者str轉換時,預設輸出該方法的返回值。
class  Foo:
    def  __str__(self):
        return  'foo_name'

obj = Foo()
print(obj)
# 輸出:foo_name

print(str(obj)) 
# 輸出:foo_name
  1. __getitem__、__setitem__、__delitem__
    用於索引操作,如字典、列表。以上分別表示獲取、設定、刪除資料
    字典
class Foo:
    def __getitem__(self, item):
        print("__getitem__: ", item)

    def __setitem__(self, key, value):
        print("__setitem__: ", key, value)

    def __delitem__(self, key):
        print("__delitem__: ", key)

obj = Foo()
result = obj["k1"]    # 自動觸發__getitem__
obj["k2"] = "name"    # 自動觸發__setitem__
del obj["k1"]         # 自動觸發__delitem__

列表

class Foo:
    def __getitem__(self, item):
        print("__getitem__.start: ", item.start)
        print("__getitem__.stop: ", item.stop)
        print("__getitem__.step: ", item.step)

    def __setitem__(self, key, value):
        print("__setitem__.index: ", key)
        print("__setitem__.value: ", value)
        return value

    def __delitem__(self, key):
        print("__delitem__: ", key)

f = Foo()
f[1:5:2]    #自動觸發__getitem__
f[0] = 1   #自動觸發__setitem__
del f[0]    #自動觸發__delitem__

#輸出
__getitem__.start:  1
__getitem__.stop:  5
__getitem__.step:  2
__setitem__.index:  0
__setitem__.value:  1
__delitem__:  0
  1. __iter__
    用於迭代器,之所以列表、字典、元組可以進行for迴圈,是因為型別內部定義了 iter
class Foo:
    def __init__(self, sq):
        self.sq = sq

    def __iter__(self):
        return iter(self.sq)

obj = Foo([11,22,33,44])
for i in obj:
    print(i)

for迴圈迭代的其實是 iter([11,22,33,44]) ,所以相當於如下:

obj = iter([11,22,33,44])
for i in obj:
    print(i)

For迴圈迭代器輸出

obj = iter([11,22,33,44])
while True:
    try:
        val = next(obj)
        print(val)
    except StopIteration as e:
        break

還有__metaclass__也是非常重要的一個,這個牽扯到反射機制,我們放在下一篇進行詳細說明,今天的文章就到這裡了,你有沒有Get到新技能呢?歡迎留言一起探討

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4301/viewspace-2822993/,如需轉載,請註明出處,否則將追究法律責任。

相關文章