Python中面向對象你應該知道的事

0x00 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
複製代碼

0x01 __repr____str__

每一個類都應該提供一個__repr__方法。__repr__方法和__str__方法有什麼不同呢?工具

簡單的說,__repr__能夠反映一個對象的類型以及包含的內容,而__str__主要是用於打印一個對象的內容。例如看一下Python中的日期類datetimepost

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)
複製代碼

0x02 對象複製

對象的複製或說對象拷貝能夠分爲淺拷貝和深拷貝優化

淺拷貝與深拷貝

咱們經過代碼來講明,就很好理解ui

若是要拷貝的對象是基本數據類型,那麼深拷貝和淺拷貝的區別不是很大。編碼

>>> a = [1,2,3]
>>> b = list(a)
>>> a[1]=200
>>> a
[1, 200, 3]
>>> b
[1, 2, 3]
複製代碼

修改a中的元素並不會影響到bspa

但若是要拷貝的對象包含了另外一個對象,那麼就要考慮深拷貝和淺拷貝的問題了。

>>> 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模塊了,該模塊有兩個重要的方法deepcopycopy。不錯,就是分別表明深拷貝和淺拷貝。

>>> 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

0x03 Abstract Base Classes(ABC)

抽象基類的使用

爲了說明爲何要使用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了。

0x04 使用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)
複製代碼

0x05 類變量和實例變量

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這個實例增長了一個屬性,只不過這個屬性名稱跟類屬性的名稱是一致的。

0x06 實例方法、類方法和靜態方法

爲了更好區分,咱們仍是來看代碼

>>> 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對象同樣,只不過它是屬於類命名空間的。

0x07 總結一下

本文主要對Python中一些常見的面向對象的相關的一些特性進行了說明。包括對象的比較、輸出、拷貝等操做,以及推薦使用namedtuple定義數據類。最後對類變量和實例變量以及類方法、實例方法和靜態方法的不一樣做了分析。

0x08 學習資料

  • Python Tricks: A Buffet of Awesome Python Features ——Dan Bader
相關文章
相關標籤/搜索