日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

[python 进阶] 9. 符合Python风格的对象

發(fā)布時間:2023/12/10 python 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [python 进阶] 9. 符合Python风格的对象 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文章目錄

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

本章包含以下話題:

  • 支持用于生成對象其他表示形式的內(nèi)置函數(shù)(如 repr()、bytes(),等等)
  • 使用一個類方法實現(xiàn)備選構(gòu)造方法
  • 擴展內(nèi)置的 format() 函數(shù)和 str.format() 方法使用的格式微語言
  • 實現(xiàn)只讀屬性
  • 把對象變?yōu)榭缮⒘械?#xff0c;以便在集合中及作為 dict 的鍵使用
  • 利用 _slots_ 節(jié)省內(nèi)存。

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

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

9.1 對象表示形式

每門面向?qū)ο蟮恼Z言至少都有一種獲取對象的字符串表示形式的標準方式。Python 提供了
兩種方式。

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

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

  • _repr_、_str_ 和 _format_ 都必須返回 Unicode 字符串(str 類型)。
  • 只有_bytes_ 方法應該返回字節(jié)序列(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 分量構(gòu)成的直角三角形的斜邊長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 備選構(gòu)造方法

我們可以把 Vector2d 實例轉(zhuǎn)換成字節(jié)序列了;同理,也應該能從字節(jié)序列轉(zhuǎn)換成Vector2d 實例。在標準庫中探索一番之后,我們發(fā)現(xiàn) array.array 有個類方法.frombytes(2.9.1 節(jié)介紹過)正好符合需求。下面在 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 參數(shù);相反,要通過 cls 傳入類本身。
? 從第一個字節(jié)中讀取 typecode。
? 使用傳入的 octets 字節(jié)序列創(chuàng)建一個 memoryview,然后使用 typecode 轉(zhuǎn)換。
2.9.2 節(jié)簡單介紹過 memoryview,說明了它的 .cast 方法。
? 拆包轉(zhuǎn)換后的 memoryview,得到構(gòu)造方法所需的一對參數(shù)。

9.4 classmethod與staticmethod

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

>>> 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 格式化顯示

內(nèi)置的 format() 函數(shù)和 str.format() 方法把各個類型的格式化方式委托給相應的.format(format_spec) 方法。format_spec 是格式說明符,它是:format(my_obj, format_spec) 的第二個參數(shù),或者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'

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

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

下面是內(nèi)置的 format() 函數(shù)和 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)。我
們?yōu)?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__

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

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

實現(xiàn)這種輸出的 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)

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

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

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

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 方法,前面已經(jīng)實現(xiàn)了)。

什么是可散列的數(shù)據(jù)類型

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

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

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

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

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 分量構(gòu)成的直角三角形的斜邊長def __bool__(self):return bool(abs(self))v1 = Vector2d(3, 4)

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

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

Python 不能像 Java 那樣使用 private 修飾符創(chuàng)建私有屬性,但是 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)

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

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

9.8 使用 slots 類屬性節(jié)省空間

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

總結(jié)

以上是生活随笔為你收集整理的[python 进阶] 9. 符合Python风格的对象的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。