is
與==
==
運算符是比較兩個對象的內容是否相等,默認狀況是調用對象的__eq__
方法進行比較;而is
是比較兩個對象是否同樣,它比較的兩個對象的id
,即它們的內存地址是否相同。shell
>>> a = [1,2,3]
>>> b = [1,2,3]
>>> a == b
True
# a和b是不是同一個對象
>>> a is b
False
# a和b的地址實際上是不同的
>>> id(a)
4498717128
>>> id(b)
4446861832
複製代碼
在比較時但也有例外。Python
對一些經常使用的值進行緩存優化,例如在區間[-5,256]的整數,它們在建立時,不管建立多少個對象,它們的id
是同樣的,即它們在底層中只保存一分內存。緩存
>>> a = -5
>>> b = -5
>>> a == b
True
>>> a is b
True
>>> a = -6
>>> b = -6
>>> a == b
True
>>> a is b
False
複製代碼
對一些短的字符串也是如此,所以並非全部字符串都會建立新的實例bash
>>> 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
複製代碼
__repr__
與__str__
每一個類都應該提供一個__repr__
方法。__repr__
方法和__str__
方法有什麼不同呢?工具
簡單的說,__repr__
能夠反映一個對象的類型以及包含的內容,而__str__
主要是用於打印一個對象的內容。例如看一下Python
中的日期類datetime
post
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__
在字符串鏈接,打印等操做會用到,而__repr__
主要是面向開發者,它能反饋的信息比較多,例如在交互環境下輸入today
這個變量會打印出datetime.date(2019, 7, 7)
,不只能夠看出today
表明的是今天的日期信息,還能夠看出它的類型信息。更重要的是你能夠直接複製這段打印出來的信息,直接構造一個「相同」的對象出來。 例如學習
>>> now = datetime.date(2019, 7, 7)
>>> now
datetime.date(2019, 7, 7)
複製代碼
對象的複製或說對象拷貝能夠分爲淺拷貝和深拷貝。優化
咱們經過代碼來講明,就很好理解ui
若是要拷貝的對象是基本數據類型,那麼深拷貝和淺拷貝的區別不是很大。編碼
>>> a = [1,2,3]
>>> b = list(a)
>>> a[1]=200
>>> a
[1, 200, 3]
>>> b
[1, 2, 3]
複製代碼
修改a
中的元素並不會影響到b
spa
但若是要拷貝的對象包含了另外一個對象,那麼就要考慮深拷貝和淺拷貝的問題了。
>>> a = [[1,2,3],[4,5,6],[7,8,9]]
>>> b = list(a)
>>> a == b
True
>>> a is b
False
複製代碼
這裏有一個列表a
,裏面有三個子列表,即列表裏包含的是對象。
咱們使用list
工廠方法建立了一個a
的拷貝b
,這個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[1][2]
的元素修改爲了x
,這時候b
列表中也響應了相同的修改。因此這是淺拷貝,由於沒有把子對象進行拷貝,只是拷貝了指向子對象的引用。
知道淺拷貝,那麼深拷貝就很好理解了。執行拷貝以後,拷貝對象和原對象是徹底獨立的,修改任何一個對象都不會影響到另外一個對象
這時候就須要copy
模塊了,該模塊有兩個重要的方法deepcopy
和copy
。不錯,就是分別表明深拷貝和淺拷貝。
>>> 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]]
複製代碼
執行深拷貝以後,對a
的修改並不會影響到b
。
抽象基類的使用
爲了說明爲何要使用ABC
,咱們先看下不使用ABC
的狀況
# 定義了基類Base
>>> class Base:
def foo(self):
raise NotImplemented
def bar(self):
raise NotImplemented
# 定義實現類
>>> class Concrete(Base):
def foo(self):
print('called foo')
# 實現類並無實現bar方法
>>> c = Concrete()
>>> c.foo()
called foo
>>> c.bar()
Traceback (most recent call last):
File "<pyshell#391>", line 1, in <module>
c.bar()
File "<pyshell#384>", line 5, in bar
raise NotImplemented
TypeError: exceptions must derive from BaseException
複製代碼
能夠看到沒有實現基類bar
方法的Concrete
類,依然可使用,並無在第一時間拋出錯誤。
要解決這個問題,就要用到咱們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")
# 實現類並無實現bar方法
>>> c = Concrete()
Traceback (most recent call last):
File "<pyshell#357>", line 1, in <module>
c = Concrete()
TypeError: Can't instantiate abstract class Concrete with abstract methods bar
複製代碼
能夠看到,在使用Concrete
構造方法的時候,就當即拋出TypeError
了。
namedtuple
的好處關於namedtuple
的用法在前面的文章《如何在Python中表示一個對象》 也有提到。 簡單的說namedtuple
是一個能夠命名的tuple
,他是對tuple
的擴展,它有跟tuple
同樣不可變的屬性。
對於一些數據類的定義,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定義對象,就是一個class類型的
>>> type(p)
<class '__main__.Point'>
複製代碼
還可使用它內部的一些工具方法,在實際的編碼當中也是很是實用的。
# 轉化爲dict
>>> p._asdict()
OrderedDict([('x', 1), ('y', 3), ('z', 5)])
# 更新或替換某個屬性值
>>> p._replace(x=111)
Point(x=111, y=3, z=5)
# 使用_make建立新對象
>>> Point._make([333,666,999])
Point(x=333, y=666, z=999)
複製代碼
Python
中對象的屬性類型有實例變量和類變量。
類變量是屬於類的,它存儲在「類的內存空間」裏,並可以被它的各個實例對象共享。而實例變量是屬於某個特定實例的,它不在「類的內存空間」中,它是獨立於各個實例存在的。
>>> 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
複製代碼
這裏定義了一個貓類,它有一個實例變量name
,還有一個類變量num_legs
,這個是各個實例共享的。
若是對類變量進行,那麼其它實例也會同步修改。而對某個實例對修改,並不會影響都類變量。
>>> 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
這個語句都做用其實對tom
這個實例增長了一個屬性,只不過這個屬性名稱跟類屬性的名稱是一致的。
爲了更好區分,咱們仍是來看代碼
>>> 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
<bound method MyClass.method of <__main__.MyClass object at 0x10c280b00>>
>>> mc.classmethod
<bound method MyClass.classmethod of <class '__main__.MyClass'>>
>>> mc.staticmethod
<function MyClass.staticmethod at 0x1090d4378>
複製代碼
能夠看到在MyClass
中分別定義實例方法(method
)、類方法(classmethod
)和靜態方法(staticmethod
)
在Python
中一切都是對象,因此我打印來一下各個方法的__repr__
輸出。
對於實例方法method
是綁定在MyClass
的具體實現對象中的,而類方法classmethod
是綁定在MyClass
中的,而靜態方法staticmethod
既不綁定在實例裏,也不綁定在類中,它就是一個function
對象
實例方法的調用,須要傳遞一個實例對象到實例方法中,如下兩種方法的調用是等價的。
>>> mc.method()
instance method at <__main__.MyClass object at 0x10910ada0>
>>> MyClass.method(mc)
instance method at <__main__.MyClass object at 0x10910ada0>
複製代碼
類方法的調用和靜態方法的調用都是使用ClassName.methodName()
的方式。
>>> MyClass.classmethod()
classmethod at <class '__main__.MyClass'>
>>> MyClass.staticmethod()
staticmethod
複製代碼
類方法和靜態方法有什麼區別呢?
bound method
,一個是function
。MyClass
,而靜態方法不能。function
對象同樣,只不過它是屬於類命名空間的。本文主要對Python
中一些常見的面向對象的相關的一些特性進行了說明。包括對象的比較、輸出、拷貝等操做,以及推薦使用namedtuple
定義數據類。最後對類變量和實例變量以及類方法、實例方法和靜態方法的不一樣做了分析。