摘要:以便于用戶理解的方式返回對象的字符串表示形式。函數(shù)會調(diào)用函數(shù),對來說,輸出的是一個有序?qū)Α4送猓€有用于支持內(nèi)置的構(gòu)造函數(shù)的方法。可散列實現(xiàn)了方法,使用推薦的異或運算符計算實例屬性的散列值私有屬性最好用命名規(guī)則來實現(xiàn)這種方式有好有壞
repr()
以便于開發(fā)者理解的方式返回對象的字符串表示形式。
str()
以便于用戶理解的方式返回對象的字符串表示形式。
? 定義 iter 方法,把 Vector2d 實例變成可迭代的對象,這樣才能拆包(例
如,x, y = my_vector)。這個方法的實現(xiàn)方式很簡單,直接調(diào)用生成器表達式一個接
一個產(chǎn)出分量。這一行也可以寫成 yield self.x; yield.self.y。第 14 章會進一步討論 iter 特殊方法、生成器表達式和
yield 關(guān)鍵字。
? print 函數(shù)會調(diào)用 str 函數(shù),對 Vector2d 來說,輸出的是一個有序?qū)Α?br>? bytes 函數(shù)會調(diào)用 bytes 方法,生成實例的二進制表示形式。
bool 函數(shù)會調(diào)用 bool 方法,如果 Vector2d 實例的模為零,返回 False,否則
返回 True。
from array import array import math class Vector2d: typecode = "d" def __init__(self, x, y): self.x = float(x) self.y = float(y) def __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) def __bool__(self): return bool(abs(self))
? 在 init 方法中把 x 和 y 轉(zhuǎn)換成浮點數(shù),盡早捕獲錯誤,以防調(diào)用 Vector2d 函
數(shù)時傳入不當參數(shù)。
? 定義 iter 方法,把 Vector2d 實例變成可迭代的對象,這樣才能拆包(例
如,x, y = my_vector)。這個方法的實現(xiàn)方式很簡單,直接調(diào)用生成器表達式一個接
一個產(chǎn)出分量。
這一行也可以寫成 yield self.x; yield.self.y。第 14 章會進一步討論 iter 特殊方法、生成器表達式和
yield 關(guān)鍵字。
"__eq__ 方法",在兩個操作數(shù)都是 Vector2d 實例時可用,不備選構(gòu)造方法
過拿 Vector2d 實例與其他具有相同數(shù)值的可迭代對象相比,結(jié)果也是 True(如
Vector(3, 4) == [3, 4])。這個行為可以視作特性,也可以視作缺陷。
1.把 Vector2d 實例轉(zhuǎn)換成字節(jié)序列了;
2.同理,也應該能從字節(jié)序列轉(zhuǎn)換成Vector2d 實例。
3.在標準庫中探索一番之后,我們發(fā)現(xiàn) array.array 有個類方法.frombytes(2.9.1 節(jié)介紹過)正好符合需求。
代碼如下:
from array import array import math class Vector2d: typecode = "d" def __init__(self, x, y): self.x = float(x) self.y = float(y) def __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) def __bool__(self): return bool(abs(self)) @classmethod def frombytes(cls, octets): typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(*memv)
? 從第一個字節(jié)中讀取 typecode。
? 使用傳入的 octets 字節(jié)序列創(chuàng)建一個 memoryview,然后使用 typecode 轉(zhuǎn)換。
2.9.2 節(jié)簡單介紹過 memoryview,說明了它的 .cast 方法。
? 拆包轉(zhuǎn)換后的 memoryview,得到構(gòu)造方法所需的一對參數(shù)。
內(nèi)置的 format() 函數(shù)和 str.format() 方法把各個類型的格式化方式委托給相應的
.__format__(format_spec) 方法。format_spec 是格式說明符,它是:
format(my_obj, format_spec) 的第二個參數(shù),或者
str.format() 方法的格式字符串,{} 里代換字段中冒號后面的部分
定義操作類,而不是操作實例的方法。
classmethod 改變了調(diào)用方法的方式,因此類方法的第一個參數(shù)是類本身,而不是實例。
classmethod 最常見的用途是定義備選構(gòu)造方法,例如示例 9-3 中的frombytes。
注意,frombytes 的最后一行使用 cls 參數(shù)構(gòu)建了一個新實例,即cls(*memv)。
我們對 classmethod 的作用已經(jīng)有所了解(而且知道 staticmethod 不是特別有 用)
9.5 格式化顯示內(nèi)置的 format() 函數(shù)和 str.format() 方法把各個類型的格式化方式委托給相應的.__format__(format_spec) 方法。
format_spec 是格式說明符,它是:format(my_obj, format_spec) 的第二個參數(shù),或者str.format() 方法的格式字符串,
{} 里代換字段中冒號后面的部分
>>> brl = 1/2.43 # BRL到USD的貨幣兌換比價 >>> brl 0.4115226337448559 >>> format(brl, "0.4f") # ? "0.4115" >>> "1 BRL = {rate:0.2f} USD".format(rate=brl) # ? "1 BRL = 0.41 USD"
格式規(guī)范微語言為一些內(nèi)置類型提供了專用的表示代碼。
比如,b 和 x 分別表示二進制和十六進制的 int 類型,f 表示小數(shù)形式的 float 類型,而 % 表示百分數(shù)形式:
>>> format(42, "b") "101010" >>> format(2/3, ".1%") "66.7%"
>>> from datetime import datetime >>> now = datetime.now() >>> format(now, "%H:%M:%S") "18:49:05" >>> "It"s now {:%I:%M %p}".format(now) "It"s now 06:49 PM"如果類沒有定義 format 方法,從 object 繼承的方法會返回 str(my_object)。我們?yōu)?Vector2d 類定義了 str 方法
>>> v1 = Vector2d(3, 4) >>> format(v1) "(3.0, 4.0)"向量類做format
from array import array import math class Vector2d: typecode = "d" def __init__(self, x, y): self.x = float(x) self.y = float(y) def __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) def __bool__(self): return bool(abs(self)) @classmethod def frombytes(cls, octets): typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(*memv) def __format__(self, fmt_spec=""): components = (format(c, fmt_spec) for c in self) # ? #這里還是有些看不懂,拆包拆出來個啥? return "({}, {})".format(*components) v1 = Vector2d(3, 4) print(format(v1)) print(format(v1, ".2f")) print(format(v1, ".3e"))增強 format 方法,計算極坐標
我們已經(jīng)定義了計算模的 abs 方法,因此還要定義一個簡單的
angle 方法,使用 math.atan2() 函數(shù)計算角度
from array import array import math class Vector2d: typecode = "d" def __init__(self, x, y): self.x = float(x) self.y = float(y) def __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) def __bool__(self): return bool(abs(self)) @classmethod def frombytes(cls, octets): typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(*memv) def angle(self): return math.atan2(self.y, self.x) 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) print(format(Vector2d(1, 1), "p")) print(format(Vector2d(1, 1), ".3ep")) print(format(Vector2d(1, 1), "0.5fp"))可散列的Vector2d
為了把 Vector2d 實例變成可散列的,必須使用 hash 方法(還需要 eq 方法,前面已經(jīng)實現(xiàn)了)。此外,還要讓向量不可變,
目前,我們可以為分量賦新值,如 v1.x = 7,Vector2d 類的代碼并不阻止這么做。我
們想要的行為是這樣的:
>>> v1.x, v1.y (3.0, 4.0) >>> v1.x = 7 Traceback (most recent call last): ... AttributeError: can"t set attribute #為此,我們要把 x 和 y 分量設為只讀特性,
不完整代碼
class Vector2d: typecode = "d" def __init__(self, x, y): self.__x = float(x) self.__y = float(y) @property def x(self): return self.__x @property def y(self): return self.__y def __iter__(self): return (i for i in (self.x, self.y)) def __hash__(self): return hash(self.x) ^ hash(self.y)
使用兩個前導下劃線(尾部沒有下劃線,或者有一個下劃線),把屬性標記為私有
的
@property 裝飾器把讀值方法標記為特性。
讀值方法與公開屬性同名,都是 x。
使用位運算符異或(^)混合各分量的散列值——我們會這么做
要想創(chuàng)建可散列的類型,不一定要實現(xiàn)特性,也不一定要保護實例屬性。只需
正確地實現(xiàn) hash 和 eq 方法即可。但是,實例的散列值絕不應該變化,
hash成功了
>>> v1 = Vector2d(3, 4) >>> v2 = Vector2d(3.1, 4.2) >>> hash(v1), hash(v2) (7, 384307168202284039) >>> set([v1, v2]) {Vector2d(3.1, 4.2), Vector2d(3.0, 4.0)}
如果定義的類型有標量數(shù)值,可能還要實現(xiàn) int 和 float 方法(分別被 int()和 float() 構(gòu)造函數(shù)調(diào)用),以便在某些情況下用于強制轉(zhuǎn)換類型。
此外,還有用于支持內(nèi)置的 complex() 構(gòu)造函數(shù)的 complex 方法。
Vector2d 或許應該提供__complex__ 方法,不過我把它留作練習給讀者。
完整的向量類代碼
from array import array import math class Vector2d: typecode = "d" def __init__(self, x, y): self.__x = float(x) self.__y = float(y) @property def x(self): return self.__x @property def y(self): return self.__y def __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 __hash__(self): return hash(self.x) ^ hash(self.y) def __abs__(self): return math.hypot(self.x, self.y) def __bool__(self): return bool(abs(self)) def angle(self): return math.atan2(self.y, self.x) 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) @classmethod def frombytes(cls, octets): typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(*memv)Python的私有屬性和“受保護的”屬性
舉個例子。有人編寫了一個名為 Dog 的類,這個類的內(nèi)部用到了 mood 實例屬性,但是沒有將其開放。現(xiàn)在,你創(chuàng)建了 Dog 類的子類:Beagle。如果你在毫不知情的情況下又創(chuàng)建了名為 mood 的實例屬性,那么在繼承的方法中就會把 Dog 類的 mood 屬性覆蓋掉。這是個難以調(diào)試的問題。
為了避免這種情況,如果以 mood 的形式(兩個前導下劃線,尾部沒有或最多有一個下劃線)命名實例屬性,Python 會把屬性名存入實例的 __dict 屬性中,而且會在前面加上一個下劃線和類名。因此,對 Dog 類來說,__mood 會變成 _Dog__mood;對 Beagle類來說,會變成 _Beagle__mood。這個語言特性叫名稱改寫(name mangling)。
>>> v1 = Vector2d(3, 4) >>> v1.__dict__ {"_Vector2d__y": 4.0, "_Vector2d__x": 3.0} >>> v1._Vector2d__x 3.0對于私有屬性推薦這么做
Python 解釋器不會對使用單個下劃線的屬性名做特殊處理,不過這是很多 Python 程序員使用 slots 類屬性節(jié)省空間
嚴格遵守的約定,他們不會在類外部訪問這種屬性。 遵守使用一個下劃線標記對象的私
有屬性很容易,就像遵守使用全大寫字母編寫常量那樣容易。
但是,如果我們想要限制class的屬性怎么辦?比如,只允許對Student實例添加name和age屬性。
為了達到限制的目的,Python允許在定義class的時候,定義一個特殊的__slots__變量,來限制該class能添加的屬性
class Vector2d: __slots__ = ("__x", "__y") typecode = "d"
在類中定義 slots 屬性的目的是告訴解釋器:“這個類中的所有實例屬性都在這兒
了!”這樣,Python 會在各個實例中使用類似元組的結(jié)構(gòu)存儲實例變量,從而避免使用消
耗內(nèi)存的 dict 屬性。如果有數(shù)百萬個實例同時活動,這樣做能節(jié)省大量內(nèi)存。
綜上,__slots__ 屬性有些需要注意的地方,而且不能濫用,不能使用它限制用戶能賦覆蓋類屬性
值的屬性。處理列表數(shù)據(jù)時 slots 屬性最有用,例如模式固定的數(shù)據(jù)庫記錄,以及
特大型數(shù)據(jù)集。如果你的程序不用處理數(shù)百萬個實例,或許不值得費勁去創(chuàng)建不尋常的類,那就禁止它創(chuàng)
建動態(tài)屬性或者不支持弱引用。與其他優(yōu)化措施一樣,僅當權(quán)衡當下的需求并仔細搜集資
料后證明確實有必要時,才應該使用 slots 屬性。
因為 Vector2d 實例本身沒有 typecode 屬性,所以 self.typecode 默認獲
取的是 Vector2d.typecode 類屬性的值
使用 type(self).__name__ 獲取,如下所示:
# 在Vector2d類中定義 def __repr__(self): class_name = type(self).__name__ return "{}({!r}, {!r})".format(class_name, *self)
如果硬編碼 class_name 的值,那么 Vector2d 的子類(如 ShortVector2d)要覆蓋__repr__ 方法,只是為了修改 class_name 的值。
從實例的類型中讀取類名,__repr__ 方法就可以放心繼承。
如何利用數(shù)據(jù)模型處理 Python 的其他功能:小總結(jié)
提供不同的對象表示形式、實現(xiàn)自定義的格式代碼、公開只讀屬性,以及通過 hash() 函數(shù)支持集合和映射
所有用于獲取字符串和字節(jié)序列表示形式的方法:__repr__、__str__、__format__ 和 __bytes__。
把對象轉(zhuǎn)換成數(shù)字的幾個方法:__abs__、__bool__和 __hash__。
用于測試字節(jié)序列轉(zhuǎn)換和支持散列(連同 hash 方法)的 eq 運算符。
format現(xiàn) format 方法,對提供給內(nèi)置函數(shù) format(obj,format_spec) 的 format_spec
提供給 str.format 方法的"{:?format_spec?}" 位于代換字段中的 ?format_spec? 做簡單的解析
用 slots 屬性節(jié)省內(nèi)存,以及這么做要注意的問題。slots 屬性有點棘手,因此僅當處理特別多的實例(數(shù)百萬個,而不是幾千
個)時才建議使用。
實現(xiàn)了 hash 方法,使用推薦的異或運算符計算實例屬性的散列值
私有屬性最好用 命名規(guī)則來實現(xiàn)self.__x 這種方式有好有壞
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/41708.html
摘要:類或父類中聲明的方法的優(yōu)先級高于任何聲明為默認方法的優(yōu)先級。只有聲明了一個默認方法。由于比更加具體,所以編譯器會選擇中聲明的默認方法。 如果在現(xiàn)存的接口上引入了非常多的新方法,所有的實現(xiàn)類都必須進行改造,實現(xiàn)新方法,為了解決這個問題,Java 8為了解決這一問題引入了一種新的機制。Java 8中的接口現(xiàn)在支持在聲明方法的同時提供實現(xiàn),這聽起來讓人驚訝!通過兩種方式可以完成這種操作。其一...
摘要:第一章數(shù)據(jù)類型隱式方法利用快速生成類方法方法通過下標找元素自動支持切片操作可迭代方法與如果是一個自定義類的對象,那么會自己去調(diào)用其中由你實現(xiàn)的方法。若返回,則會返回否則返回。一個對象沒有函數(shù),解釋器會用作為替代。 第一章 python數(shù)據(jù)類型 1 隱式方法 利用collections.namedtuple 快速生成類 import collections Card = collec...
摘要:第一章數(shù)據(jù)類型隱式方法利用快速生成字典方法方法通過下標找元素自動支持切片操作可迭代方法與如果是一個自定義類的對象,那么會自己去調(diào)用其中由你實現(xiàn)的方法。若返回,則會返回否則返回。一個對象沒有函數(shù),解釋器會用作為替代。 第一章 python數(shù)據(jù)類型 1 隱式方法 利用collections.namedtuple 快速生成字典 import collections Card = coll...
摘要:處理文本的最佳實踐是三明治要盡早把輸入例如讀取文件時的字節(jié)序列解碼成字符串。這種三明治中的肉片是程序的業(yè)務邏輯,在這里只能處理字符串對象。 處理文本的最佳實踐是Unicode 三明治 要盡早把輸入(例如讀取文件時)的字節(jié)序列解碼成字符串。 這種三明治中的肉片是程序的業(yè)務邏輯,在這里只能處理字符串對象。 在其他處理過程中,一定不能編碼或解碼。 對輸出來說,則要盡量晚地把字符串編碼成字...
摘要:自己定義的抽象基類要繼承。抽象基類可以包含具體方法。這里想表達的觀點是我們可以偷懶,直接從抽象基類中繼承不是那么理想的具體方法。 抽象基類 抽象基類的常見用途: 實現(xiàn)接口時作為超類使用。 然后,說明抽象基類如何檢查具體子類是否符合接口定義,以及如何使用注冊機制聲明一個類實現(xiàn)了某個接口,而不進行子類化操作。 如何讓抽象基類自動識別任何符合接口的類——不進行子類化或注冊。 接口在動態(tài)類...
閱讀 3027·2021-11-02 14:40
閱讀 843·2019-08-30 15:53
閱讀 1265·2019-08-30 15:53
閱讀 3259·2019-08-30 13:53
閱讀 3305·2019-08-29 12:50
閱讀 1132·2019-08-26 13:49
閱讀 1863·2019-08-26 12:20
閱讀 3660·2019-08-26 11:33