魔法方法
在python中,有一些內置好的特定的方法,這些方法在進行特定的操作時會自動被調用,稱之為魔法方法。
構造和析構
魔法方法總是被雙下橫線包圍,例如__init__;
是面向對象的Python的一切;
他們總是能在適當的時候被調用。
-
__init__(self[, …])
相當于其他編程語言的構造方法,類在實例化對象的時候首先會調用的一個方法。
-
__new__(cls[, …])
init并不是實例化對象調用的第一個方法,new方法才是,它的第一個參數是cls,通常情況下是返回cls類的對象,也可以返回其他類的對象。
new方法是極少去重寫它的,Python會默認執行,但是當需要繼承一個不可變類型又需要修改的時候,那么就需要重寫了。
-
__del__(self)
當對象將要被銷毀的時候這個方法會自動被調用,但是
del x
并不等于調用了
x.__del__()
del方法是當垃圾回收機制,即當沒有任何變量去引用這個對象的時候,垃圾回收機制會自動銷毀,這時才會調用對象的self方法。
注意:內置的__del__()方法并不是發生del操作的時候就會調用,當對象生成后,所有對它的引用都被del后才會啟動垃圾回收機制,才會調用__del__()方法。
算數運算
在Python2.2之前類和類型是分開的,類是屬性和方法的封裝,類型是如整型、浮點型、字符串這些類型,但是在Python2.2后,試圖對兩者進行統一,做法就是將int、float、string、list、tuple這些內置函數通常轉化為工廠函數。
參考
原因: 當a+b,識別到加法會先調用前者a的add,返回self+other,即返回了a+b,就又運行了加法add,進入了無限遞歸。
所以在實現的時候,一定要注意避免出現無限遞歸的情況。
稍作修改
方法相應二元算數運算符
| __add__(self, other) | 定義加法的行為:+ |
| __sub__(self, other) | 定義減法的行為:- |
| __mul__(self, other) | 定義乘法的行為:* |
| __truediv__(self, other) | 定義真除法的行為:/ |
| __floordiv__(self, other) | 定義整數除法的行為:// |
| __mod__(self, other) | 定義取模算法的行為:% |
| __divmod__(self, other) | 定義當被divmod()調用時的行為【divmod(a, b)返回值是一個元組(a//b, a%b)】 |
| __pow__(self, other[, modulo]) | 定義當被pow()調用或**運算時的行為 |
| __lshift__(self, other) | 定義按位左移位的行為:<< |
| __rshift__(self, other) | 定義按位右移位的行為:>> |
| __and__(self, other) | 定義按位與操作的行為:& |
| __xor__(self,other) | 定義按位異或操作的行為:^ |
| __or__(self, other) | 定義按位或操作的行為:| |
通過對指定魔法方法的重寫,可以讓Python根據自己的意圖來實現程序
魔法方法參考
- 反運算
這里是3-1,并不是1-3,如果想讓1-3,那么就要互換int.sub()中self, other的位置,舉例說明
定制一個簡單的類
- 需要的資源
(1)使用time模塊的localtime方法獲取時間
time模塊詳解
(2)time.localtime返回struct_time的時間格式
(3)表現你的類:重寫__str__和__repr__
import time as tclass MyTimer():def __str__(self):return self.prompt__repr__ = __str__# 開始計時def start(self):self.start = t.localtime()print("計時開始...")# 停止計時def stop(self):self.stop = t.localtime()self._calc()print("計時結束...")# 內部方法,計算運行時間def _calc(self):self.lasted = []self.prompt = "總共運行了"for index in range(6):self.lasted.append(self.stop[index] - self.start[index])self.prompt += str(self.lasted[index])
但是這里有一個問題就是,如果定以后直接調用,就會報錯
因為這時,prompt還沒有被定義,這時就需要所有屬于實例對象的變量先在init中定義
import time as tclass MyTimer():# 添加init定義def __init__(self):self.prompt = "未開始計時!"self.lasted = []self.start = 0self.stop = 0def __str__(self):return self.prompt__repr__ = __str__# 開始計時def start(self):self.start = t.localtime()print("計時開始...")# 停止計時def stop(self):self.stop = t.localtime()self._calc()print("計時結束...")# 內部方法,計算運行時間def _calc(self):self.lasted = []self.prompt = "總共運行了"for index in range(6):self.lasted.append(self.stop[index] - self.start[index])self.prompt += str(self.lasted[index])
這時在執行雖然不會直接調用t1出錯了,但是運行起來卻又出現“整型不能被調用”問題,這里是由于在init中將self.start定義為0導致,因為類的方法名和屬性名一樣時,屬性會覆蓋方法,這里就認為start是屬性。
這時只需要將start和stop改一下名字即可
并且在這里改變一下顯示的方式和累加計時
import time as tclass MyTimer():def __init__(self):self.unit = ['年', '月', '天', '小時', '分鐘', '秒']self.prompt = "未開始計時!"self.lasted = []self.begin = 0self.end = 0def __str__(self):return self.prompt__repr__ = __str__def __add__(self, other):prompt = "總共運行了"result = []for index in range(6):result.append(self.lasted[index] + other.lasted[index])if result[index]:prompt += (str(result[index]) + self.unit[index])return prompt# 開始計時def start(self):self.begin = t.localtime()self.prompt = "提示:請先調用 stop() 停止計時!"print("計時開始...")# 停止計時def stop(self):if not self.begin:print("提示:請先調用start()進行計時!")else:self.end = t.localtime()self._calc()print("計時結束...")# 內部方法,計算運行時間def _calc(self):self.lasted = []self.prompt = "總共運行了"for index in range(6):self.lasted.append(self.end[index] - self.begin [index])if self.lasted[index]: # 為0不顯示 self.prompt += str(self.lasted[index]) + self.unit[index]# 為下一輪計時初始化變量self.begin = 0self.end = 0
- 代碼存在的問題
(1)生成的時間會存在負數的情況
(2)精度不夠,只能到秒
屬性訪問
(1)直接訪問屬性
(2)通過getattr()訪問
(3)利用property(),以屬性的方式訪問屬性
- __getattr__(self, name)
定義當用戶試圖獲取一個不存在的屬性時的行為 - __getattribute__(self, name)
定義當該類的屬性被訪問時的行為 - __setattr__(self, name, value)
定義當一個屬性被設置時的行為 - __delattr__(self, name)
定義當一個屬性被刪除時的行為
>>> class C:def __getattribute__(self, name):print("getattribute")return super().__getattribute__(name)def __getattr__(self, name):print("getattr")def __setattr__(self, name, value):print("setattr")super().__setattr__(name, value)def __delattr__(self, name):print("delattr")super().__delattr__(name)
class Rectangle:def __init__(self, width=0, height=0):self.width = widthself.height = heightdef __setattr__(self, name, value):if name == 'square':self.width = valueself.height = valueelse:self.name = valuedef getArea(self): # 獲得面積return self.width * self.height
# 輸入 r1 = Rectangle(4, 5)
# 這樣寫會出現一個無限遞歸,因為執行__init__中的self.width和self.height賦值語句,會觸發__setattr__中的else后的語句self.name = value,再重復調用__setattr__,這樣就會無限遞歸下去
# =====================================================================================================
# 下面進行改進
class Rectangle:def __init__(self, width=0, height=0):self.width = widthself.height = heightdef __setattr__(self, name, value):if name == 'square':self.width = valueself.height = valueelse:super().__setattr__(name, value)def getArea(self): # 獲得面積return self.width * self.height
另一種改進方法就是給一個特殊屬性dict,dict是以字典的形式顯示出當前對象的所有屬性以及對應的值
class Rectangle:def __init__(self, width=0, height=0):self.width = widthself.height = heightdef __setattr__(self, name, value):if name == 'square':self.width = valueself.height = valueelse:self.__dict__[name] = valluedef getArea(self): # 獲得面積return self.width * self.height
描述符
描述符就是將某種特殊類型的類的實例指派給另一個類的屬性。
__get__(self, instance, owner)
# 用于訪問屬性,返回屬性的值
__set__(self, instane, value)
# 將在屬性分配操作中調用,不返回任何內容
__delete__(self, instance)
# 控制刪除操作,不返回任何內容
class MyDescriptordef __get__(self, instance, owner):print("getting...", self, instance, owner)def __set__(self, instance, value):print("setting...", self, instance, value)def __delete__(self, instnce):print("deleting...", self, instance)class Test:x = MyDescriptor()
【ps:這里改用spyder編輯了,輸入處顯示方式改變,實際操作同python idle相同】
這里就是將某種特殊類型的類(MyDescriptor)的實例(MyDescriptor())指派給另一個類(Test)的屬性(x),就說明MyDescriptor就是x的描述符。
實例化對象后,用text.x強制打印
可以看到打印出三個參數,第一個是self的參數描述符類MyDescriptor本身的實例,第二個是instance的參數類的擁有者Test的實例test,第三個就是擁有者類Test本身
驗證一下
對實例化對象進行賦值,出現賦值調用set的特殊方法,打印self、instance和value
del同理,打印self和instance
- 定義一個MyProperty
之前提到的property其實就是一個描述符
class MyProperty:def __init__(self, fget=None, fset=None, fdel=None):self.fget = fgetself.fset = fsetself.fdel = fdeldef __get__(self, instance, value):return self.fget(instance)def __set__(self, instance, value):self.fset(instance, value)def __delete__(self, instance):self.fdel(instance)class C:def __init__(self):self._x = Nonedef getX(self):return self._xdef setX(self, value):self._x = valuedef delX(self):del self._xx = MyProperty(getX, setX, delX)
同樣的這里將MyProperty的實例MyProperty()指派給類C的屬性x,對類C的實例對象c的x屬性賦值,調用setX返回c._x再進行操作。
class Celsius:def __init__(self, value = 26.0):self.value = float(value)def __get__(self, instance, owner):return self.valuedef __set__(self, instance, value):self.value = float(value)class Fahrenheit: def __get__(self, instance, owner):return instance.cel * 1.8 +32def __set__(self, instance, value):instance.cel = (float(value) - 32) / 1.8class Temperature:cel = Celsius()fah = Fahrenheit()
定制容器
- 協議
協議Protocols相似于接口,規定了必須要定義的方法。而在Python中協議更像是一種指南。 - 容器類型的協議
(1)定制不可變容器
只需定義__len__()和__getitem__()方法
(2)定制可變容器
除__len__()和__getitem__()方法外,還需定義__setitem__()和__delitem__()兩個方法
Python魔法方法詳解
- 練習
編寫一個不可改變的自定義列表,要求記錄列表中每個元素被訪問的次數。
class CountList:def __init__(self, *args): # 星號代表參數是可變數量的self.values = [x for x in args] # 依次取列表中元素self.count = {}.fromkeys(range(len(self.values)),0)# fromkeys 用于創建一個新字典def __len__(self):return len(self.values)def __getitem__(self, key):self.count[key] += 1return self.values[key]
迭代器
提供迭代方法的容器稱之為迭代器。
通常的迭代器有序列、列表、元組、字符串、字典、文件。
- for語句迭代
- 字典迭代
- 關于迭代操作,Python提供了兩個BIF內置函數inter()和next()
inter() 即iteration
對于容器對象調用iter就得到它的迭代器,調用next()就會返回下一個值,當迭代器沒有值可以返回了,Python就會拋出一個“StopIteration”的異常,此時迭代結束。
這樣就可以知道for語句是如何執行的
利用while語句來模擬for語句的執行
- 迭代器的魔法方法
# 兩個魔法方法分別對應兩個BIF容器的實現
iter()
--> __iter__() # 返回迭代器本身
next()
--> __next__() # 決定迭代器的迭代規則
class Fibs:def __init__(self, n=10):self.a = 0self.b = 1self.n = ndef __iter__(self):return selfdef __next__(self):self.a, self.b = self.b, self.a + self.bif self.a > self.n:raise StopIterationreturn self.a
生成器
生成器并不涉及魔法方法、類和對對象,只通過普通的函數實現。生成器實際上是迭代器的一種實現。
生成器延續了Python簡潔的特點,并且使協同程序的概念得以實現,協同程序就是可以運行的獨立函數調用,函數可以暫?;驋炱?#xff0c;并在需要的時候從程序離開的地方繼續或者重新開始。
Generator 實例
def myGen():print("生成器被執行!")yield 1yield 2# 一旦一個函數中出現yield語句# 那么就說明這個函數被定義為生成器# yield就相當于普通函數中的return# 和return的區別:# 出現yield,就將yield后的參數返回,并暫停在yield處
def libs():a = 0b = 1while True:a, b = b, a + byield a # 由于有yield,所以while True不會變成死循環
- 推導式
(1)列表推導式
(2)字典推導式
有“:”的是字典,沒有的是集合
(3)集合推導式
(4)沒有字符串推導式
(5)元組(tuple)推導式
打印元組e發現e是一個生成器推導式
生成器推導式如果作為函數的參數,是可以直接寫推導式,不需要加括號
【100以內不能被2整除的整數和】
[擴展閱讀] 提高你的 Python:解釋 yield 和 Generators(生成器)
總結
以上是生活随笔為你收集整理的Python学习(六)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。