Python 進階_OOP 面向對象編程_實例屬性和方法

目錄

構造器和解構器

構造器 __init__()

類函數 __init__() 是 Python 類中預約義的方法,須要被重載纔會生效。以雙下劃線 「__」 開頭和結尾, 在 Python 中使用這種命名方式的方法會被理解爲是一種特殊方法, Python 的特殊方法功能很是豐富, 種類也不少, 在聲明變量名的時候要注意不要和特殊方法重名. css

一般,構造器用於在 實例化對象被建立後,返回這個實例以前 的這段時間裏,執行一些特定的任務或設置。例如:初始化實例對象屬性(以 self 關鍵字調用的屬性)。它在實例化一個新的對象時被自動調用,因此除了初始化實例屬性以外,還常被用於運行一些初步的診斷代碼。其調用的具體步驟:
1. 建立類的實例化對象
2. Python 解析器檢查類是否實現了構造器
3. 如有,則執行構造器的實現,且要求建立對象的時候傳入對應的實參。實例對象會傳遞給第一個形參 self 。
4. 若沒有,則直接返回實例對象python

通常建議在構造器中設定須要初始化的實例屬性,並且構造器應該返回 None,即沒有 return 語句。程序員

真·構造器 __new__()

__init__() 相比 __new__() 纔是真正的構造器,實際上,在 Python 解析器中是先調用了 __new__() 生成一個實例,再將該實例對象傳入 __init__() 實現初始化操做。但 __new__() 不多須要咱們去重載,通常只有在派生了不可變類型的子類後須要重載,EG. 派生 String/Int/Tuple 等markdown

爲何說 __new__() 是真·構造器呢?
由於這個特殊的類方法是真的返回了一個類的實例對象,而不像 __init__() 是傳入了一個實例化對象。ssh

EXAMPLE:不可表類型的派生函數

class RoundFloat(float):
    def __new__(cls, val):
        return float.__new__(cls, round(val, 2)) #由於 __new__ 是一個類方法,因此咱們要顯式的傳遞一個類對象

類 RoundFloat 是類 float 的子類,咱們經過重載父類的 __new__() 構造器來定製一個新的不可變類型(Python 2.2以後將類和類型統一了,因此能夠繼承 Python 的內置數據類型)。當實例化 RoundFloat 的對象時,其實是實例化了Python 內置數據類型 Float 的對象,並對這個對象作了一些定製化的操做(round(val, 2))。
NOTE:即使咱們也能夠經過重載 __init__() 來實現這個結果,但這裏卻不能這麼作。由於若是 __new__() 沒有被重載的話,仍會默認調用父類 Float 的構造器,建立 Float 類型的對象,而不是建立如今的 RoundFloat 類型對象。這也是二者的本質區別。ui

解構器 __del__()

解構器 __del__() 會在實例對象被垃圾回收以前被 Python 解析器調用一次(只會調用一次,以後就回收實例對象)。解構器是實例對象釋放資源前提供特殊處理功能的方法。通常咱們不多會用到,但解構器是檢查對象引用狀態的好方法,加入對象仍然存在引用時,解構器是不會執行的。spa

注意不要隨便重載解構器。 .net

實例方法

實例方法的基本特徵就是要傳入一個實例對象做爲實參。
在 Python 看來,實例方法嚴格來講屬於類屬性的一種,實例方法只能經過實例化對象來調用,沒法經過類來直接調用。3d

In [59]: class AClass(object):
    ...:     LOG = 'Define a class'
    ...:     def my_no_actioon_method(self):
    ...:         pass
    ...:

In [60]: dir(AClass)
Out[60]:
['LOG',
 '__class__',
 '__delattr__',
 '__dict__',
 '__doc__',
 '__format__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__module__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'my_no_actioon_method']     # 經過 dir() 能夠找到實例方法存在於類的屬性列表中。

In [62]: AClass.my_no_actioon_method()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-62-33bc36ae78f7> in <module>()
----> 1 AClass.my_no_actioon_method()

TypeError: unbound method my_no_actioon_method() must be called with AClass instance as first argument (got nothing instead)

若是類直接調用實例方法的話,會觸發 TypeError,由於實例方法須要綁定一個實例化對象,這就是 self 存在的意義。全部的含有 self 關鍵字的屬性和方法都只能經過類的實例化對象來調用。因此,通常而言類中定義的實例方法都須要含有 self 形參,固然也能夠經過別的方式來解除這種約束,這個咱們後面會說到。

In [63]: a_object = AClass()

In [64]: a_object.my_no_actioon_method() #類方法是經過局點標識符來與它的實例化對象綁定的

Python 中的 「抽象方法」

Python 並不支持 Java 中的抽象方法,但能夠經過在父類中 ``raise NotImplememtedError 來實現一樣的效果。抽象方法的意義在於能夠強制的規範在子類中必需定義與父類中方法同名的子類成員方法。

In [74]: class AClass(object):
    ...:     LOG = 'Define a class'
    ...:     def my_method(self):
    ...:         raise NotImplementedError()
    ...:
    ...:

In [75]: class BClass(AClass):
    ...:     pass
    ...:

In [76]: b_object = BClass()

In [77]: dir(b_object)
Out[77]:
['LOG',
 '__class__',
 '__delattr__',
 '__dict__',
 '__doc__',
 '__format__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__module__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'my_method']

In [78]: b_object.my_method()
---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
<ipython-input-78-49e04830d861> in <module>()
----> 1 b_object.my_method()

<ipython-input-74-d6213dfecb1b> in my_method(self)
      2     LOG = 'Define a class'
      3     def my_method(self):
----> 4         raise NotImplementedError()
      5
      6

NotImplementedError:

因此經過這種方式,程序員們只能乖乖在子類的重載這個同名方法了,這對程序的規範作了很好的約束。

In [79]: class BClass(AClass):
    ...:     def my_method(self):
    ...:         print "overload the function."
    ...:
    ...:

In [80]: b_object = BClass()

In [81]: b_object.my_method()
overload the function.

實例屬性

在類定義中,經過 self 關鍵字來調用的屬性,即屬於實例對象的屬性,類對象是不能調用的。類屬性的建立其實是經過調用實例方法 __setattr__ 來完成的。x.__setattr__('name', value) <==> x.name = value,除此以外還有內建函數 setattr(),setattr(object, name, value) <==> object.name = value

Python 能夠在任意的時間內建立一個命名空間,因此咱們能夠在建立了實例對象以後定義實例屬性,也能夠在定義類的時候建立實例屬性。後者建立實例屬性最重要的方式就是構造器 __init__()

In [98]: class AClass(object):
    ...:     def __init__(self, name, age):
    ...:         self.name = name
    ...:         self.age = age
    ...:

In [99]: a_object = AClass('JMILKFAN', 24)

In [101]: a_object.name
Out[101]: 'JMILKFAN'

In [102]: a_object.age
Out[102]: 24

In [103]: a_object.sex = 'man'

In [104]:  a_object.sex
Out[104]: 'man'

固然,咱們除了能夠在構造器中建立實例屬性以外,還能夠在類體的任意地方建立,當咱們並不推薦這麼作。

查看實例屬性

查看實例屬性和查看類屬性是同樣的,能夠經過 dir()instance.__dict__ 來查看。
EXAMPLE

In [105]: dir(a_object)
Out[105]:
['__class__',
 '__delattr__',
 '__dict__',
 '__doc__',
 '__format__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__module__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'age',
 'name',
 'sex']

In [106]: a_object.__dict__
Out[106]: {'age': 24, 'name': 'JMILKFAN', 'sex': 'man'}

再與查看類屬性作一個對比:

In [108]: dir(AClass)
Out[108]:
['__class__',
 '__delattr__',
 '__dict__',
 '__doc__',
 '__format__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__module__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__']

In [109]: AClass.__dict__
Out[109]:
dict_proxy({'__dict__': <attribute '__dict__' of 'AClass' objects>,
            '__doc__': None,
            '__init__': <function __main__.__init__>,
            '__module__': '__main__',
            '__weakref__': <attribute '__weakref__' of 'AClass' objects>})

NOTE: 對於類或實力對象來講 __dict__ 都是能夠手動修改的,可是並不建議咱們手動去修改這個屬性字典。

咱們還可以經過內置方法 getattr() 或實例方法 __getattribute__() 來查看

In [119]: getattr(a_object, 'name')
Out[119]: 'JMILKFAN'

In [122]: a_object.__getattribute__('name')
Out[122]: 'JMILKFAN'

實例屬性和類屬性的區別

類屬性與實例無關,類屬性的值不會由於實例化對象而改變,除非在實例對象中 顯式 的改變了 可變類屬性 的值。類屬型的值就像是靜態成員那樣被使用,咱們通常採用 class.attr 的方式來調用類屬性 attr,除此咱們也可以使用實例對象來調用類屬性 instance.attr,但前提是實例對象中沒有同名的實例屬性,不然 instance.attr 中的 attr 就是一個實例屬性,返回實例屬性的值。相反,實例屬性只能由類實例對象來調用。

類屬性相似於一個靜態屬性,在定義類屬性的時候會在內存開闢靜態空間,用於存放。並且這個靜態空間是類的全部實例對象均可以訪問的,因此類屬性是被全部的由該類實例化的對象共享的。

In [150]: class AClass(object):
     ...:     version = 1.0
     ...:

In [151]: a_object = AClass()

In [152]: a_object.version
Out[152]: 1.0

In [153]: AClass.version += 1

In [154]: a_object.version
Out[154]: 2.0

類屬性的改變會被實例對象感知。

訪問不可變類屬性

對於不可變類屬性(int/str/tuple)而言,實例對象是沒法改變類屬性的值得。但若是類屬性是可變數據類型,那麼實例對象就能夠顯式的修改其的值。

In [124]: class AClass(object):
     ...:     version = 1.0
     ...:

In [125]: a_object = AClass()

In [126]: AClass.version
Out[126]: 1.0

In [127]: a_object.version
Out[127]: 1.0

In [128]: AClass.version = 1.1

In [129]: a_object.version
Out[129]: 1.1

In [130]: AClass.version
Out[130]: 1.1

In [131]: a_object.version
Out[131]: 1.1

In [132]: b_object = AClass()

In [133]: b_object.version
Out[133]: 1.1

In [134]: b_object.version = 1.2   # 這裏並不沒有修改類屬性,而是建立了一個新的實例屬性

In [135]: AClass.version
Out[135]: 1.1

In [136]: a_object.version
Out[136]: 1.1

從上面的例子能夠看出,實例對象能夠引用類屬性(前提是實例對象沒有同名的實例屬性),卻不能改變類屬性,而是建立了一個新的實例屬性並覆蓋了同名的類屬性。

訪問可變類屬性

In [143]: class AClass(object):
     ...:     aDict = {'name': 'jmilkfan'}
     ...:

In [144]: a_object = AClass()

In [146]: AClass.aDict
Out[146]: {'name': 'jmilkfan'}

In [147]: a_object.aDict
Out[147]: {'name': 'jmilkfan'}

In [148]: a_object.aDict['name'] = 'fanguiju'

In [149]: AClass.aDict
Out[149]: {'name': 'fanguiju'}

實例對象能夠修改類屬性的本質是,實例對象在沒有建立一個新的實例屬性的基礎上進行了修改。

注意:通常認爲經過實例對象來修改類屬性是危險的,會有不少潛在的風險。因此建議使用類名來修改類屬性,而不是實例對象名。

相關文章
相關標籤/搜索