[python 進階] 9. 符合Python風格的對象

文章目錄

    • 9.1 對象表示形式
    • 9.2 再談向量類
    • 9.3 備選構造方法
    • 9.4 classmethod與staticmethod
    • 9.5 格式化顯示
    • 9.6 可散列的Vector2d
    • 什么是可散列的數據類型
    • 9.6 可散列的Vector
    • 9.7 Python的私有屬性和“受保護的”屬性
    • 9.8 使用 __slots__ 類屬性節省空間

本章包含以下話題:

  • 支持用于生成對象其他表示形式的內置函數(如 repr()、bytes(),等等)
  • 使用一個類方法實現備選構造方法
  • 擴展內置的 format() 函數和 str.format() 方法使用的格式微語言
  • 實現只讀屬性
  • 把對象變為可散列的,以便在集合中及作為 dict 的鍵使用
  • 利用 _slots_ 節省內存。

我們將開發一個簡單的二維歐幾里得向量類型,在這個過程中涵蓋上述全部話題。
在實現這個類型的中間階段,我們會討論兩個概念:

  • 如何以及何時使用 @classmethod 和 @staticmethod 裝飾器
  • Python 的私有屬性和受保護屬性的用法、約定和局限
    我們從對象表示形式函數開始。

9.1 對象表示形式

每門面向對象的語言至少都有一種獲取對象的字符串表示形式的標準方式。Python 提供了
兩種方式。

  • repr()
    以便于開發者理解的方式返回對象的字符串表示形式。
  • str()
    以便于用戶理解的方式返回對象的字符串表示形式。

為了給對象提供其他的表示形式,還會用到另外兩個特殊方法:_bytes_ 和_format_。_bytes_ 方法與 _str_ 方法類似:bytes() 函數調用它獲取對象的字節序列表示形式。而 _format_ 方法會被內置的 format() 函數和 str.format() 方法調用,使用特殊的格式代碼顯示對象的字符串表示形式。
記住,在 Python 3 中,

  • _repr_、_str_ 和 _format_ 都必須返回 Unicode 字符串(str 類型)。
  • 只有_bytes_ 方法應該返回字節序列(bytes 類型)

9.2 再談向量類

from array import array
import mathclass Vector2d:typecode='d'# typecode是類屬性def __init__(self, x, y):self.x = xself.y = ydef __iter__(self):return (i for i in (self.x, self.y))def __repr__(self):class_name = type(self).__name__return '{}{!r},{!r}'.format(class_name, *self)def __str__(self):return str(tuple(self))def __bytes__(self): # 生成實例的二進制表示形式return (bytes([ord(self.typecode)]) + bytes(array(self.typecode, self)))def __eq__(self, other):return tuple(self) == tuple(other)def __abs__(self):return math.hypot(self.x, self.y) #模是 x 和 y 分量構成的直角三角形的斜邊長def __bool__(self):return bool(abs(self))v1 = Vector2d(3, 4)
print(v1.x, v1.y)
x, y = v1
print(x, y)
print(v1)
v1_clone = eval(repr(v1))
print(v1_clone == v1)
print(v1)
octets = bytes(v1)
print(octets)
print(abs(v1))

9.3 備選構造方法

我們可以把 Vector2d 實例轉換成字節序列了;同理,也應該能從字節序列轉換成Vector2d 實例。在標準庫中探索一番之后,我們發現 array.array 有個類方法.frombytes(2.9.1 節介紹過)正好符合需求。下面在 vector2d_v1.py(見示例 9-3)中為Vector2d 定義一個同名類方法。

@classmethod ?
def frombytes(cls, octets): ?typecode = chr(octets[0]) ?memv = memoryview(octets[1:]).cast(typecode) ?return cls(*memv) ?

? 類方法使用 classmethod 裝飾器修飾。
? 不用傳入 self 參數;相反,要通過 cls 傳入類本身。
? 從第一個字節中讀取 typecode。
? 使用傳入的 octets 字節序列創建一個 memoryview,然后使用 typecode 轉換。
2.9.2 節簡單介紹過 memoryview,說明了它的 .cast 方法。
? 拆包轉換后的 memoryview,得到構造方法所需的一對參數。

9.4 classmethod與staticmethod

先來看 classmethod。示例 9-3 展示了它的用法:定義操作類,而不是操作實例的方法。classmethod 改變了調用方法的方式,因此類方法的第一個參數是類本身,而不是實例。classmethod 最常見的用途是定義備選構造方法,例如示例 9-3 中的
frombytes。注意,frombytes 的最后一行使用 cls 參數構建了一個新實例,即cls(*memv)。按照約定,類方法的第一個參數名為 cls(但是 Python 不介意具體怎么命名)。
staticmethod 裝飾器也會改變方法的調用方式,但是第一個參數不是特殊的值。其實,靜態方法就是普通的函數,只是碰巧在類的定義體中,而不是在模塊層定義。

>>> class Demo:
...     @classmethod
...     def klassmeth(*args):
...             return args
...     @staticmethod
...     def statmeth(*args):
...             return args
... 
>>> Demo.klassmeth()
(<class '__main__.Demo'>,)
>>> Demo.statmeth()
()
>>> Demo.klassmeth('spam')
(<class '__main__.Demo'>, 'spam')

9.5 格式化顯示

內置的 format() 函數和 str.format() 方法把各個類型的格式化方式委托給相應的.format(format_spec) 方法。format_spec 是格式說明符,它是:format(my_obj, format_spec) 的第二個參數,或者str.format() 方法的格式字符串,{} 里代換字段中冒號后面的部分。
例如:

>>> br1 = 1/2.43
>>> br1
0.4115226337448559
>>> format(br1, '0.4f')
'0.4115'
>>> '1 BRL={rate:0.2f} USD'.format(rate=br1)
'1 BRL=0.41 USD'

格式規范微語言為一些內置類型提供了專用的表示代碼。比如,b 和 x 分別表示二進制和十六進制的 int 類型,f 表示小數形式的 float 類型,而 % 表示百分數形式:

>>> format(42,'b')
'101010'
>>> format(2/3, '.1%')
'66.7%'

下面是內置的 format() 函數和 str.format() 方法的幾個示例

>>> from datetime import datetime
>>> now= datetime.now()
>>> format(now, '%H:%M:%S')
'18:35:23'
>>> "Its now {:%I:%M %p}".format(now)
'Its now 06:35 PM'

如果類沒有定義 format 方法,從 object 繼承的方法會返回 str(my_object)。我
們為 Vector2d 類定義了 str 方法,因此可以這樣做:

>>> v1 = Vector2d(3, 4)
>>> format(v1)
'(3.0, 4.0)'

然而,如果傳入格式說明符,object.format 方法會拋出 TypeError:

>>> format(v1, '.3f')
Traceback (most recent call last):
...
TypeError: non-empty format string passed to object.__format__

我們將實現自己的微語言來解決這個問題。首先,假設用戶提供的格式說明符是用于格式
化向量中各個浮點數分量的。我們想達到的效果是:

>>> v1 = Vector2d(3, 4)
>>> format(v1)
'(3.0, 4.0)'
>>> format(v1, '.2f')
'(3.00, 4.00)'
>>> format(v1, '.3e')
'(3.000e+00, 4.000e+00)'

實現這種輸出的 format 方法如示例 9-5 所示。
示例 9-5 Vector2d._format_ 方法,第 1 版

# 在Vector2d類中定義
def __format__(self, fmt_spec=''):components = (format(c, fmt_spec) for c in self)  return '({}, {})'.format(*components) 

對極坐標來說,我們已經定義了計算模的 abs 方法,因此還要定義一個簡單的
angle 方法,使用 math.atan2() 函數計算角度。angle 方法的代碼如下:

# 在Vector2d類中定義
def angle(self):return math.atan2(self.y, self.x)

這樣便可以增強 format 方法,計算極坐標,如示例 9-6 所示。
示例 9-6 Vector2d.format 方法,第 2 版,現在能計算極坐標了

def __format__(self, fmt_spec=''):if fmt_spec.endswith('p'): fmt_spec = fmt_spec[:-1] coords = (abs(self), self.angle()) outer_fmt = '<{}, {}>' else:coords = self outer_fmt = '({}, {})' components = (format(c, fmt_spec) for c in coords) return outer_fmt.format(*components) 

9.6 可散列的Vector2d

按照定義,目前 Vector2d 實例是不可散列的,因此不能放入集合(set)中:

>>> v1 = Vector2d(3, 4)
>>> hash(v1)
Traceback (most recent call last):
...
TypeError: unhashable type: 'Vector2d'
>>> set([v1])
Traceback (most recent call last):
...
TypeError: unhashable type: 'Vector2d'

為了把 Vector2d 實例變成可散列的,必須使用 hash 方法(還需要 eq 方法,前面已經實現了)。

什么是可散列的數據類型

可散列的(hashable)
在散列值永不改變,而且如果 a == b,那么 hash(a) == hash(b) 也是 True 的情況下,如果對象既有 _hash_ 方法,也有 _eq_ 方法,那么這樣的對象稱為可散列的對象。在內置的類型中,大多數不可變的類型都是可散列的;但是,僅當元組的每一個元素都是可散列的時,元組才是可散列的。

  • 如果一個對象是可散列的,那么在這個對象的生命周期中,它的散列值是不變的,而且這個對象需要實現 _hash_() 方法。另外可散列對象還要有_eq_() 方法,這樣才能跟其他鍵做比較。如果兩個可散列對象是相等的,那么它們的散列值一定是一樣的……
  • 原子不可變數據類型(str、bytes 和數值類型)都是可散列類型,frozenset 也是可散列的,因為根據其定義,frozenset 里只能容納可散列類型。元組的話,只有當一個元組包含的所有元素都是可散列類型的情況下,它才是可散列的。

有這么一句話“Python 里所有的不可變類型都是可散列的”。這個說法其實是不準確的,比如雖然元組本身是不可變序列,它里面的元素可能是其他可變類型的引用。

一般來講用戶自定義的類型的對象都是可散列的,散列值就是它們的 id() 函數的返回值,所以所有這些對象在比較的時候都是不相等的。如果一個對象實現了 _eq_ 方法,并且在方法中用到了這個對象的內部狀態的話,那么只有當所有這些內部狀態都是不可變的情況下,這個對象才是可散列的。

9.6 可散列的Vector

from array import array
import mathclass Vector2d:typecode='d'# typecode是類屬性def __init__(self, x, y):self.__x = float(x) #使用兩個前導下劃線(尾部沒有下劃線,或者有一個下劃線),把屬性標記為私有self.__y = float(y)@property # @property 裝飾器把讀值方法標記為特性def x(self):return self.__x@propertydef y(self):return self.__ydef __iter__(self):return (i for i in (self.x, self.y))def __repr__(self):class_name = type(self).__name__return '{}{!r},{!r}'.format(class_name, *self)def __str__(self):return str(tuple(self))def __bytes__(self): # 生成實例的二進制表示形式return (bytes([ord(self.typecode)]) + bytes(array(self.typecode, self)))def __eq__(self, other):return tuple(self) == tuple(other)def __abs__(self):return math.hypot(self.x, self.y) #模是 x 和 y 分量構成的直角三角形的斜邊長def __bool__(self):return bool(abs(self))v1 = Vector2d(3, 4)

注意,我們讓這些向量不可變是有原因的,因為這樣才能實現 hash 方法。這個方法應該返回一個整數,理想情況下還要考慮對象屬性的散列值(eq 方法也要使用),因為相等的對象應該具有相同的散列值。
要想創建可散列的類型,不一定要實現特性,也不一定要保護實例屬性。只需正確地實現 hasheq 方法即可。但是,實例的散列值絕不應該變化,因此我們借機提到了只讀特性。
如果定義的類型有標量數值,可能還要實現 intfloat 方法(分別被 int()和 float() 構造函數調用),以便在某些情況下用于強制轉換類型。此外,還有用于支持內置的 complex() 構造函數的 complex 方法。Vector2d 或許應該提供
complex 方法。

9.7 Python的私有屬性和“受保護的”屬性

Python 不能像 Java 那樣使用 private 修飾符創建私有屬性,但是 Python 有個簡單的機制,能避免子類意外覆蓋“私有”屬性。
舉個例子。有人編寫了一個名為 Dog 的類,這個類的內部用到了 mood 實例屬性,但是沒有將其開放。現在,你創建了 Dog 類的子類:Beagle。如果你在毫不知情的情況下又創建了名為 mood 的實例屬性,那么在繼承的方法中就會把 Dog 類的 mood 屬性覆蓋掉。這是個難以調試的問題。
為了避免這種情況,如果以__mood 的形式(兩個前導下劃線,尾部沒有或最多有一個下劃線)命名實例屬性,Python 會把屬性名存入實例的__dict__ 屬性中,而且會在前面加上一個下劃線和類名。因此,對 Dog 類來說,__mood 會變成 _Dog__mood;對 Beagle類來說,會變成 _Beagle__mood。這個語言特性叫名稱改寫(name mangling)

示例 9-10 私有屬性的名稱會被“改寫”,在前面加上下劃線和類名
>>> v1 = Vector2d(3, 4)
>>> v1.__dict__
{'_Vector2d__y': 4.0, '_Vector2d__x': 3.0}
>>> v1._Vector2d__x
3.0

名稱改寫是一種安全措施,不能保證萬無一失:它的目的是避免意外訪問,不能防止故意做錯事。

9.8 使用 slots 類屬性節省空間

默認情況下,Python 在各個實例中名為 dict 的字典里存儲實例屬性。如 3.9.3 節所述,為了使用底層的散列表提升訪問速度,字典會消耗大量內存。如果要處理數百萬個屬性不多的實例,通過 slots 類屬性,能節省大量內存,方法是讓解釋器在元組中存儲實例屬性,而不用字典。
定義 slots 的方式是,創建一個類屬性,使用 slots 這個名字,并把它的值設為一個字符串構成的可迭代對象,其中各個元素表示各個實例屬性。我喜歡使用元組,因為這樣定義的 slots 中所含的信息不會變化。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/273788.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/273788.shtml
英文地址,請注明出處:http://en.pswp.cn/news/273788.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

android軟件獲取系統簽名

有時候有的功能必須要有系統簽名才能使用&#xff0c;例如調用系統自帶的Surface.screenShot方法時&#xff0c;就必須在androidManifest.xml里聲明android:sharedUserId"android.uid.system" 但是這個時候在編譯生成的apk很有可能無法安裝的情況 并且報這個錯誤&…

Python3中的可變與不可變類型

在描述變量是否是可變類型時&#xff0c;可變與否實際上說的是對變量進行“修改”時變量的內存地址是否會發生變化&#xff0c;而非值是否可變。在Python中&#xff0c;對不可變的變量進行“修改”實際上是重新賦值&#xff0c;對可變的變量進行修改才是真正的修改&#xff0c;…

python中帶*(單星號)的變量和**(雙星號)的變量

一、*args的使用方法 *args 用來將參數打包成tuple給函數體調用二、**kwargs的使用方法 **kwargs 打包關鍵字參數成dict給函數體調用注意點&#xff1a;參數arg、*args、**kwargs三個參數的位置必須是一定的。必須是(arg,*args,**kwargs)這個順序&#xff0c;否則程序會報錯。單…

百度知道回答的依賴注入

oC 或者 DI 或者 ...一大堆的縮寫詞不管是面向對象&#xff0c;還是面向過程&#xff0c;都需要分成許多的塊&#xff0c;然后由這些部件協同工作完成任務 要協同工作就會產生依賴&#xff0c;一個方法調用另一個方法&#xff0c;一個對象包含另一個對象 如果對象A包含對象B的話…

Django model中的 class Meta 詳解

參考 (1) https://www.cnblogs.com/tongchengbin/p/7670927.html

C\C++ 獲取當前路徑

C\C 獲取當前路徑 獲取當前工作目錄是使用函數&#xff1a;getcwd。cwd指的是“current working directory”&#xff0c;這樣就好記憶了。 函數說明&#xff1a; 函數原型&#xff1a;char* getcwd(char* buffer, int len); 參數&#xff1a;buffer是指將當前工作…

[python進階]11接口:從協議到抽象基類

本章討論的話題是接口&#xff1a;從鴨子類型的代表特征動態協議&#xff0c;到使接口更明確、能驗證實現是否符合規定的抽象基類&#xff08;Abstract Base Class&#xff0c;ABC&#xff09;。 首先&#xff0c;本章說明抽象基類的常見用途&#xff1a;實現接口時作為**超類(…

ie11瀏覽器不能顯示最新修改的程序,調試出現代碼邏輯錯誤卻依舊執行

1、問題&#xff1a;ie11瀏覽器不能顯示最新修改的程序&#xff0c;調試也不能&#xff0c;出現代碼邏輯錯誤卻依舊執行 2、百度解決方案&#xff1a;http://blog.163.com/wang_hj138126/blog/static/1408001062012631508444/ FireFox每次訪問頁面時檢查最新版本 2012-07-31 …

C# 基礎備忘錄

1. decimal 類型調用ToString()方法后沒把末尾的0去掉的解決辦法: 例子&#xff1a;decimal? money Convert.ToDecimal(10.8950);string moneyStrmoney.Value.ToString(); 結果在同一臺機子&#xff0c;兩個項目里面會出現兩個不同的結果。結果一&#xff1a;moneyStr"1…

[python進階]12.繼承的優缺點

本章探討繼承和子類化&#xff0c;重點是說明對 Python 而言尤為重要的兩個細節&#xff1a; 子類化內置類型的缺點多重繼承和方法解析順序 12.1 子類化內置類型很 12.2 多重繼承和方法解析

Android中用GridView實現九宮格的兩種方法(轉)

Android中用GridView實現九宮格的兩種方法http://blog.csdn.net/shakespeare001/article/details/7768455 1.傳統辦法&#xff1a;實現一個繼承BaseAdapter的 ImageAdapter package com.test; import android.app.Activity; import android.content.Context; import andro…

django框架中的模型

文章目錄關聯關系Many-to-one relationshipsMany-to-many relationshipsdjango學習——model中的get和filter方法的區別模型模型是您的數據唯一而且準確的信息來源。它包含您正在儲存的數據的重要字段和行為。一般來說&#xff0c;每一個模型都映射一個數據庫表。基礎&#xff…

虛擬主機TOMCAT配置

在tomcat中添加虛擬主機&#xff1a;   編輯"tomcat\conf\server.xml"&#xff0c;在"<Engine></Engine>"元素中新加子元素"<Host></Host>"&#xff0c;如下&#xff1a;  </Host>     <Host name&quo…

django框架中表單

參考官方文檔,太詳細了 (https://docs.djangoproject.com/zh-hans/2.1/topics/forms/)

鳥哥學習筆記六(基礎篇第十一章)

type:查看指令是否是bash內建指令 變量的設定規則 1. 變量與變量內容以一個等號『』來連結&#xff0c;如下所示&#xff1a; 『mynameVBird』 2. 等號兩邊不能直接接空格符&#xff0c;如下所示為錯誤&#xff1a; 『myname VBird』或『mynameVBird Tsai』3. 變量名稱只能…

django-models類索引外鍵時候的related_name屬性作用

其實可以就理解為,一對多關系拿對象的解決 可以把引用理解為主從關系 主引用從,即一對多 , 注意外鍵字段是放在多的一端的,比如一個班級class 有很多同學 students,那么就在students類里面設置class字段值是外鍵類型 從students拿class數據很好拿, studet.class就拿到了 但是從…

查找算法分析

參考&#xff1a; https://www.cnblogs.com/maybe2030/p/4715035.html#_label0

PPT設計里的小技巧

首先想說的是PPT設計&#xff0c;現在還是有很多人只把PPT當成一個存放文字和圖片的軟件&#xff0c;說的更直接點就是當是一個可以全屏放映內容的軟件。但是我想說的是PPT已經走向了設計類型的軟件&#xff0c;當Microsoft office Powerpoint2010正式版出來的時候這種感覺更盛…

PDFlib免費下載地址及詳細介紹手冊

PDFlib是一個用于創建PDF文檔的開發工具,也可直接在你的服務器端產生PDF輸出, 可利用PDFLib提供的簡單易用的API&#xff08;應用編程接口&#xff09;在服務器或客戶端產生PDF文檔, PDFlib在生成PDF文檔時不需要第3方軟件的支持,也不需要其它工具。此產品屬于產品 PDFlibPDI 的…

Hbase時間同步

如果Hbase的時間沒有同步&#xff0c;啟動主節點會起來&#xff0c;子節點的regionServer就不會起來。 錯誤日志如下&#xff1a; aused by: org.apache.hadoop.hbase.ipc.RemoteWithExtrasException(org.apache.hadoop.hbase.ClockOutOfSyncException): org.apache.hadoop.hba…