溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

python中類、基類、多態(tài)、取消基類的原理是什么

發(fā)布時間:2021-10-14 15:16:19 來源:億速云 閱讀:97 作者:柒染 欄目:編程語言

這期內(nèi)容當(dāng)中小編將會給大家?guī)碛嘘P(guān)python中類、基類、多態(tài)、取消基類的原理是什么,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

很早以前見到過“python的類、基類、多態(tài)、取消基類的代碼",現(xiàn)在很想找到那個例子,可惜,又找不到了!算了,把已經(jīng)知道的收集一下,以后看到了再補充!
__bases__是一個元組(可能是None或獨元), 包括其基類, 以基類列表中它們的排列次序出現(xiàn)

def classic_lookup(cls, name):
      "Look up name in cls and its base classes."
      if cls.__dict__.has_key(name):
          return cls.__dict__[name]
      for base in cls.__bases__:
          try:
              return classic_lookup(base, name)
          except AttributeError:
              pass
      raise AttributeError, name

-----------

定義

Python 的 Class 比較特別,和我們習(xí)慣的靜態(tài)語言類型定義有很大區(qū)別。

1. 使用一個名為 __init__ 的方法來完成初始化。
2. 使用一個名為 __del__ 的方法來完成類似析購操作。
3. 所有的實例方法都擁有一個 self 參數(shù)來傳遞當(dāng)前實例,類似于 this。
4. 可以使用 __class__ 來訪問類型成員。

>>> class Class1:
    def __init__(self):
      print "initialize..."
    def test(self):
      print id(self)
    
>>> a = Class1()
initialize...
>>> a.test()
13860176
>>> id(a)
13860176

Class 有一些特殊的屬性,便于我們獲得一些額外的信息。
>>> class Class1(object):
    """Class1 Doc."""
    def __init__(self):
      self.i = 1234

    
>>> Class1.__doc__ # 類型幫助信息
'Class1 Doc.'
>>> Class1.__name__ # 類型名稱
'Class1'
>>> Class1.__module__ # 類型所在模塊
'__main__'
>>> Class1.__bases__ # 類型所繼承的基類
(<type 'object'>,)
>>> Class1.__dict__ # 類型字典,存儲所有類型成員信息。
<dictproxy object at 0x00D3AD70>
>>> Class1().__class__ # 類型
<class '__main__.Class1'>
>>> Class1().__module__ # 實例類型所在模塊
'__main__'
>>> Class1().__dict__ # 對象字典,存儲所有實例成員信息。
{'i': 1234}

繼承

Python 支持多繼承,但有幾點需要注意:

1. 基類 __init__ / __del__ 需顯示調(diào)用。
2. 繼承方法的調(diào)用和基類聲明順序有關(guān)。

>>> class Base1:
    def __init__(self):
      print "Base1"

    def test(self):
      print "Base1 test..."

>>> class Base2:
    def __init__(self):
      print "Base2"

    def test(self):
      print "Base2 test..."

>>> class Class1(Base2, Base1):
    def __init__(self):
      Base1.__init__(self)
      Base2.__init__(self)
      print "Class1"

>>> a = Class1()
Base1
Base2
Class1
>>> a.test()
Base2 test...

成員

Python Class 同樣包含類型和實例兩種成員。

>>> class Class1:
    i = 123 # Class Field
    def __init__(self):
      self.i = 12345 # Instance Field
    
>>> print Class1.i
123
>>> print Class1().i
12345

-----------------------

有幾個很 "特殊" 的 "規(guī)則" 需要注意。

(1) 我們可以通過實例引用訪問類型成員。因此下面的例子中 self.i 實際指向 Class1.i,直到我們?yōu)閷嵗略隽艘粋€成員 i。
>>> class Class1:
    i = 123
    def __init__(self):
      print self.i 
      print hex(id(self.i))

    
>>> hex(id(Class1.i)) # 顯示 Class1.i
'0xab57a0'
>>> a = Class1() # 創(chuàng)建 Class1 實例,我們會發(fā)現(xiàn) self.i 實際指向 Class1.i。
123
0xab57a0
>>> Class1.__dict__ # 顯示 Class1 成員
{'i': 123, '__module__': '__main__', '__doc__': None, '__init__': <function __init__ at 0x00D39470>}
>>> a.__dict__ # 顯示實例成員
{}
>>> a.i = 123456789 # 為實例新增一個成員 i
>>> hex(id(a.i)) # 顯示新增實例成員地址
'0xbbb674'
>>> a.__dict__ # 顯示實例成員
{'i': 123456789}

(2) 調(diào)用類型內(nèi)部方法,需要省略 self 參數(shù)。
>>> class Class1:
    def __init__(self):
      self.__test("Hello, World!")
    def __test(self, s):
      print s

>>> Class1()
Hello, World!
<__main__.Class1 instance at 0x00D37B48>

-----------------------

我們可以在成員名稱前添加 "__" 使其成為私有成員。
>>> class Class1:
    __i = 123
    def __init__(self):
      self.__x = 0
    def __test(self):
      print id(self)

>>> Class1.i
Traceback (most recent call last):
File "<pyshell#102>", line 1, in <module>
Class1.i
AttributeError: class Class1 has no attribute 'i'

>>> Class1().__x
Traceback (most recent call last):
File "<pyshell#103>", line 1, in <module>
Class1().__x
AttributeError: Class1 instance has no attribute '__x'

>>> Class1().test()
Traceback (most recent call last):
File "<pyshell#104>", line 1, in <module>
Class1().test()
AttributeError: Class1 instance has no attribute 'test'

事實上這只是一種規(guī)則,并不是編譯器上的限制。我們依然可以用特殊的語法來訪問私有成員。
>>> Class1._Class1__i
123
>>> a = Class1()
>>> a._Class1__x
0
>>> a._Class1__test()
13860376

-----------------------

除了靜態(tài)(類型)字段,我們還可以定義靜態(tài)方法。

>>> class Class1:
    @staticmethod
    def test():
      print "static method"
    
>>> Class1.test()
static method

-----------------------

從設(shè)計的角度,或許更希望用屬性(property)來代替字段(field)。
>>> class Class1:
    def __init__(self):
      self.__i = 1234
    def getI(self): return self.__i
    def setI(self, value): self.__i = value
    def delI(self): del self.__i
    I = property(getI, setI, delI, "Property I")
  
>>> a = Class1()
>>> a.I
1234
>>> a.I = 123456
>>> a.I
123456

如果只是 readonly property,還可以用另外一種方式。
>>> class Class1:
    def __init__(self):
      self.__i = 1234  
    @property
    def I(self):
      return self.__i
  
>>> a = Class1()
>>> a.I
1234

-----------------------

用 __getitem__ 和 __setitem__ 可以實現(xiàn) C# 索引器的功能。
>>> class Class1:
    def __init__(self):
      self.__x = ["a", "b", "c"]
    def __getitem__(self, key):
      return self.__x[key]
    def __setitem__(self, key, value):
      self.__x[key] = value

    
>>> a = Class1()
>>> a[1]
'b'
>>> a[1] = "xxxx"
>>> a[1]
'xxxx'

重載

Python 支持一些特殊方法和運算符重載。

>>> class Class1:
    def __init__(self):
      self.i = 0
    def __str__(self):
      return "id=%i" % id(self)
    def __add__(self, other):
      return self.i + other.i

>>> a = Class1()
>>> a.i = 10
>>> str(a)
'id=13876120'
>>> b = Class1()
>>> b.i = 20
>>> a + b
30

通過重載 "__eq__",我們可以改變 "==" 運算符的行為。
>>> class Class1:
    pass

>>> a = Class1()
>>> b = Class1()
>>> a == b
False

>>> class Class1:
    def __eq__(self, x):
      return True
  
>>> a = Class1()
>>> b = Class1()
>>> a == b
True

Open Class

這是個有爭議的話題。在 Python 中,我們隨時可以給類型或?qū)ο筇砑有碌某蓡T。

1. 添加字段
>>> class Class1:
    pass

>>> a = Class1()
>>> a.x = 10
>>> a.x
10
>>> dir(a)
['__doc__', '__module__', 'x']
>>> b = Class1()
>>> dir(b)
['__doc__', '__module__']
>>> del a.x
>>> dir(a)
['__doc__', '__module__']

2. 添加方法
>>> class Class1:
    pass

>>> def test():
    print "test"
  
>>> def hello(self):
    print "hello ", id(self)
  
>>> a = Class1()
>>> dir(a)
['__doc__', '__module__']
>>> Class1.test = test
>>> dir(a)
['__doc__', '__module__', 'test']
>>> b = Class1()
>>> dir(b)
['__doc__', '__module__', 'test']
>>> a.hello = hello
>>> a.hello(a)
hello 13860416
>>> dir(a)
['__doc__', '__module__', 'hello', 'test']
>>> dir(b)
['__doc__', '__module__', 'test']

3. 改變現(xiàn)有方法
>>> class Class1:
    def test(self):
      print "a"
    
>>> def test(self):
    print "b"
  
>>> Class1.test = test
>>> Class1().test()
b

另外,有幾個內(nèi)建函數(shù)方便我們在運行期進(jìn)行操作。
>>> hasattr(a, "x")
False
>>> a.x = 10
>>> getattr(a, "x")
10
>>> setattr(a, "x", 1234)
>>> a.x
1234

Python Open Class 是如何實現(xiàn)的呢?我們看一下下面的代碼。

>>> class Class1:
    pass

>>> a = Class1()
>>> a.__dict__
{}
>>> a.x = 123
>>> a.__dict__
{'x': 123}
>>> a.x
123
>>> a.test = lambda i: i + 1
>>> a.test(1)
2
>>> a.__dict__
{'test': <function <lambda> at 0x00D39DB0>, 'x': 123}

原來,Python Class 對象或類型通過內(nèi)置成員 __dict__ 來存儲成員信息。

我們還可以通過重載 __getattr__ 和 __setattr__ 來攔截對成員的訪問,需要注意的是 __getattr__ 只有在訪問不存在的成員時才會被調(diào)用。
>>> class Class1:
    def __getattr__(self, name):
      print "__getattr__"
      return None
    def __setattr__(self, name, value):
      print "__setattr__"
      self.__dict__[name] = value

    
>>> a = Class1()
>>> a.x
__getattr__
>>> a.x = 123
__setattr__
>>> a.x
123

如果類型繼承自 object,我們可以使用 __getattribute__ 來攔截所有(包括不存在的成員)的獲取操作。
注意在 __getattribute__ 中不要使用 "return self.__dict__[name]" 來返回結(jié)果,因為在訪問 "self.__dict__" 時同樣會被 __getattribute__ 攔截,從而造成無限遞歸形成死循環(huán)。

>>> class Class1(object):
    def __getattribute__(self, name):
      print "__getattribute__"
      return object.__getattribute__(self, name)

  
>>> a = Class1()
>>> a.x
__getattribute__

Traceback (most recent call last):
File "<pyshell#3>", line 1, in <module>
a.x
File "<pyshell#1>", line 4, in __getattribute__
return object.__getattribute__(self, name)
AttributeError: 'Class1' object has no attribute 'x'
>>> a.x = 123
>>> a.x
__getattribute__
123

---------------
__bases__是父類吧,指的是繼承關(guān)系;
__class__才是一個對象的"類/類型",指的是類型/實例關(guān)系。

在06-1-10,shhgs <shhgs.efhilt at gmail.com> 寫道:
>
> mixin
>
> class A(object) :
>       pass
>
> class B :
>      def f(self) :
>          print "B.f()"
>
> A.__bases__ += (B, )     #好像這個就是我要找的,可以把.__bases__這個元組?list里面的基類們隨時刪除、添加! 呵呵!功能超強??!
>
> a = A()
> a.f()

-------------
開始學(xué)習(xí)python的時候,看了一些教程和資料,都覺得在面向?qū)ο缶幊踢@一方面講得比較零散,自己也就總覺得不得要領(lǐng)。直到看到了Oreilly出的Python in the Nutshell,英文版,特別是Charpter5: Object-Oriented Python,才開始明白一點點東西。這本書,對章節(jié)的編排非常合理,而且不光教你how還教你why,覺得受益匪淺。

看的過程中,自己陸續(xù)的記下一些東西,有對書的部分翻譯,有自己的體會和測試代碼。

翻譯中,有少部分是直接翻譯,大部分其實只是自己的意譯,還添油加醋加上了自己的一些說明。畢竟,我的目的是為了把問題弄懂,不想去做那么多hard translation反讓人如墜迷霧。于是想?yún)R成一篇,就有了這篇文章。文章的安排,基本上和Python in the Nutshell的Charpter5相同,但內(nèi)容要短得多。

Wilson Sun
----------------------------------------------------------------------------------------------------------------

python是一種面向?qū)ο蟮?span id="rn5spag" class="wp_keywordlink">編程語言。不過不象其它的面向?qū)ο蟮恼Z言,python并不強迫你用面向?qū)ο蟮姆椒▉韺懗绦颍苍试S你用面向過程的方式來寫模塊、函數(shù)等。

1.1           經(jīng)典對象(classic class)

在2.1及其之前版本的python,只能用經(jīng)典對象這種對象模型來編程。在2.2和2.3版本的python,經(jīng)典對象也是默認(rèn)的對象模型。

1.1.1      經(jīng)典對象的一些特征:

l          你可以象調(diào)用函數(shù)一樣的來調(diào)用一個對象。一旦調(diào)用,該對象的一個實例就被建立。

l          你可以隨意地為對象內(nèi)部的屬性命名。

l          屬性可以是數(shù)據(jù)類型,也可以是函數(shù)類型。

l          若屬性(attribute)是函數(shù)類型,那么就把它看做對象的一個方法(method)。

l          python為函數(shù)規(guī)定了一種特殊的命名方式,用前后兩個下劃線來包圍函數(shù)名,例如:__methodName__。

l          對象可以繼承。

1.1.2      對象聲明

聲明對象的語法:

class classname[(base-classes)]: 
     statement(s)
classname是函數(shù)名。

base-classes的值必須為對象,用逗號分隔,它相當(dāng)于java中的超類(superclass)。

繼承關(guān)系可以被傳遞,如果c1是c2的子類,c2是c3的子類,那么c1也是c3的子類。

內(nèi)建函數(shù)issubclass(C1, C2)可以判斷繼承關(guān)系,若c1是c2的子類,那么函數(shù)返回true。由于任何類都被看作是自身的子類,所以若有類C,那么issubclass(C,C)返回true。

Def聲明語法和class聲明語法的區(qū)別:

Def聲明時,冒號前面必須有括號,即使它本身沒有任何參數(shù);但class聲明時,只有當(dāng)class有其基類(base class)的時候,才需要在括號中寫出其基類。

1.1.3      對象正文

1.1.3.1     對象內(nèi)部的屬性

在對象內(nèi)部調(diào)用其屬性,直接寫其屬性名稱即可,如:

class C3:

x = 23

y = x + 22                   # must use just x, not C3.x

但若在對象內(nèi)部定義了方法,要在方法中調(diào)用對象中的其它屬性,需要寫屬性的全名,如:

class C4:

     x = 23

     def amethod(self):

         print C4.x       # must use C4.x, not just x

當(dāng)對象被聲明的時候,其中的一些屬性已經(jīng)被隱式聲明了。

__name__:類的名稱

__base__:tuple對象,放置對象的所有基類

__dict__:dict對象,放置對象的所有屬性

例如:對象C內(nèi)部有屬性S,那么C.S=x 等價于C._ _dict_ _['S']=x

1.1.3.2     對象內(nèi)部的函數(shù)

對象內(nèi)部的函數(shù)寫法與普通的函數(shù)寫法差不多,不過函數(shù)的第一個參數(shù)要寫為self,如:

class C5:
     def hello(self):
         print "Hello"

1.1.3.3     私有變量

私有變量的聲明,只需在變量名前面加兩個下劃線,如類C的內(nèi)部有私有變量user,應(yīng)聲明為:__user。

事實上,當(dāng)python編譯的時候,會把__user改為_C__user(即_ClassName__VariableName的格式)。

        無論是否在對象內(nèi)部,以一個下劃線開頭的變量,都被看作私有變量。

1.1.4      實例

回顧一下前面的“經(jīng)典對象的一些特征”中的第一點:“你可以象調(diào)用函數(shù)一樣的來調(diào)用一個對象”。創(chuàng)建實例的時候,就是如此創(chuàng)建:
anInstance = C5( )

1.1.4.1     _ _init_ _

如果一個對象的內(nèi)部,有或繼承有_ _init_ _方法,那么當(dāng)這個對象被調(diào)用(用java上的詞可以叫實例化)時,_ _init_ _方法會自動地被調(diào)用。

_ _init_ _方法不能有返回值,如果一定需要跳出或返回,也只能返回None。

例如:

class C4:

        def __init__(self):

               return 'sss'

a = C4();

python會報錯:
Traceback (most recent call last):

   File "<pyshell#26>", line 1, in -toplevel-

     a = C4();

TypeError: __init__() should return None

_ _init_ _方法的主要目的,是為了在創(chuàng)建對象實例的時候,對對象的屬性賦值。這么做可以增加程序的可讀性。如果對象內(nèi)部沒有_ _init_ _,那么你調(diào)用對象的時候就不能帶上任何的參數(shù)。

1.1.4.2     實例中的屬性

用點(.)來訪問實例中的屬性。

即使一個對象已經(jīng)被實例化,仍可以個實例增加任意的屬性,并對其賦值。

class C7: pass
z = C7(   )
z.x = 23

實例被創(chuàng)建以后,該實例會被自動加上兩個屬性:
_ _class_ _:實例所屬的對象

_ _dict_ _:實例的所有屬性(實例自身的屬性和其所屬對象的屬性)
如:
class C6:

     def _ _init_ _(self,n):

         self.x = n

a = C6(234)

a.y=213213

a.__dict__

#執(zhí)行結(jié)果:{'y': 213213, 'x': 234}

1.1.4.3    工廠函數(shù)

遙想一下,設(shè)計模式中被用得最多得工廠模式(Factory),它被用于創(chuàng)建對象的實例。在python當(dāng)中,最直接的用來實現(xiàn)工廠模式的方式,似乎是用_ _init_ _來返回不同的實例,但是,unfortunately,_ _init_ _最多也只能返回None。所以要實現(xiàn)工廠模式,最佳的方式就是專門寫一個函數(shù),用來返回不同的實例,這類函數(shù),可以稱之為工廠函數(shù)(Factory Function)。

如下例,appropriateCase就是一個工廠函數(shù)。

class SpecialCase:

     def amethod(self): print "special"

class NormalCase:

     def amethod(self): print "normal"

def appropriateCase(isnormal=1):

     if isnormal: return NormalCase(   )

     else: return SpecialCase(   )

aninstance = appropriateCase(isnormal=0)

aninstance.amethod(   )

1.1.5      屬性引用

假設(shè)x是對象C的實例,當(dāng)引用x.name的時候,是如何來查找它的值呢?用最簡單的話來概括,就是,由小到大由近到遠(yuǎn),依次查找name的值。

再說得具體一些,是按下面的方式查找:

l          若x.name是x._ _dict_ _,中的key,那么返回x._ _dict_ _['name'] (查找自身)

l          否則,查找C._ _dict_ _中的key,是則返回C._ _dict_ _['name'] (查找所屬對象)

l          否則,查找C的基類,在C._ _bases_ _中繼續(xù)按上面的兩步查找(查找所屬對象的基類)

l          否則,拋出異常:AttributeError

1.1.6      方法的綁定與非綁定(Bound and Unbound)

上面講了屬性的引用,方法的綁定與非綁定實際上涉及到的是方法引用的問題。方法實際上使用函數(shù)來實現(xiàn)。當(dāng)方法被引用時,并非直接返回其對應(yīng)的函數(shù),而是將這個函數(shù),載入到了bound或者unbound方法上。

bound和unbound的區(qū)別在于:bound將特定的函數(shù),與特定的實例相關(guān)聯(lián);而unbound則相反。

若沒有同名屬性,直接用函數(shù)名(函數(shù)名后不帶括號),可以觀察到其綁定狀態(tài)。

假設(shè)有對象C和實例x:

class C:

        a = 1

        def g(self): print "method g in class C"

x = C()   

print x.g

#執(zhí)行結(jié)果:<bound method C.g of <__main__.C instance at 0x00BA2F58>>

print C.g

#執(zhí)行結(jié)果:<unbound method C.g>

上面的執(zhí)行結(jié)果表明:x.g被綁定到了C.g()函數(shù)上,所以執(zhí)行x.g()會有結(jié)果返回;而C.g沒有被綁定,所以執(zhí)行C.g()沒有結(jié)果。

1.1.6.1     細(xì)說非綁定
若處于非綁定狀態(tài),當(dāng)一個函數(shù)被引用的時候,實際返回的是unbound方法,該方法內(nèi)部載有該函數(shù)。Unbound方法有三個只讀屬性

im_class:被引用函數(shù)所在的對象
im_func:被引用的函數(shù)
im_self:總是為None

非綁定的方法也可以被調(diào)用,需要把im_class對象的實例名做為第一個參數(shù),那么就會返回im_func的函數(shù)了。
例如上面的C.g()沒有結(jié)果,但可以執(zhí)行C.g(x),x為C的實例,就可以得到C.g()函數(shù)的正確執(zhí)行結(jié)果了。

1.1.6.2     細(xì)說綁定
當(dāng)執(zhí)行x.g()時,返回的是bound 方法。bound方法和unbound方法類似,也有三個只讀屬性:im_class,im_func,im_self,但不同之處在于:im_self的值為x。

1.1.7      繼承與重載
從前面說到的“屬性引用”的查找方法,不難看出python繼承的實現(xiàn)方式。若x為C的實例,當(dāng)調(diào)用x.g()的時候,python先看x._ _dict_ _中有沒有g(shù),沒有就查找C._ _dict_ _,若再沒有就查找C._ _base_ _有沒有g(shù)。C._ _base_ _中放置了C的基類,就實現(xiàn)了對象的繼承。用這樣的機制,也同樣實現(xiàn)了重載。

1.1.7.1     超類代理
在子類中,有可能要用到超類的屬性,那么就要使用到超類代理機制,實際上是用unbound方式調(diào)用超類的函數(shù)。例如:

class Base:
     def greet(self, name): print "Welcome ", name
class Sub(Base):
     def greet(self, name):
         print "Well Met and",
         Base.greet(self, name)
x = Sub(   )
x.greet('Alex')
超類代理常用于_ _init_ _方法中,因為在python中,子類的_ _init_ _方法中并不會自動地調(diào)用其超類中的_ _init_ _方法,所以需要利用這種超類代理機制手動調(diào)用一下。

上述就是小編為大家分享的python中類、基類、多態(tài)、取消基類的原理是什么了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI