深入理解python面向对象_转:Python3 面向对象,较为深入的两个理解
一,
1. 類的聲明和創(chuàng)建
對于 Python 函數(shù)來說,聲明與定義類沒什么區(qū)別,因?yàn)樗麄兪峭瑫r進(jìn)行的,定義(類體)緊跟在聲明(含 class 關(guān)鍵字的頭行[header line])和可選(但總是推薦使用)的文檔字符串后面。同時,所有的方法也必須同時被定義。
請注意 Python 并不支持純虛函數(shù)(像 C++)或者抽象方法(如在 JAVA 中),這些都強(qiáng)制程序員在子類中定義方法。作為替代方法,你可以簡單地在基類方法中引發(fā) NotImplementedError 異常,這樣可以獲得類似的效果。
2. 有關(guān)類的屬性
(1)查看類的屬性
要知道一個類有哪些屬性,有兩種方法。最簡單的是使用 dir()內(nèi)建函數(shù)(也可以查看實(shí)例屬性)。另外是通過訪問類的字典屬性__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)是相應(yīng)的屬性對象的數(shù)據(jù)值。
(2)類的特殊屬性
C.__name__????? 類C的名字(字符串)
C.__doc__?????? 類C的文檔字符串
C.__bases__???? 類C的所有父類構(gòu)成的元組
C.__dict__????? 類C的屬性
C.__module__??? 類C定義所在的模塊(1.5 版本新增)
C.__class__???? 實(shí)例C對應(yīng)的類(僅新式類中)
1
2
3
4
5
6
3. 對象
(1)Understanding __new__ and __init__
Understanding __new__ and __init__
(2)__del__()方法
有一個相應(yīng)的特殊解構(gòu)器(destructor)方法名為__del__()。然而,由于 Python 具有垃圾對象回收機(jī)制(靠引用計數(shù)),這個函數(shù)要直到該實(shí)例對象所有的引用都被清除掉后才會執(zhí)行。
Python 中的解構(gòu)器是在實(shí)例釋放前提供特殊處理功能的方法,它們通常沒有被實(shí)現(xiàn),因?yàn)閷?shí)例很少被顯式釋放。
要注意,解構(gòu)器只能被調(diào)用一次,一旦引用計數(shù)為 0,則對象就被清除了。
總結(jié):
不要忘記首先調(diào)用父類的__del__()。
調(diào)用 del x 不表示調(diào)用了 x.__del__() —–它僅僅是減少 x 的引用計數(shù)。
如果你有一個循環(huán)引用或其它的原因,讓一個實(shí)例的引用逗留不去, 該對象的__del__()可能永遠(yuǎn)不會被執(zhí)行。
__del__()未捕獲的異常會被忽略掉 (因?yàn)橐恍┰赺_del__()用到的變量或許已經(jīng)被刪除了)。
不要在__del__()中干與實(shí)例沒任何關(guān)系的事情。
除非你知道你正在干什么,否則不要去實(shí)現(xiàn)__del__()。
如果你定義了__del__(),并且實(shí)例是某個循環(huán)的一部分,垃圾回收器將不會終止這個循環(huán)——你需要自已顯式調(diào)用 del。
(3)跟蹤對象
Python 沒有提供任何內(nèi)部機(jī)制來跟蹤一個類有多少個實(shí)例被創(chuàng)建了,或者記錄這些實(shí)例是些什么東西。如果需要這些功能,你可以顯式加入一些代碼到類定義或者_(dá)_init__()和__del__()中去。最好的方式是使用一個靜態(tài)成員來記錄實(shí)例的個數(shù)。 靠保存它們的引用來跟蹤實(shí)例對象是很危險的,因?yàn)槟惚仨毢侠砉芾磉@些引用,不然,你的引用可能沒辦法釋放(因?yàn)檫€有其它的引用)!
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)類、實(shí)例的其它內(nèi)建函數(shù)
issubclass() 布爾函數(shù)判斷一個類是另一個類的子類或子孫類。它有如下語法:issubclass(sub, sup)
isinstance() 布爾函數(shù)在判定一個對象是否是另一個給定類的實(shí)例時,非常有用。它有如下語法:isinstance(obj1, obj2)
hasattr(), getattr(),setattr(), delattr()這些函數(shù)顧名思義,不做解釋。
4. 靜態(tài)方法和類方法
有兩種方式聲明靜態(tài)方法和類方法:
使用staticmethod()和 classmethod()內(nèi)建函數(shù)
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
靜態(tài)方法和類方法的區(qū)別:
靜態(tài)方法沒有cls參數(shù),所以它既不能訪問實(shí)例變量,也不能訪問類變量。
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()函數(shù)在子類中調(diào)用.
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__()方法后,如果你想調(diào)用父類的此方法,你必須顯示調(diào)用!Python默認(rèn)不會幫我們做這件事。
(3)多重繼承
方法解釋順序(MRO)
經(jīng)典類采用的是深度優(yōu)先算法(python2.2之前),而新式類采用的是廣度優(yōu)先算法。因?yàn)樵谛率筋愔惺褂蒙疃葍?yōu)先,會出現(xiàn)菱形效應(yīng)。
假設(shè)我們有如下繼承結(jié)構(gòu)的類:
這里寫圖片描述
左邊為經(jīng)典類情況下,右邊為新式類情況下。在新式類下B,C都繼承自object類。
在新式類(右邊繼承結(jié)構(gòu))中采用舊的深度優(yōu)先算法,假設(shè)在D的實(shí)例d中調(diào)用foo()方法,對于此方法的搜索順序是D->B->A->C;采用廣度優(yōu)先算法,搜索順序?yàn)镈->B->C-A。因?yàn)镈繼承了B、C,多數(shù)情況下我們更希望首先搜索的是C而不是A,因?yàn)榧僭O(shè)A、C中都有foo()方法時,你可能覺得A中的foo()方法太過通用了。很典型的就是__init__()方法。
(4)從標(biāo)準(zhǔn)類派生
比如你想從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
我們派生了一個可以自動四舍五入到兩位小數(shù)的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中有很多特殊方法,它們是以__開頭和結(jié)尾的。使用它們可以實(shí)現(xiàn):
模擬標(biāo)準(zhǔn)類型
重載操作符
1
2
3
4
5
6
7. 私有化
Python 為類元素(屬性和方法)的私有性提供初步的形式。由雙下劃線開始的屬性在運(yùn)行時被“混淆”(mixin),所以直接訪問是不允許的。
混淆會在名字前面加下劃線和類名。比如,以例NumStr類中的 self.__num 屬性為例,被“混淆”后,用于訪問這個數(shù)據(jù)值的標(biāo)識就變成了self._NumStr__num。把類名加上后形成的新的“混淆”結(jié)果將可以防止在祖先類或子孫類中的同名沖突。
8. 包裝和授權(quán)
(1)包裝(wrapping)
定義:
對一個已存在的對象進(jìn)行包裝,不管它是數(shù)據(jù)類型,還是一段代碼,可以是對一個已存在的對象,增加新的,刪除不要的,或者修改其它已存在的功能。你可以包裝任何類型作為一個類的核心成員,以使新對象的行為模仿你想要的數(shù)據(jù)類型中已存在的行為,并且去掉你不希望存在的行為。
包裝類:
你可以包裝類,但是實(shí)際上沒有必要。因?yàn)槟阃耆梢酝ㄟ^派生實(shí)現(xiàn)相同的效果。
(2)授權(quán)
簡介:
授權(quán)是包裝的一個特性,采用已存在的功能以達(dá)到最大限度的代碼重用。包裝一個類型通常是對已存在的類型的一些定制。授權(quán)的過程,即是所有更新的功能都是由新類的某部分來處理,但已存在的功能就授權(quán)給已存在的對象的默認(rèn)屬性。
實(shí)現(xiàn)授權(quán):
實(shí)現(xiàn)授權(quán)的關(guān)鍵點(diǎn)就是覆蓋__getattr__()方法,在代碼中包含一個對 getattr()內(nèi)建函數(shù)的調(diào)用。特別地,調(diào)用 getattr()以得到默認(rèn)對象屬性(數(shù)據(jù)屬性或者方法)并返回它以便訪問或調(diào)用。特殊方法__getattr__()的工作方式是, 當(dāng)搜索一個屬性時, 任何局部對象首先被找到 (定制的對象)。如果搜索失敗了,則__getattr__()會被調(diào)用,然后調(diào)用 getattr()得到一個對象的默認(rèn)行為。
我們來實(shí)現(xiàn)一個包裝文件對象的例子:
>>> 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()函數(shù)返回的文件對象。改寫了write()方法。當(dāng)使用包裝后的類實(shí)例化的對象調(diào)用write()方法時,使用的是修改的方法;當(dāng)調(diào)用close()方法時,因?yàn)槲覀儾]有改動此方法,則授權(quán)給原始文件對象,調(diào)用它的close()方法。
9. 新式類的高級特性
(1)工廠函數(shù)
在python中,所有的內(nèi)建轉(zhuǎn)換函數(shù)都是工廠函數(shù)。當(dāng)這些函數(shù)被調(diào)用時,實(shí)際上是對相應(yīng)的類型實(shí)例化。
類型測試:
使用
isinstance(obj, int)
1
也可以使用
isinstance(obj, (int, bool))
1
檢測obj是否是int或者bool類型。
但要注意:盡管 isinstance()很靈活,但它沒有執(zhí)行“嚴(yán)格匹配”比較—-如果 obj 是一個給定類型的實(shí)例或其子類的實(shí)例,也會返回 True。但如果想進(jìn)行嚴(yán)格匹配,你仍然需要使用 is 操作符:
type(obj) is int
1
(2)__slots__類屬性
__dict__屬性跟蹤所有實(shí)例屬性,以字典格式存儲(屬性名為key,屬性值為value)。字典會占據(jù)大量內(nèi)存,如果你有一個屬性數(shù)量很少的類,但有很多實(shí)例,那么正好是這種情況。為內(nèi)存上的考慮,用戶現(xiàn)在可以使用__slots__屬性來替代__dict__。
__slots__是一個類變量,由一序列型對象組成,由所有合法標(biāo)識構(gòu)成的實(shí)例屬性的集合來表示。它可以是一個列表,元組或可迭代對象。也可以是標(biāo)識實(shí)例能擁有的唯一的屬性的簡單字符串。任何試圖創(chuàng)建一個其名不在__slots__中的名字的實(shí)例屬性都將導(dǎo)致 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
這種特性的主要目的是節(jié)約內(nèi)存。其副作用是某種類型的”安全”,它能防止用戶隨心所欲的動態(tài)增加實(shí)例屬性。帶__slots__屬性的類定義不會存在__dict__了。
(3)特殊方法__getattribute__()
請注意,這個方法不是上面我們在授權(quán)中提到的__getattr__()方法。
當(dāng)有屬性被訪問時,不管這個屬性會不會被找到,__getattribute__()函數(shù)都會被調(diào)用。
如果類同時定義了__getattribute__()及__getattr__()方法,除非明確從__getattribute__()調(diào)用,或__getattribute__()引發(fā)了 AttributeError 異常,否則后者不會被調(diào)用。
如果你將要在__getattribute__()中訪問這個類或其祖先類的屬性,請務(wù)必小心。因?yàn)槠鋵?shí)你是在__getattribute__()中調(diào)用__getattribute__(),你將會進(jìn)入無窮遞歸。
(4)描述符
關(guān)于描述符,請移步這里:Python描述符
(5)元類:Metaclasses 和__metaclass__
在python中,類其實(shí)也是對象,它由元類創(chuàng)建。典型的應(yīng)用場景是:ORM。這里不詳細(xì)展開,你可以參見:
深刻理解Python中的元類(metaclass)
二,
Python中至少有三種比較常見的方法類型,即實(shí)例方法,類方法、靜態(tài)方法。它們是如何定義的呢?如何調(diào)用的呢?它們又有何區(qū)別和作用呢?且看下文。
首先,這三種方法都定義在類中。下面我先簡單說一下怎么定義和調(diào)用的。(PS:實(shí)例對象的權(quán)限最大。)
實(shí)例方法
定義:第一個參數(shù)必須是實(shí)例對象,該參數(shù)名一般約定為“self”,通過它來傳遞實(shí)例的屬性和方法(也可以傳類的屬性和方法);
調(diào)用:只能由實(shí)例對象調(diào)用。
類方法
定義:使用裝飾器@classmethod。第一個參數(shù)必須是當(dāng)前類對象,該參數(shù)名一般約定為“cls”,通過它來傳遞類的屬性和方法(不能傳實(shí)例的屬性和方法);
調(diào)用:實(shí)例對象和類對象都可以調(diào)用。
靜態(tài)方法
定義:使用裝飾器@staticmethod。參數(shù)隨意,沒有“self”和“cls”參數(shù),但是方法體中不能使用類或?qū)嵗娜魏螌傩院头椒?#xff1b;
調(diào)用:實(shí)例對象和類對象都可以調(diào)用。
實(shí)例方法
簡而言之,實(shí)例方法就是類的實(shí)例能夠使用的方法。這里不做過多解釋。
類方法
使用裝飾器@classmethod。
原則上,類方法是將類本身作為對象進(jìn)行操作的方法。假設(shè)有個方法,且這個方法在邏輯上采用類本身作為對象來調(diào)用更合理,那么這個方法就可以定義為類方法。另外,如果需要繼承,也可以定義為類方法。
如下場景:
假設(shè)我有一個學(xué)生類和一個班級類,想要實(shí)現(xiàn)的功能為:
執(zhí)行班級人數(shù)增加的操作、獲得班級的總?cè)藬?shù);
學(xué)生類繼承自班級類,每實(shí)例化一個學(xué)生,班級人數(shù)都能增加;
最后,我想定義一些學(xué)生,獲得班級中的總?cè)藬?shù)。
思考:這個問題用類方法做比較合適,為什么?因?yàn)槲覍?shí)例化的是學(xué)生,但是如果我從學(xué)生這一個實(shí)例中獲得班級總?cè)藬?shù),在邏輯上顯然是不合理的。同時,如果想要獲得班級總?cè)藬?shù),如果生成一個班級的實(shí)例也是沒有必要的。
classClassTest(object):
__num =0
@classmethod
defaddNum(cls):
cls.__num += 1@classmethod
defgetNum(cls):
return cls.__num
#這里我用到魔術(shù)方法__new__,主要是為了在創(chuàng)建實(shí)例的時候調(diào)用累加方法。
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())
靜態(tài)方法
使用裝飾器@staticmethod。
靜態(tài)方法是類中的函數(shù),不需要實(shí)例。靜態(tài)方法主要是用來存放邏輯性的代碼,邏輯上屬于類,但是和類本身沒有關(guān)系,也就是說在靜態(tài)方法中,不會涉及到類中的屬性和方法的操作。可以理解為,靜態(tài)方法是個獨(dú)立的、單純的函數(shù),它僅僅托管于某個類的名稱空間中,便于使用和維護(hù)。
譬如,我想定義一個關(guān)于時間操作的類,其中有一個獲取當(dāng)前時間的函數(shù)。
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)
如上,使用了靜態(tài)方法(函數(shù)),然而方法體中并沒使用(也不能使用)類或?qū)嵗膶傩?或方法)。若要獲得當(dāng)前時間的字符串時,并不一定需要實(shí)例化對象,此時對于靜態(tài)方法而言,所在類更像是一種名稱空間。
其實(shí),我們也可以在類外面寫一個同樣的函數(shù)來做這些事,但是這樣做就打亂了邏輯關(guān)系,也會導(dǎo)致以后代碼維護(hù)困難。
以上就是我對Python的實(shí)例方法,類方法和靜態(tài)方法之間的區(qū)別和作用的簡要闡述。
總結(jié)
以上是生活随笔為你收集整理的深入理解python面向对象_转:Python3 面向对象,较为深入的两个理解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: colspan会影响内部单元格宽度失效_
- 下一篇: 用python随机生成5000个网址_使