0x00 is與==
==運(yùn)算符是比較兩個(gè)對(duì)象的內(nèi)容是否相等,默認(rèn)情況是調(diào)用對(duì)象的__eq__方法進(jìn)行比較;而is是比較兩個(gè)對(duì)象是否一樣,它比較的兩個(gè)對(duì)象的id,即它們的內(nèi)存地址是否相同。
>>> a = [1,2,3] >>> b = [1,2,3] >>> a == b True # a和b是否是同一個(gè)對(duì)象 >>> a is b False # a和b的地址其實(shí)是不一樣的 >>> id(a) 4498717128 >>> id(b) 4446861832
在比較時(shí)但也有例外。Python對(duì)一些常用的值進(jìn)行緩存優(yōu)化,例如在區(qū)間[-5,256]的整數(shù),它們?cè)趧?chuàng)建時(shí),無論創(chuàng)建多少個(gè)對(duì)象,它們的id是一樣的,即它們?cè)诘讓又兄槐4嬉环輧?nèi)存。
>>> a = -5 >>> b = -5 >>> a == b True >>> a is b True >>> a = -6 >>> b = -6 >>> a == b True >>> a is b False
對(duì)一些短的字符串也是如此,因此并不是所有字符串都會(huì)創(chuàng)建新的實(shí)例
>>> a='123' >>> b='123' >>> a==b True >>> a is b True >>> id(a) 4446903800 >>> id(b) 4446903800 >>> x = 'long char' >>> y = 'long char' >>> x == y True >>> x is y False
0x01 __repr__與__str__
每個(gè)類都應(yīng)該提供一個(gè)__repr__方法。__repr__方法和__str__方法有什么不一樣呢?
簡(jiǎn)單的說,__repr__可以反映一個(gè)對(duì)象的類型以及包含的內(nèi)容,而__str__主要是用于打印一個(gè)對(duì)象的內(nèi)容。例如看一下Python中的日期類datetime
import datetime >>> today = datetime.date.today() >>> today datetime.date(2019, 7, 7) >>> print(today) 2019-07-07 >>> str(today) '2019-07-07' >>> repr(today) 'datetime.date(2019, 7, 7)'
__str__在字符串連接,打印等操作會(huì)用到,而__repr__主要是面向開發(fā)者,它能反饋的信息比較多,例如在交互環(huán)境下輸入today這個(gè)變量會(huì)打印出datetime.date(2019, 7, 7),不僅可以看出today代表的是今天的日期信息,還可以看出它的類型信息。更重要的是你可以直接復(fù)制這段打印出來的信息,直接構(gòu)造一個(gè)“相同”的對(duì)象出來。
例如
>>> now = datetime.date(2019, 7, 7) >>> now datetime.date(2019, 7, 7)
0x02 對(duì)象復(fù)制
對(duì)象的復(fù)制或說對(duì)象拷貝可以分為淺拷貝和深拷貝。
淺拷貝與深拷貝
我們通過代碼來說明,就很好理解
如果要拷貝的對(duì)象是基本數(shù)據(jù)類型,那么深拷貝和淺拷貝的區(qū)別不是很大。
>>> a = [1,2,3] >>> b = list(a) >>> a[1]=200 >>> a [1, 200, 3] >>> b [1, 2, 3]
修改a中的元素并不會(huì)影響到b
但如果要拷貝的對(duì)象包含了另一個(gè)對(duì)象,那么就要考慮深拷貝和淺拷貝的問題了。
>>> a = [[1,2,3],[4,5,6],[7,8,9]] >>> b = list(a) >>> a == b True >>> a is b False
這里有一個(gè)列表a,里面有三個(gè)子列表,即列表里包含的是對(duì)象。
我們使用list工廠方法創(chuàng)建了一個(gè)a的拷貝b,這個(gè)b就是a的淺拷貝,為什么呢?
>>> a[1][2]='x' >>> a [[1, 2, 3], [4, 5, 'x'], [7, 8, 9]] >>> b [[1, 2, 3], [4, 5, 'x'], [7, 8, 9]]
把a(bǔ)[1][2]的元素修改成了x,這時(shí)候b列表中也響應(yīng)了相同的修改。所以這是淺拷貝,因?yàn)闆]有把子對(duì)象進(jìn)行拷貝,只是拷貝了指向子對(duì)象的引用。
知道淺拷貝,那么深拷貝就很好理解了。執(zhí)行拷貝之后,拷貝對(duì)象和原對(duì)象是完全獨(dú)立的,修改任何一個(gè)對(duì)象都不會(huì)影響到另一個(gè)對(duì)象
如何深拷貝一個(gè)對(duì)象
這時(shí)候就需要copy模塊了,該模塊有兩個(gè)重要的方法deepcopy和copy。不錯(cuò),就是分別代表深拷貝和淺拷貝。
>>> import copy >>> a = [[1,2,3],[4,5,6],[7,8,9]] >>> b = copy.deepcopy(a) >>> a [[1, 2, 3], [4, 5, 6], [7, 8, 9]] >>> b [[1, 2, 3], [4, 5, 6], [7, 8, 9]] >>> a[1][2]='change' >>> a [[1, 2, 3], [4, 5, 'change'], [7, 8, 9]] >>> b [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
執(zhí)行深拷貝之后,對(duì)a的修改并不會(huì)影響到b。
0x03 Abstract Base Classes(ABC)
抽象基類的使用
為了說明為什么要使用ABC,我們先看下不使用ABC的情況
# 定義了基類Base >>> class Base: def foo(self): raise NotImplemented def bar(self): raise NotImplemented # 定義實(shí)現(xiàn)類 >>> class Concrete(Base): def foo(self): print('called foo') # 實(shí)現(xiàn)類并沒有實(shí)現(xiàn)bar方法 >>> c = Concrete() >>> c.foo() called foo >>> c.bar() Traceback (most recent call last): File "", line 1, in c.bar() File " ", line 5, in bar raise NotImplemented TypeError: exceptions must derive from BaseException
可以看到?jīng)]有實(shí)現(xiàn)基類bar方法的Concrete類,依然可以使用,并沒有在第一時(shí)間拋出錯(cuò)誤。
要解決這個(gè)問題,就要用到我們abc模塊了。
from abc import ABC # 定義基類,繼承于ABC >>> class Base(ABC): @abstractmethod def foo(self): pass @abstractmethod def bar(self): pass >>> class Concrete(Base): def foo(self): print("called foo") # 實(shí)現(xiàn)類并沒有實(shí)現(xiàn)bar方法 >>> c = Concrete() Traceback (most recent call last): File "", line 1, in c = Concrete() TypeError: Can't instantiate abstract class Concrete with abstract methods bar
可以看到,在使用Concrete構(gòu)造方法的時(shí)候,就立即拋出TypeError了。
0x04 使用namedtuple的好處
關(guān)于namedtuple的用法在前面的文章《如何在Python中表示一個(gè)對(duì)象》 也有提到。
簡(jiǎn)單的說namedtuple是一個(gè)可以命名的tuple,他是對(duì)tuple的擴(kuò)展,它有跟tuple一樣不可變的屬性。
對(duì)于一些數(shù)據(jù)類的定義,namedtuple使用起來非常方便
>>> from collections import namedtuple >>> Point = namedtuple('Point','x y z') >>> p = Point(1,3,5) >>> p Point(x=1, y=3, z=5) >>> p.x 1 >>> p.y = 3.5 AttributeError: can't set attribute # 可以看出通過namedtuple定義對(duì)象,就是一個(gè)class類型的 >>> type(p)
還可以使用它內(nèi)部的一些工具方法,在實(shí)際的編碼當(dāng)中也是非常實(shí)用的。
# 轉(zhuǎn)化為dict >>> p._asdict() OrderedDict([('x', 1), ('y', 3), ('z', 5)]) # 更新或替換某個(gè)屬性值 >>> p._replace(x=111) Point(x=111, y=3, z=5) # 使用_make創(chuàng)建新對(duì)象 >>> Point._make([333,666,999]) Point(x=333, y=666, z=999)
0x05 類變量和實(shí)例變量
Python中對(duì)象的屬性類型有實(shí)例變量和類變量。
類變量是屬于類的,它存儲(chǔ)在“類的內(nèi)存空間”里,并能夠被它的各個(gè)實(shí)例對(duì)象共享。而實(shí)例變量是屬于某個(gè)特定實(shí)例的,它不在“類的內(nèi)存空間”中,它是獨(dú)立于各個(gè)實(shí)例存在的。
>>> class Cat: num_legs = 4 >>> class Cat: num_legs = 4 def __init__(self,name): self.name = name >>> tom = Cat('tom') >>> jack = Cat('jack') >>> tom.name,jack.name ('tom', 'jack') >>> tom.num_legs,jack.num_legs (4, 4) >>> Cat.num_legs 4
這里定義了一個(gè)貓類,它有一個(gè)實(shí)例變量name,還有一個(gè)類變量num_legs,這個(gè)是各個(gè)實(shí)例共享的。
如果對(duì)類變量進(jìn)行,那么其它實(shí)例也會(huì)同步修改。而對(duì)某個(gè)實(shí)例對(duì)修改,并不會(huì)影響都類變量。
>>> Cat.num_legs = 6 >>> tom.num_legs,jack.num_legs (6, 6) >>> tom.num_legs = 2 >>> tom.num_legs,jack.num_legs (2, 6) >>> Cat.num_legs 6
tom.num_legs = 2這個(gè)語句都作用其實(shí)對(duì)tom這個(gè)實(shí)例增加了一個(gè)屬性,只不過這個(gè)屬性名稱跟類屬性的名稱是一致的。
0x06 實(shí)例方法、類方法和靜態(tài)方法
為了更好區(qū)分,我們還是來看代碼
>>> class MyClass: def method(self): print(f"instance method at {self}" ) @classmethod def classmethod(cls): print(f'classmethod at {cls}') @staticmethod def staticmethod(): print('staticmethod') >>> mc = MyClass() >>> mc.method<__main__.MyClass object at 0x10c280b00>> >>> mc.classmethod > >>> mc.staticmethod
可以看到在MyClass中分別定義實(shí)例方法(method)、類方法(classmethod)和靜態(tài)方法(staticmethod)
在Python中一切都是對(duì)象,所以我打印來一下各個(gè)方法的__repr__輸出。
對(duì)于實(shí)例方法method是綁定在MyClass的具體實(shí)現(xiàn)對(duì)象中的,而類方法classmethod是綁定在MyClass中的,而靜態(tài)方法staticmethod既不綁定在實(shí)例里,也不綁定在類中,它就是一個(gè)function對(duì)象實(shí)例方法的調(diào)用,需要傳遞一個(gè)實(shí)例對(duì)象到實(shí)例方法中,以下兩種方法的調(diào)用是等價(jià)的。
>>> mc.method() instance method at <__main__.MyClass object at 0x10910ada0> >>> MyClass.method(mc) instance method at <__main__.MyClass object at 0x10910ada0>
類方法的調(diào)用和靜態(tài)方法的調(diào)用都是使用ClassName.methodName()的方式。
>>> MyClass.classmethod() classmethod at>>> MyClass.staticmethod() staticmethod
類方法和靜態(tài)方法有什么區(qū)別呢?
- 首先在前面可以看到類方法和靜態(tài)方法的對(duì)象是不一樣的,一個(gè)是bound method,一個(gè)是function。
- 其次類方法可以訪問到類對(duì)象MyClass,而靜態(tài)方法不能。
- 最后靜態(tài)方法其實(shí)跟一個(gè)普通的function對(duì)象一樣,只不過它是屬于類命名空間的。
0x07 總結(jié)一下
本文主要對(duì)Python中一些常見的面向?qū)ο蟮南嚓P(guān)的一些特性進(jìn)行了說明。包括對(duì)象的比較、輸出、拷貝等操作,以及推薦使用namedtuple定義數(shù)據(jù)類。最后對(duì)類變量和實(shí)例變量以及類方法、實(shí)例方法和靜態(tài)方法的不同作了分析。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
