在Python中,類和 OOP 都不是平常編程所必需的。儘管它從一開始設計就是面向對象的,而且結構上支持 OOP,但 Python 沒有限定或要求你在你的應用中寫 OOP 的代碼。
不過!不學面向對象能夠麼?嘿嘿!那是必須的不能夠!python
• 類(Class):程序員
用來描述具備相同的屬性和方法的對象的集合。它定義了該集合中每一個對象所共有的屬性和方法。對象是類的實例。 編程
• 方法 :數據結構
類中定義的函數。python2.7
• 類變量 :ide
類變量在整個實例化的對象中是公用的。類變量定義在類中且在函數體以外。類變量一般不做爲實例變量使用。函數
• 實例變量:oop
定義在方法中的變量,只做用於當前實例ui
• 數據成員:url
類變量或者實例變量,用於處理類及其實例對象的相關的數據。
• 方法重寫:
若是從父類繼承的方法不能知足子類的需求,能夠對其進行改寫,這個過程叫方法的覆蓋(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
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 時:
事實上,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從2.2開始,引入了 new style class(新式類)
Python 2.x中默認都是經典類,只有顯式繼承了object纔是新式類
Python 3.x中默認都是新式類,沒必要顯式的繼承object
新式類跟經典類的差異主要是如下幾點:
• 新式類對象能夠直接經過class屬性獲取自身類型:type
• 繼承搜索的順序發生了改變
• 新式類增長了slots內置屬性, 能夠把實例屬性的種類鎖定到slots規定的範圍之中
• 新式類增長了getattribute方法
#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'>。
經典類多繼承屬性搜索順序: 先深刻繼承樹左側,再返回,開始找右側
新式類多繼承屬性搜索順序: 先水平搜索,而後再向上移動
#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。它是一種深度優先查找方式。
好比只容許對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__屬性,卻能有效節省每個實例的內存消耗,有利於生成小而精幹的實例。
#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__因此出錯了