CrazyWing:Python自動化運維開發實戰 十八、Python面向對象

導語

在Python中,類和 OOP 都不是日常編程所必需的。儘管它從一開始設計就是面向對象的,並且結構上支持 OOP,但 Python 沒有限定或要求你在你的應用中寫 OOP 的代碼。
不過!不學面向對象可以麼?嘿嘿!那是必須的不可以!

相關概念

• 類(Class):

用來描述具有相同的屬性和方法的對象的集合。它定義了該集合中每個對象所共有的屬性和方法。對象是類的實例。

• 方法 :

類中定義的函數。

• 類變量 :

類變量在整個實例化的對象中是公用的。類變量定義在類中且在函數體之外。類變量通常不作爲實例變量使用。

• 實例變量:

定義在方法中的變量,只作用於當前實例

• 數據成員:

類變量或者實例變量,用於處理類及其實例對象的相關的數據。

• 方法重寫:

如果從父類繼承的方法不能滿足子類的需求,可以對其進行改寫,這個過程叫方法的覆蓋(override),也稱爲方法的重寫。

• 繼承 :

即一個派生類(derived class)繼承基類(base class)的字段和方法。繼承也允許把一個派生類的對象作爲一個基類對象對待。例如,有這樣一個設計 :一個Dog類型的對象派生自Animal類,這是模擬"是一個(is-a)"關係(例如,Dog是一個Animal)。

• 實例化 :

創建一個類的實例,類的具體對象。

• 對象 :

通過類定義的數據結構實例。對象包括兩個數據成員(類變量和實例變量)和方法。

創建類

使用class語句來創建一個新類,class之後爲類的名稱並以冒號結尾

class ClassName:
   '類的幫助信息'     #類文檔字符串,類的幫助信息可以通過ClassName.__doc__查看
   class_suite        #類體,class_suite 由類成員,方法,數據屬性組成。

例:
#!/usr/bin/python
class Employee:
'所有員工的基類'
empCount = 0
def init(self, name, salary):
self.name = name
self.salary = salary
Employee.empCount += 1

   def displayCount(self):
     print "Total Employee %d" % Employee.empCount

   def displayEmployee(self):
      print "Name : ", self.name,  ", Salary: ", self.salary

• empCount變量是一個類變量,它的值將在這個類的所有實例之間共享。你可以在內部類或外部類使用Employee.empCount訪問。
init()方法是一種特殊的方法,被稱爲類的構造函數或初始化方法,當創建了這個類的實例時就會調用該方法

創建實例對象

要創建一個類的實例,可以使用類的名稱,並通過init方法接受參數。
創建 Employee 類的第一個對象

emp1 = Employee("wing", 2000)

創建 Employee 類的第二個對象

emp2 = Employee("liuchao", 5000)

訪問屬性

使用點(.)來訪問對象的屬性:

    emp1.displayEmployee()
    emp2.displayEmployee()
    print "Total Employee %d" % Employee.empCount

使用函數的方式來訪問屬性:
• getattr(obj, name[, default]) : 訪問對象的屬性。
• hasattr(obj,name) : 檢查是否存在一個屬性。
• setattr(obj,name,value) : 設置一個屬性。如果屬性不存在,會創建一個新屬性。
• delattr(obj, name) : 刪除屬性。
例:

    hasattr(emp1, 'age')      # 如果存在 'age' 屬性返回 True。
    getattr(emp1, 'age')      # 返回 'age' 屬性的值
    setattr(emp1, 'age', 8)  # 添加屬性 'age' 值爲 8
    delattr(emp1, 'age')      # 刪除屬性 'age'

完整實例:
#!/usr/bin/python
class Employee:
'所有員工的基類'
empCount = 0
def init(self, name, salary):
self.name = name
self.salary = salary
Employee.empCount += 1

   def displayCount(self):
     print "Total Employee %d" % Employee.empCount

   def displayEmployee(self):
      print "Name : ", self.name,  ", Salary: ", self.salary

emp1 = Employee("Zara", 2000)
emp2 = Employee("Manni", 5000)
emp1.displayEmployee()
emp2.displayEmployee()
print "Total Employee %d" % Employee.empCount

輸出結果:
Name :  Zara ,Salary:  2000
Name :  Manni ,Salary:  5000
Total Employee 2

添加、刪除、修改類的屬性:

emp1.age = 7  # 添加一個 'age' 屬性
emp1.age = 8  # 修改 'age' 屬性
del emp1.age   # 刪除 'age' 屬性

靜態變量和實例變量

亦可稱爲靜態數據(類數據屬性,也叫靜態字段,靜態屬性)。
這些數據是與它們所屬的類對象綁定的,不依賴於任何類實例。
如果你熟悉 Java 或 C++,這種類型的數據相當於在一個變量聲明前加上 static 關鍵字。
靜態成員通常僅用來跟蹤與類相關的值。
一般,我們會考慮用實例屬性(也叫普通變量或實例變量),而不是類屬性。

靜態變量在內存中只保存一份
實例變量在每個對象中都保存一份

應用場景: 通過類創建對象時,如果每個對象都具有相同的字段,那麼就使用靜態變量
例1:類數據屬性(foo):

>> class C(object):
... foo = 100
>> print C.foo
100
>> C.foo = C.foo + 1
>> print C.foo
101

注意,上面的代碼中,看不到任何類實例的引用

例2:

    class Province:
        # 靜態變量
        country = '中國'
        def __init__(self, name):
            # 實例變量
            self.name = name

    # 直接訪問普通字段
    obj = Province('河北省')
    print obj.name
    #直接訪問靜態字段
    Province.country

類屬性與方法

類的私有屬性(私有字段)

__private_attrs:兩個下劃線開頭,聲明該屬性爲私有,不能在類地外部被使用或直接訪問。在類內部的方法中使用時 self.__private_attrs。

類的方法

在類地內部,使用def關鍵字可以爲類定義一個方法,與一般函數定義不同,類方法必須包含參數self,且爲第一個參數

類的私有方法

__private_method:兩個下劃線開頭,聲明該方法爲私有方法,不能在類地外部調用。在類的內部調用 self.__private_methods

實例

#!/usr/bin/python
class JustCounter:
    __secretCount = 0  # 私有變量
    publicCount = 0     # 公開變量

    def count(self):
        self.__secretCount += 1
        self.publicCount += 1
        print self.__secretCount

counter = JustCounter()
counter.count()
counter.count()
print counter.publicCount
print counter.__secretCount   # 報錯,實例不能訪問私有變量Python 通過改變名稱來包含類名:
1
2
2
Traceback (most recent call last):
  File "test.py", line 17, in <module>
    print counter.__secretCount     # 報錯,實例不能訪問私有變量
AttributeError: JustCounter instance has no attribute '__secretCount'

Python不允許實例化的類訪問私有數據,但你可以使用 object._className__attrName 訪問屬性,將如下代碼替換以上代碼的最後一行代碼:
.........................
print counter._JustCounter__secretCount
執行結果:
1
2
2
2  

特殊類屬性(Python內置類屬性):

   C.__dict__ :類C的屬性(包含一個字典,由類的數據屬性組成) 
   C.__doc__ :類C的文檔字符串 
   C.__name__:類C類名(字符串) 
   C.__module__:類C定義所在的模塊(類的全名是'__main__.className',如果類位於一個導入
模塊mymod中,那麼className.__module__ 等於 mymod) 
   C.__bases__ : 類C的所有父類構成元素(包含了一個由所有父類組成的元組) 
   C.__class__:實例對應的類(僅新式類中)

根據上面定義的類 MyClass,有如下結果:
     >>> MyClass.__name__
    'MyClass'

    >>> MyClass.__doc__
    'MyClass class definition'

    >>> MyClass.__bases__
    (<type 'object'>,)

    >>> print MyClass.__dict__
    {'__doc__': None, 'myVersion': 1, 'showMyVersion':
    <function showMyVersion at 950ed0>, '__module__': '__main__'}

    >>> MyClass.__module__
    '__main__'

    >>> MyClass.__class__
    <type 'type'>

__name__
    是給定類的字符名字。
    它適用於那種只需要字符串(類對象的名字),而非類對象本身的情況。一些內建的類型也有這個屬性,我們將會用
到其中之一來展示__name__字符串的益處。

    類型對象是一個內建類型的例子,它有__name__的屬性。回憶一下,type()返回被調用對象的類型。
    我們可以使用類型對象的__name__屬性來取得相應的字符串名。

如下例示:
    >>> stype = type('What is your quest?')
    >>> stype                       # stype is a type object stype 是一個類型對象
    <type 'string'>
    >>> stype.__name__      # get type as a string 得到類型名(字符串表示)
    'string'

    >>> type(3.14159265)    # also a type object 又一個類型對象
    <type 'float'>
    >>> type(3.14159265).__name__   # get type as a string 得到類型名(字符串表示) 
    'float'

__doc__
    是類的文檔字符串,與函數及模塊的文檔字符串相似,必須緊隨頭行(header line) 後的字符串。
    文檔字符串不能被派生類繼承,也就是說派生類必須含有它們自己的文檔字符串。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
__dict__
    包含一個字典,由類的數據屬性組成。
    訪問一個類屬性的時候,Python 解釋器將會搜索字典以得到需要的屬性。
    如果在__dict__中沒有找到,將會在基類的字典中進行搜索, 採用“深度優先搜索”順序。
    基類集的搜索是按順序的,從左到右,按其在類定義時,定義父類參數時的順序。
    對類的修改會僅影響到此類的字典;基類的__dict__屬性不會被改動的

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
__module__
    Python 支持模塊間的類繼承。爲更清晰地對類進行描述,1.5 版本中引入了__module__, 這樣類名就完全由模
塊名所限定。看一下下面的例子:
    >>> class C(object): 
    ...         pass
    ...
    >>> C
    <class __main__.C at 0x53f90>

    >>> C.__module__
    '__main__'

    類 C 的全名是“__main__.C”,比如,source_module.class_name。
    如果類 C 位於一個導入的模塊中,如 mymod,像下面的: 
    >>> from mymod import C 

    >>> C
    <class mymod.C at 0x53ea0> 

    >>> C.__module__
    'mymod'

    在以前的版本中,沒有特殊屬性__module__,很難簡單定位類的位置,因爲類沒有使用它們的全名。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
__class__
    最後,由於類型和類的統一性,當訪問任何類的__class__屬性時,你將發現它就是一個類型對象的實例。換句話說,一個類已是一種類型了。因爲經典類並不認同這種等價性(一個經典類是一 個類對象,一個類型是一個類型對象),對這些對象來說,這個屬性並未定義。
__class__到底是什麼意思?看例子
    # cat a.py
    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    class A:
        def __init__(self,url):
            self.url = url
        def out(self):
            return self.url

    a = A('news.163.com')
    print a.out()

    b = a.__class__('www.bccn.net')
    print b.out()

    print A
    print a.__class__

    # python a.py
    news.163.com
    www.bccn.net
    __main__.A
    __main__.A

    可以看出a.__class__就等效於a的類A

內置類屬性調用實例:
#!/usr/bin/python
class Employee:
'所有員工的基類'
empCount = 0

   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1

   def displayCount(self):
     print "Total Employee %d" % Employee.empCount

   def displayEmployee(self):
      print "Name : ", self.name,  ", Salary: ", self.salary

print "Employee.__doc__:", Employee.__doc__
print "Employee.__name__:", Employee.__name__
print "Employee.__module__:", Employee.__module__
print "Employee.__bases__:", Employee.__bases__
print "Employee.__dict__:", Employee.__dict__

輸出結果:
Employee.__doc__: 所有員工的基類
Employee.__name__: Employee
Employee.__module__: __main__
Employee.__bases__: ()
Employee.__dict__: {'__module__': '__main__', 'displayCount': <function displayCount at 0x10a939c80>, 'empCount': 0, 'displayEmployee': <function displayEmployee at 0x10a93caa0>, '__doc__': '\xe6\x89\x80\xe6\x9c\x89\xe5\x91\x98\xe5\xb7\xa5\xe7\x9a\x84\xe5\x9f\xba\xe7\xb1\xbb', '__init__': <function __init__ at 0x10a939578>}

類的繼承

面向對象的編程帶來的主要好處之一是代碼的重用,實現這種重用的方法之一是通過繼承機制。
繼承完全可以理解成類之間的類型和子類型關係。

繼承語法

class 派生類名(基類名):

python繼承中的特點:
1:在繼承中基類的構造(init()方法)不會被自動調用,它需要在其派生類的構造中親自專門調用。
2:在調用基類的方法時,需要加上基類的類名前綴,且需要帶上self參數變量。區別於在類中調用普通函數時並不需要帶上self參數
3:Python總是首先查找對應類型的方法,如果它不能在派生類中找到對應的方法,它纔開始到基類中逐個查找。(先在本類中查找調用的方法,找不到纔去基類中找)。

多重繼承

如果在繼承元組中列了一個以上的類,那麼它就被稱作"多重繼承" 。
語法:
派生類的聲明,與他們的父類類似,繼承的基類列表跟在類名之後,如下所示:

class SubClassName (ParentClass1[, ParentClass2, ...]):
   'Optional class documentation string'
   class_suite

實例:
#!/usr/bin/python
class Parent: # 定義父類
parentAttr = 100
def init(self):
print "調用父類構造函數"

   def parentMethod(self):
      print '調用父類方法'

   def setAttr(self, attr):
      Parent.parentAttr = attr

   def getAttr(self):
      print "父類屬性 :", Parent.parentAttr

class Child(Parent): # 定義子類
   def __init__(self):
      print "調用子類構造方法"

   def childMethod(self):
      print '調用子類方法 child method'

c = Child()              # 實例化子類
c.childMethod()      # 調用子類的方法
c.parentMethod()   # 調用父類方法
c.setAttr(200)        # 再次調用父類的方法
c.getAttr()              # 再次調用父類的方法

以上代碼執行結果如下:
調用子類構造方法
調用子類方法 child method
調用父類方法
父類屬性 : 200

繼承多個類

class A: # 定義類 A
.....

class B: # 定義類 B
.....

class C(A, B): # 繼承類 A 和 B
.....

使用issubclass()或者isinstance()方法來檢測。
issubclass()布爾函數判斷一個類是另一個類的子類或者子孫類,語法:
issubclass(sub,sup)

例子:

    def foo():  
        pass                
    print issubclass(foo.__class__, object) 

    結果:        
    True
上述代碼說明了Python 中的函數是 object 的子類

isinstance(obj, Class) 布爾函數如果obj是Class類的實例對象或者是一個Class子類的實例對象則返回true。

方法重寫

如果你的父類方法的功能不能滿足你的需求,你可以在子類重寫你父類的方法:

實例:

#!/usr/bin/python
class Parent:        # 定義父類
   def myMethod(self):
      print '調用父類方法'

class Child(Parent): # 定義子類
   def myMethod(self):
      print '調用子類方法'

c = Child()               # 子類實例
c.myMethod()         # 子類調用重寫方法

輸出結果:
    調用子類方法

基礎重載方法

下表列出了一些通用的功能,你可以在自己的類重寫:

| 序號 | 方法 描述 簡單的調用 |
| 1 | __init__ ( self [,args...] )
構造函數
簡單的調用方法: obj = className(args) |
| 2 | __del__( self )
析構方法, 刪除一個對象
簡單的調用方法 : del obj |
| 3 | __repr__( self )
轉化爲供解釋器讀取的形式
簡單的調用方法 : repr(obj) |
| 4 | __str__( self )
用於將值轉化爲適於人閱讀的形式
簡單的調用方法 : str(obj) |
| 5 | __cmp__ ( self, x )
對象比較
簡單的調用方法 : cmp(obj, x) |

運算符重載

Python同樣支持運算符重載,實例如下:
#!/usr/bin/python
class Vector:
def init(self, a, b):
self.a = a
self.b = b

def str(self):
return 'Vector (%d, %d)' % (self.a, self.b)

def add(self,other):
return Vector(self.a + other.a, self.b + other.b)

v1 = Vector(2,10)
v2 = Vector(5,-2)
print v1 + v2
以上代碼執行結果如下所示:
Vector(7,8)

類、實例和其他對象的內建函數

issubclass()
issubclass() 布爾函數判斷一個類是另一個類的子類或子孫類。

語法:
    issubclass(sub, sup)

issubclass() 返回True的情況:
   1.  給出的子類sub確實是父類sup的一個子類(反之,則爲False)。 
   2.  這個函數也允許“不嚴格”的子類,意味着,一個類可視爲其自身的子類,所以,這個函數如果當 sub 就是 sup,或者從 sup 派生而來,則返回 True。(一個“嚴格的”子類是嚴格意義上的從一個類派生而來的子類。)
   3.  從 Python 2.3 開始,issubclass()的第二個參數可以是可能的父類組成的 tuple(元組),這時, 只要第一個參數是給定元組中任何一個候選類的子類時,就會返回 True。

isinstance()
isinstance() 布爾函數在判定一個對象是否是另一個給定類的實例時,非常有用。

語法:
    isinstance(obj1, obj2)
    isinstance()在 obj1 是類 obj2 的一個實例,或者是 obj2 的子類的一個實例時,返回 True (反之,則爲 False)

例子:
    >>> class C1(object): pass
    >>> class C2(object): pass
    >>> c1 = C1()
    >>> c2 = C2()
    >>> isinstance(c1, C1) True
    >>> isinstance(c2, C1) False
    >>> isinstance(c1, C2) False
    >>> isinstance(c2, C2) True
    >>> isinstance(C2, c2) 
    Traceback (innermost last):
    File "<stdin>", line 1, in ?
    isinstance(C2, c2)
    TypeError: second argument must be a class

注意:第二個參數應當是類;不然,你會得到一個 TypeError。但如果第二個參數是一個類型對象,則不會出現異常。這是允許的,因爲你也可以使用 isinstance()來檢查一個對象 obj1 是否是 obj2 的類型,比如:
    >>> isinstance(4, int)
    True
    >>> isinstance(4, str)
    False
    >>> isinstance('4', str)
    True

isinstance()也可以使用一個元組(tuple)作爲第二個參數。如果第一個參數是第二個參數中給定元組的任何一個候選類型或類的實例時,就會返回 True。

hasattr(), getattr(),setattr(), delattr()
*attr()系列函數可以在各種對象下工作,不限於類(class)和實例(instances),因爲在類和實例中使用極其頻繁,就在這裏列出來了。

hasattr()函數是 Boolean 型的,它的目的就是爲了決定一個對象是否有一個特定的屬性,一般用於訪問某屬性前先作一下檢查。

getattr()取得對象的屬性,會在你試圖讀取一個不存在的屬性時,引發 AttributeError 異常,除非給出那個可選的默認參數。

setattr()將要麼加入一個新的屬性,要麼取代一個已存在的屬性。

delattr()函數會從一個對象中刪除屬性。

下面一些例子使用到了*attr()系列函數:

>>> class myClass(object): 
...         def __init__(self): 
...             self.foo = 100 
>>> myInst = myClass()
>>> hasattr(myInst, 'foo')
True
>>> getattr(myInst, 'foo')
100
>>> hasattr(myInst, 'bar') 
False
>>> getattr(myInst, 'bar') 
Traceback (most recent call last):
File "<stdin>", line 1, in ?
getattr(myInst, 'bar')
AttributeError: myClass instance has no attribute 'bar'
>>> getattr(c, 'bar', 'oops!')
'oops!'
>>> setattr(myInst, 'bar', 'my attr')
>>> dir(myInst)
['__doc__', '__module__', 'bar', 'foo']
>>> getattr(myInst, 'bar')       # same as myInst.bar 
'my attr'
>>> delattr(myInst, 'foo')
>>> dir(myInst)
['__doc__', '__module__', 'bar']
>>> hasattr(myInst, 'foo')
False

dir()
用 dir()列出一個模塊所有屬性的信息, dir()還可以用在對象上。
dir()提供的信息比以前更加詳盡。根據文檔,“除了實例變量名和常用方法外,它還顯示那些通過特殊標記來調用的方法,像iadd(+=),len(len()), ne(!=)。
dir()作用在實例上(經典類或新式類)時,顯示實例變量,還有在實例所在的類及所有它的基類中定義的方法和類屬性.
dir()作用在類上(經典類或新式類)時,則顯示類以及它的所有基類的dict中的內容。 但它不會顯示定義在元類(metaclass)中的類屬性.
dir()作用在模塊上時,則顯示模塊的dict的內容.
dir()不帶參數時,則顯示調用者的局部變量.

super()
super()函數的目的就是幫助程序員找出相應的父類, 然後方便調用相關的屬性。一般情況下,程序員可能僅僅採用非綁定方式調用祖先類方法。使用 super()可以簡化搜索一個合適祖先的任務,並且在調用它時,替你傳入實例或類型對象。
每個定義的類,都有一個名爲mro的屬性,它是一個元組,按照他們被搜索時的順序,列出了備搜索的類。
語法如下:
super(type[, obj])
給出 type,super()“返回此 type 的父類”。如果你希望父類被綁定,你可以傳入 obj 參數(obj 必須是
type 類型的).否則父類不會被綁定。obj 參數也可以是一個類型,但它應當是 type 的一個子類。通常,當給出 obj 時:

  1. 如果obj是一個實例,isinstance(obj,type)就必須返回True
  2. 如果obj是一個類或類型,issubclass(obj,type)就必須返回True

事實上,super()是一個工廠函數,它創造了一個 super object,爲一個給定的類使用mro 去查找相應的父類。很明顯,它從當前所找到的類開始搜索 MRO。
super()的主要用途,是來查找父類的屬性,比如, super(MyClass,self).init()。如果你沒有執行這樣的查找,你可能不需要使用 super()。

vars()
vars()內建函數與 dir()相似,只是給定的對象參數都必須有一個dict屬性。vars()返回一個字典,它包含了對象存儲於其dict中的屬性(鍵)及值。如果提供的對象沒有這樣一個屬性, 則會引發一個 TypeError 異常。如果沒有提供對象作爲 vars()的一個參數,它將顯示一個包含本地名字空間的屬性(鍵)及其值的字典,也就是locals()。

例子,使用類實例調用 vars():

    >>> class C(object):
    ...         pass
    >>> c = C()
    >>> c.foo = 100
    >>> c.bar = 'Python'
    >>> c.__dict__
    {'foo': 100, 'bar': 'Python'}
    >>> vars(c)
     {'foo': 100, 'bar': 'Python'}

Python新式類和經典類

Python從2.2開始,引入了 new style class(新式類)
Python 2.x中默認都是經典類,只有顯式繼承了object纔是新式類
Python 3.x中默認都是新式類,不必顯式的繼承object

新式類跟經典類的差別主要是以下幾點:
• 新式類對象可以直接通過class屬性獲取自身類型:type
• 繼承搜索的順序發生了改變
• 新式類增加了slots內置屬性, 可以把實例屬性的種類鎖定到slots規定的範圍之中
• 新式類增加了getattribute方法

1. 新式類對象可以直接通過class屬性獲取自身類型:type

#cat test1.py
#/usr/bin/env python2.7
    class E:    
    #經典類  
        pass  

    class E1(object):    
    #新式類  
        pass  

    e = E()  
    print "經典類"  
    print e  
    print type(e)  
    print e.__class__  

    print "新式類"  
    e1 = E1()  
    print e1  
    print e1.__class__  
    print type(e1)

輸出結果:
    經典類  
    <__main__.E instance at 0x0000000002250B08>  
    <type 'instance'>  
    __main__.E  

    新式類  
    <__main__.E1 object at 0x0000000002248710>  
    <class '__main__.E1'>  
    <class '__main__.E1'> 

    E1是定義的新式類。那麼輸出e1的時候,不論是type(e1),還是e1.__class__都是輸出的<class '__main__.E1'>。

2. 繼承搜索的順序發生了改變

經典類多繼承屬性搜索順序: 先深入繼承樹左側,再返回,開始找右側
新式類多繼承屬性搜索順序: 先水平搜索,然後再向上移動

#cat test2.py
    class A(object):    
        '新式類 ,作爲所有類的基類'
        def foo(self):    
            print "class A"   

    class A1():    
        '經典類,作爲所有類的基類 '
        def foo(self):    
            print "class A1"    

    class C(A):    
        pass  

    class C1(A1):    
        pass  

    class D(A):    
        def foo(self):    
            print "class D"    

    class D1(A1):    
        def foo(self):    
            print "class D1"    

    class E(C, D):    
        pass  

    class E1(C1, D1):    
        pass  

    e = E()  
    e.foo()  

    e1 = E1()  
    e1.foo() 

輸出結果:
    class D  
    class A1  

因爲A新式類,對於繼承A類都是新式類,首先要查找類E中是否有foo(),如果沒有則按順序查找C->D->A。它是一種廣度優先查找方式。

因爲A1經典類,對於繼承A1類都是經典類,首先要查找類E1中是否有foo(),如果沒有則按順序查找C1->A1->D1。它是一種深度優先查找方式。

3. 新式類增加了slots內置屬性, 可以把實例屬性的種類鎖定到slots規定的範圍之中。

比如只允許對A實例添加name和age屬性:

#cat test3.py
    class A(object):    
        __slots__ = ('name', 'age')   

    class A1():    
        __slots__ = ('name', 'age')   

    a1 = A1()  
    a = A()  

    a1.name1 = "a1"  
    a.name1 = "a" 

A是新式類添加了__slots__ 屬性,所以只允許添加 name age
A1經典類__slots__ 屬性沒用

執行結果:
     Traceback (most recent call last):  
       File "t.py", line 13, in <module>  
         a.name1 = "a"  
     AttributeError: 'A' object has no attribute 'name1'  
所以a.name是會出錯的

通常每一個實例都會有一個__dict__屬性,用來記錄實例中所有的屬性和方法,也是通過這個字典,可以讓實例綁定任意的屬性。
__slots__屬性作用就是,當類C有比較少的變量,而且擁有__slots__屬性時,類C的實例就沒有__dict__屬性,而是把變量的值存在一個固定的地方。如果試圖訪問一個__slots__中沒有的屬性,實例就會報錯。
這樣操作的好處:__slots__屬性雖然令實例失去了綁定任意屬性的便利,但是因爲每一個實例沒有__dict__屬性,卻能有效節省每一個實例的內存消耗,有利於生成小而精幹的實例。

4. 新式類增加了getattribute方法

#cat test4.py
class A(object):    
    def __getattribute__(self, *args, **kwargs):    
        print "A.__getattribute__"  

class A1():    
    def __getattribute__(self, *args, **kwargs):    
        print "A1.__getattribute__"  

a1 = A1()  
a = A()  

a.test  
print "========="  
a1.test

A.__getattribute__  
=========  
Traceback (most recent call last):  
    File "t.py", line 18, in <module>  
    a1.test  
AttributeError: A1 instance has no attribute 'test'  

A是新式類,每次通過實例訪問屬性,都會經過__getattribute__函數,
A1不會調用__getattribute__所以出錯了
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章