深入理解python面向对象_转:Python3 面向对象,较为深入的两个理解
一,
1. 類的聲明和創建
對于 Python 函數來說,聲明與定義類沒什么區別,因為他們是同時進行的,定義(類體)緊跟在聲明(含 class 關鍵字的頭行[header line])和可選(但總是推薦使用)的文檔字符串后面。同時,所有的方法也必須同時被定義。
請注意 Python 并不支持純虛函數(像 C++)或者抽象方法(如在 JAVA 中),這些都強制程序員在子類中定義方法。作為替代方法,你可以簡單地在基類方法中引發 NotImplementedError 異常,這樣可以獲得類似的效果。
2. 有關類的屬性
(1)查看類的屬性
要知道一個類有哪些屬性,有兩種方法。最簡單的是使用 dir()內建函數(也可以查看實例屬性)。另外是通過訪問類的字典屬性__dict__,這是所有類都具備的特殊屬性之一。
python
>>> class HaHa:
"""Haha to you!"""
variable1 = "Good"
variable2 = "Nice"
def change(self):
self.variable1 = "Bad"
>>> dir(HaHa)
['__doc__', '__module__', 'change', 'variable1', 'variable2']
>>> HaHa.__dict__
{'variable1': 'Good', '__module__': '__main__', 'variable2': 'Nice', '__doc__': 'Haha to you!', 'change': }
1
2
3
4
5
6
7
8
9
10
11
dir()返回的僅是對象的屬性的一個名字列表, 而__dict__返回的是一個字典,它的鍵(keys)是屬性名,鍵值(values)是相應的屬性對象的數據值。
(2)類的特殊屬性
C.__name__????? 類C的名字(字符串)
C.__doc__?????? 類C的文檔字符串
C.__bases__???? 類C的所有父類構成的元組
C.__dict__????? 類C的屬性
C.__module__??? 類C定義所在的模塊(1.5 版本新增)
C.__class__???? 實例C對應的類(僅新式類中)
1
2
3
4
5
6
3. 對象
(1)Understanding __new__ and __init__
Understanding __new__ and __init__
(2)__del__()方法
有一個相應的特殊解構器(destructor)方法名為__del__()。然而,由于 Python 具有垃圾對象回收機制(靠引用計數),這個函數要直到該實例對象所有的引用都被清除掉后才會執行。
Python 中的解構器是在實例釋放前提供特殊處理功能的方法,它們通常沒有被實現,因為實例很少被顯式釋放。
要注意,解構器只能被調用一次,一旦引用計數為 0,則對象就被清除了。
總結:
不要忘記首先調用父類的__del__()。
調用 del x 不表示調用了 x.__del__() —–它僅僅是減少 x 的引用計數。
如果你有一個循環引用或其它的原因,讓一個實例的引用逗留不去, 該對象的__del__()可能永遠不會被執行。
__del__()未捕獲的異常會被忽略掉 (因為一些在__del__()用到的變量或許已經被刪除了)。
不要在__del__()中干與實例沒任何關系的事情。
除非你知道你正在干什么,否則不要去實現__del__()。
如果你定義了__del__(),并且實例是某個循環的一部分,垃圾回收器將不會終止這個循環——你需要自已顯式調用 del。
(3)跟蹤對象
Python 沒有提供任何內部機制來跟蹤一個類有多少個實例被創建了,或者記錄這些實例是些什么東西。如果需要這些功能,你可以顯式加入一些代碼到類定義或者__init__()和__del__()中去。最好的方式是使用一個靜態成員來記錄實例的個數。 靠保存它們的引用來跟蹤實例對象是很危險的,因為你必須合理管理這些引用,不然,你的引用可能沒辦法釋放(因為還有其它的引用)!
class InstCt(object):
count = 0 # count is class attr
def __init__(self): # increment count
InstCt.count += 1
def __del__(self): # decrement count
InstCt.count -= 1
def howMany(self): # return count
return InstCt.count
>>> a = InstTrack()
>>> b = InstTrack()
>>> b.howMany()
2
>>> a.howMany()
2
>>> del b
>>> a.howMany()
1
>>> del a
>>> InstTrack.count
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(4)類、實例的其它內建函數
issubclass() 布爾函數判斷一個類是另一個類的子類或子孫類。它有如下語法:issubclass(sub, sup)
isinstance() 布爾函數在判定一個對象是否是另一個給定類的實例時,非常有用。它有如下語法:isinstance(obj1, obj2)
hasattr(), getattr(),setattr(), delattr()這些函數顧名思義,不做解釋。
4. 靜態方法和類方法
有兩種方式聲明靜態方法和類方法:
使用staticmethod()和 classmethod()內建函數
class TestStaticMethod:
def foo():
print 'calling static method foo()'
foo = staticmethod(foo)
class TestClassMethod:
def foo(cls):
print 'calling class method foo()'
print 'foo() is part of class:', cls.__name__
foo = classmethod(foo)
1
2
3
4
5
6
7
8
9
10
使用裝飾器
class TestStaticMethod:
@staticmethod
def foo():
print 'calling static method foo()'
class TestClassMethod:
@classmethod
def foo(cls):
print 'calling class method foo()'
print 'foo() is part of class:', cls.__name__
1
2
3
4
5
6
7
8
9
10
靜態方法和類方法的區別:
靜態方法沒有cls參數,所以它既不能訪問實例變量,也不能訪問類變量。
5. 繼承
(1)__bases__類屬性
我們可以通過此屬性獲得父類的信息。語法:ClassName.__bases__
(2)方法覆蓋(overriding)
Code example :
>>> class Parent(object):
def foo(self):
print 'Parent foo'
>>> class Son(Parent):
def foo(self):? # 父類的foo方法被覆蓋
print 'Son foo'
>>> son = Son()
>>> son.foo()
Son foo
1
2
3
4
5
6
7
8
9
10
11
12
13
被覆蓋了的父類方法可以通過super()函數在子類中調用.
Code Example :
>>> class NewSon(Parent):
def foo(self):
super(NewSon, self).foo()
>>> new_son = NewSon()
>>> new_son.foo()
Parent foo
1
2
3
4
5
6
7
8
注意:
子類覆蓋父類的__init__()方法后,如果你想調用父類的此方法,你必須顯示調用!Python默認不會幫我們做這件事。
(3)多重繼承
方法解釋順序(MRO)
經典類采用的是深度優先算法(python2.2之前),而新式類采用的是廣度優先算法。因為在新式類中使用深度優先,會出現菱形效應。
假設我們有如下繼承結構的類:
這里寫圖片描述
左邊為經典類情況下,右邊為新式類情況下。在新式類下B,C都繼承自object類。
在新式類(右邊繼承結構)中采用舊的深度優先算法,假設在D的實例d中調用foo()方法,對于此方法的搜索順序是D->B->A->C;采用廣度優先算法,搜索順序為D->B->C-A。因為D繼承了B、C,多數情況下我們更希望首先搜索的是C而不是A,因為假設A、C中都有foo()方法時,你可能覺得A中的foo()方法太過通用了。很典型的就是__init__()方法。
(4)從標準類派生
比如你想從float類派生出一個子類,這都是很常見的需求。下面看兩個例子:
繼承float類
>>> class RoundFloat(float):
...???? def __new__(cls, val):
...???????????? return super(RoundFloat, cls).__new__(cls, round(val, 2))
...
>>> RoundFloat(1.5955)
1.6
1
2
3
4
5
6
我們派生了一個可以自動四舍五入到兩位小數的RoundFloat類。
繼承dict類
>>> class SortedKeyDict(dict):
...???? def keys(self):
...???????????? return sorted(super(SortedKeyDict, self).keys())
...
>>> dict1 = SortedKeyDict((('wang', 1), ('jiang', 2), ('guo', 3), ('han', 4)))
>>> dict1.keys()? # 排序了
['guo', 'han', 'jiang', 'wang']
>>> [key for key in dict1]? # 散列順序
['guo', 'jiang', 'wang', 'han']
1
2
3
4
5
6
7
8
9
6. 特殊方法定制類
Python中有很多特殊方法,它們是以__開頭和結尾的。使用它們可以實現:
模擬標準類型
重載操作符
1
2
3
4
5
6
7. 私有化
Python 為類元素(屬性和方法)的私有性提供初步的形式。由雙下劃線開始的屬性在運行時被“混淆”(mixin),所以直接訪問是不允許的。
混淆會在名字前面加下劃線和類名。比如,以例NumStr類中的 self.__num 屬性為例,被“混淆”后,用于訪問這個數據值的標識就變成了self._NumStr__num。把類名加上后形成的新的“混淆”結果將可以防止在祖先類或子孫類中的同名沖突。
8. 包裝和授權
(1)包裝(wrapping)
定義:
對一個已存在的對象進行包裝,不管它是數據類型,還是一段代碼,可以是對一個已存在的對象,增加新的,刪除不要的,或者修改其它已存在的功能。你可以包裝任何類型作為一個類的核心成員,以使新對象的行為模仿你想要的數據類型中已存在的行為,并且去掉你不希望存在的行為。
包裝類:
你可以包裝類,但是實際上沒有必要。因為你完全可以通過派生實現相同的效果。
(2)授權
簡介:
授權是包裝的一個特性,采用已存在的功能以達到最大限度的代碼重用。包裝一個類型通常是對已存在的類型的一些定制。授權的過程,即是所有更新的功能都是由新類的某部分來處理,但已存在的功能就授權給已存在的對象的默認屬性。
實現授權:
實現授權的關鍵點就是覆蓋__getattr__()方法,在代碼中包含一個對 getattr()內建函數的調用。特別地,調用 getattr()以得到默認對象屬性(數據屬性或者方法)并返回它以便訪問或調用。特殊方法__getattr__()的工作方式是, 當搜索一個屬性時, 任何局部對象首先被找到 (定制的對象)。如果搜索失敗了,則__getattr__()會被調用,然后調用 getattr()得到一個對象的默認行為。
我們來實現一個包裝文件對象的例子:
>>> class UpperFile(object):
...???? def __init__(self, fn, mode='r', buf=-1):
...???????????? self.file = open(fn, mode, buf)
...???? def __str__(self):
...???????????? return str(self.file)
...???? def __repr__(self):
...???????????? return '%s' % self.file
...???? def write(self, line):
...???????????? self.file.write(line.upper())
...???? def __getattr__(self, attr):
...???????????? return getattr(self.file, attr)
...
>>> f = UpperFile(r'C:\test.txt', 'w')
>>> f.write('abcde')
>>> f.close()
>>> f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
我們包裝了open()函數返回的文件對象。改寫了write()方法。當使用包裝后的類實例化的對象調用write()方法時,使用的是修改的方法;當調用close()方法時,因為我們并沒有改動此方法,則授權給原始文件對象,調用它的close()方法。
9. 新式類的高級特性
(1)工廠函數
在python中,所有的內建轉換函數都是工廠函數。當這些函數被調用時,實際上是對相應的類型實例化。
類型測試:
使用
isinstance(obj, int)
1
也可以使用
isinstance(obj, (int, bool))
1
檢測obj是否是int或者bool類型。
但要注意:盡管 isinstance()很靈活,但它沒有執行“嚴格匹配”比較—-如果 obj 是一個給定類型的實例或其子類的實例,也會返回 True。但如果想進行嚴格匹配,你仍然需要使用 is 操作符:
type(obj) is int
1
(2)__slots__類屬性
__dict__屬性跟蹤所有實例屬性,以字典格式存儲(屬性名為key,屬性值為value)。字典會占據大量內存,如果你有一個屬性數量很少的類,但有很多實例,那么正好是這種情況。為內存上的考慮,用戶現在可以使用__slots__屬性來替代__dict__。
__slots__是一個類變量,由一序列型對象組成,由所有合法標識構成的實例屬性的集合來表示。它可以是一個列表,元組或可迭代對象。也可以是標識實例能擁有的唯一的屬性的簡單字符串。任何試圖創建一個其名不在__slots__中的名字的實例屬性都將導致 AttributeError 異常
Code Example :
>>> class SlottedClass(object):
...???? __slots__ = ('foo', 'bar')
...
>>> c = SlottedClass()
>>> c.foo = 12
>>> c.xx = 12
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'SlottedClass' object has no attribute 'xx'
1
2
3
4
5
6
7
8
9
這種特性的主要目的是節約內存。其副作用是某種類型的”安全”,它能防止用戶隨心所欲的動態增加實例屬性。帶__slots__屬性的類定義不會存在__dict__了。
(3)特殊方法__getattribute__()
請注意,這個方法不是上面我們在授權中提到的__getattr__()方法。
當有屬性被訪問時,不管這個屬性會不會被找到,__getattribute__()函數都會被調用。
如果類同時定義了__getattribute__()及__getattr__()方法,除非明確從__getattribute__()調用,或__getattribute__()引發了 AttributeError 異常,否則后者不會被調用。
如果你將要在__getattribute__()中訪問這個類或其祖先類的屬性,請務必小心。因為其實你是在__getattribute__()中調用__getattribute__(),你將會進入無窮遞歸。
(4)描述符
關于描述符,請移步這里:Python描述符
(5)元類:Metaclasses 和__metaclass__
在python中,類其實也是對象,它由元類創建。典型的應用場景是:ORM。這里不詳細展開,你可以參見:
深刻理解Python中的元類(metaclass)
二,
Python中至少有三種比較常見的方法類型,即實例方法,類方法、靜態方法。它們是如何定義的呢?如何調用的呢?它們又有何區別和作用呢?且看下文。
首先,這三種方法都定義在類中。下面我先簡單說一下怎么定義和調用的。(PS:實例對象的權限最大。)
實例方法
定義:第一個參數必須是實例對象,該參數名一般約定為“self”,通過它來傳遞實例的屬性和方法(也可以傳類的屬性和方法);
調用:只能由實例對象調用。
類方法
定義:使用裝飾器@classmethod。第一個參數必須是當前類對象,該參數名一般約定為“cls”,通過它來傳遞類的屬性和方法(不能傳實例的屬性和方法);
調用:實例對象和類對象都可以調用。
靜態方法
定義:使用裝飾器@staticmethod。參數隨意,沒有“self”和“cls”參數,但是方法體中不能使用類或實例的任何屬性和方法;
調用:實例對象和類對象都可以調用。
實例方法
簡而言之,實例方法就是類的實例能夠使用的方法。這里不做過多解釋。
類方法
使用裝飾器@classmethod。
原則上,類方法是將類本身作為對象進行操作的方法。假設有個方法,且這個方法在邏輯上采用類本身作為對象來調用更合理,那么這個方法就可以定義為類方法。另外,如果需要繼承,也可以定義為類方法。
如下場景:
假設我有一個學生類和一個班級類,想要實現的功能為:
執行班級人數增加的操作、獲得班級的總人數;
學生類繼承自班級類,每實例化一個學生,班級人數都能增加;
最后,我想定義一些學生,獲得班級中的總人數。
思考:這個問題用類方法做比較合適,為什么?因為我實例化的是學生,但是如果我從學生這一個實例中獲得班級總人數,在邏輯上顯然是不合理的。同時,如果想要獲得班級總人數,如果生成一個班級的實例也是沒有必要的。
classClassTest(object):
__num =0
@classmethod
defaddNum(cls):
cls.__num += 1@classmethod
defgetNum(cls):
return cls.__num
#這里我用到魔術方法__new__,主要是為了在創建實例的時候調用累加方法。
def __new__(self):
ClassTest.addNum()
return super(ClassTest, self).__new__(self)
classStudent(ClassTest):
def __init__(self):
self.name = ''a =Student()
b =Student()
print(ClassTest.getNum())
靜態方法
使用裝飾器@staticmethod。
靜態方法是類中的函數,不需要實例。靜態方法主要是用來存放邏輯性的代碼,邏輯上屬于類,但是和類本身沒有關系,也就是說在靜態方法中,不會涉及到類中的屬性和方法的操作。可以理解為,靜態方法是個獨立的、單純的函數,它僅僅托管于某個類的名稱空間中,便于使用和維護。
譬如,我想定義一個關于時間操作的類,其中有一個獲取當前時間的函數。
importtime
classTimeTest(object):
def __init__(self, hour, minute, second):
self.hour =hour
self.minute =minute
self.second =second
@staticmethod
defshowTime():
return time.strftime("%H:%M:%S", time.localtime())
print(TimeTest.showTime())
t = TimeTest(2, 10, 10)
nowTime =t.showTime()
print(nowTime)
如上,使用了靜態方法(函數),然而方法體中并沒使用(也不能使用)類或實例的屬性(或方法)。若要獲得當前時間的字符串時,并不一定需要實例化對象,此時對于靜態方法而言,所在類更像是一種名稱空間。
其實,我們也可以在類外面寫一個同樣的函數來做這些事,但是這樣做就打亂了邏輯關系,也會導致以后代碼維護困難。
以上就是我對Python的實例方法,類方法和靜態方法之間的區別和作用的簡要闡述。
總結
以上是生活随笔為你收集整理的深入理解python面向对象_转:Python3 面向对象,较为深入的两个理解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: colspan会影响内部单元格宽度失效_
- 下一篇: 用python随机生成5000个网址_使