本節(jié)最主要搞懂魔法方法什么時(shí)候被觸發(fā)(被調(diào)用)
調(diào)用分為隱式調(diào)用和顯式調(diào)用砂客,而魔法方法都屬于隱式調(diào)用卑吭。
1. 構(gòu)造和初始化
- new():在實(shí)例創(chuàng)建之前被調(diào)用的,它的任務(wù)就是創(chuàng)建實(shí)例然后返回該實(shí)例痒给,是個(gè)靜態(tài)方法
- init():當(dāng)實(shí)例對(duì)象創(chuàng)建完成后被調(diào)用的查辩,然后設(shè)置對(duì)象屬性的一些初始值
當(dāng)自己創(chuàng)建了__new__()和__init__()同時(shí)存在時(shí),會(huì)只調(diào)用__new__()
解決方法:在__new__()創(chuàng)建實(shí)例+返回:
def __new__(cls,...):
instance = object.__new__(cls)
return instance
self是當(dāng)前實(shí)例本身
cls是type類的實(shí)例(類對(duì)象)
print(type(cls))
—— <class 'type'>
執(zhí)行順序:new() --> init()
【例】new() 和 init()的比較
class Person(object):
def __new__(cls, *args, **kwargs):
print('__new__')
print(type(cls))
instance = object.__new__(cls)
print(id(instance))
return instance
def __init__(self, name):
print(id(self))
self.name = name
self.age = 33
print('init')
def output(self):
print('hello')
p = Person('xuebi')
# __new__
# <class 'type'> # print(type(cls))
# 10882896 # print(id(instance))
# 10882896 # print(id(self))
# init
print(p.name) # self.name = name
# xuebi
print(p.age) # self.age = 33
# 33
p.output()
# hello
2. 屬性訪問(wèn)控制
- getattr(self, name):訪問(wèn)不存在的屬性時(shí)調(diào)用
- getattribute(self, name):訪問(wèn)存在的屬性時(shí)調(diào)用(先調(diào)用該方法眠副,查看是否存在該屬性画切,若不存在,接著去調(diào)用①)
- setattr(self, name, value):設(shè)置實(shí)例對(duì)象的一個(gè)新的屬性時(shí)調(diào)用
- delattr(self, name):刪除一個(gè)實(shí)例對(duì)象的屬性時(shí)調(diào)用
當(dāng)自己創(chuàng)建了__getattribute__()時(shí)囱怕,實(shí)例名.__dict__得到的時(shí)None
解決方法:在__getattribute__()中返回調(diào)用父類的__getattribute__():
def __getattribute__(self, item):
return super().__getattribute__(item)
執(zhí)行順序:實(shí)例化 --> setattr() --> getattr()或getattribute()
【例】setattr()霍弹、getattr()、getattribute()的用法
class Foo:
def __init__(self, y):
self.y = y
pass
def __getattribute__(self, item):
print('__getattribute__')
print(item)
# self.key=value # 這就無(wú)限遞歸了
return super().__getattribute__(item) # 正確做法
def __getattr__(self, item):
print('__getattr__')
print(item)
return '未知屬性'
def __setattr__(self, key, value):
print('__setattr__')
print(key)
print(value)
# self.key = value # 這就無(wú)限遞歸了
self.__dict__[key] = value # 正確做法
def __delattr__(self, item):
print('__delattr__')
# del self.item # 無(wú)限遞歸了
super().__delattr__(item) # 正確做法
f = Foo(100)
# __setattr__
# y # print(key)
# 100 # print(value)
# __getattribute__
# __dict__ # print(item)
print('-'*50)
print(f.__dict__)
print('-'*50)
# --------------------------------------------------
# __getattribute__
# __dict__
# {'y': 100}
# --------------------------------------------------
print('-'*50)
f.z = 3 # 調(diào)用__setattr__()方法
print('-'*50)
# --------------------------------------------------
# __setattr__
# z # print(key)
# 3 # print(value)
# __getattribute__
# __dict__ # print(item)
# --------------------------------------------------
print('-'*50)
print(f.__dict__)
print('-'*50)
# --------------------------------------------------
# __getattribute__
# __dict__
# {'y': 100, 'z': 3}
# --------------------------------------------------
print('-'*50)
f.x = 90 # f.x == f.__dict__['x']
print('-'*50)
# --------------------------------------------------
# __setattr__
# x
# 90
# __getattribute__
# __dict__
# --------------------------------------------------
print('-'*50)
print(f.__dict__)
print('-'*50)
# --------------------------------------------------
# __getattribute__
# __dict__
# {'y': 100, 'z': 3, 'x': 90}
# --------------------------------------------------
print('-'*50)
del f.x # 調(diào)用__delattr__()方法
print('-'*50)
# --------------------------------------------------
# __delattr__
# --------------------------------------------------
print('-'*50)
print(f.__dict__)
print('-'*50)
# --------------------------------------------------
# __getattribute__
# __dict__
# {'y': 100, 'z': 3}
# --------------------------------------------------
print('-'*50)
print(f.xxxxxxxxx) # 在__getattribute__()方法中嘗試返回時(shí)失敗娃弓,調(diào)用__getattr__()方法
print('-'*50)
# --------------------------------------------------
# __getattribute__
# xxxxxxxxx # print(item)
# __getattr__
# xxxxxxxxx # print(item)
# 未知屬性
# --------------------------------------------------
# 下面與上面的f.xxxxxxxxx功能上一樣
# print(f.abc)
# print(f.uuu)
# print(f.name)
# print(f.age)
f.name = 'xuebi'
# __setattr__
# name
# xuebi
# __getattribute__
# __dict__
3. 描述符
實(shí)現(xiàn)了以下三個(gè)方法之一典格,就是一個(gè)描述符
- get():用于訪問(wèn)屬性。它返回屬性的值台丛,若屬性不存在耍缴、不合法等都可以拋出對(duì)應(yīng)的異常。
- set():將在屬性分配操作中調(diào)用齐佳。不會(huì)返回任何內(nèi)容私恬。
- delete():控制刪除操作。不會(huì)返回內(nèi)容炼吴。
【例】成績(jī)處理
class Score:
def __init__(self, value=0):
self.__score = value
# 處理
def __set__(self, instance, value):
print('__set__')
if not isinstance(value, int):
raise TypeError('TypeError')
if not 0 <= value <= 100:
raise ValueError('ValueError')
self.__score = value
def __get__(self, instance, owner):
print('__get__')
return self.__score
def __del__(self):
del self.__score
class Student:
math = Score()
chinese = Score()
english = Score()
wuli = Score()
def __init__(self, name, math, chinese, english,wuli):
self.name = name
self.math = math
self.chinese = chinese
self.english = english
self.wuli = wuli
def __str__(self):
return "<Student: {}, math:{}, chinese: {}, english:{}, wuli:{}>".format(
self.name, self.math, self.chinese, self.english, self.wuli
)
stu = Student('xuebi',100,78,98,32)
# __set__
# __set__
# __set__
# __set__
# __get__
print(type(stu.math))
# <class 'int'>
print(stu.__dict__)
# {'name': 'xuebi'}
print(stu)
# __get__
# __get__
# __get__
# __get__
# <Student: xuebi, math:100, chinese: 78, english:98, wuli:32>
# 過(guò)程:
# stu = Student('xuebi',100,78,98,32)本鸣,在__init__()中:
# self.name = name
# self.math = math --> __set__() --> print('__set__') --> self.__score = value
# self.chinese = chinese --> __set__() --> print('__set__') --> self.__score = value
# self.english = english --> __set__() --> print('__set__') --> self.__score = value
# self.wuli = wuli --> __set__() --> print('__set__') --> self.__score = value
# print(type(stu.math))
# 略
# print(stu.__dict__)
# 略
# print(stu) --> __str__() --> return ... --> 每碰到self.?時(shí) --> __get__() --> print('__get__()') --> return self.__score
【例】
class Typed:
def __init__(self, key, expected_type): # 構(gòu)造函數(shù)接收所傳入的參數(shù)和參數(shù)類型
self.key = key
self.expected_type = expected_type
def __get__(self, instance, owner):
print('get方法')
return instance.__dict__[self.key] # 從底層字典獲取值
def __set__(self, instance, value):
print('set方法')
if not isinstance(value, self.expected_type): # 類型判斷
raise TypeError('%s 傳入的類型不是%s' % (self.key, self.expected_type)) # 格式化拋出異常
instance.__dict__[self.key] = value # 修改底層字典
def __delete__(self, instance):
print('delete方法')
instance.__dict__.pop(self.key)
class People:
name = Typed('name', str) # p1.__set__() self.__set__()硅蹦,觸發(fā)描述符__set__方法荣德,設(shè)置參數(shù)類型傳給構(gòu)造函數(shù)
age = Typed('age', int) # p1.__set__() self.__set__()
salary = Typed('salary', float) # p1.__set__() self.__set__()
def __init__(self, name, age, salary):
self.name = name
self.age = age
self.salary = salary
# p1=People('alex','13',13.3)#類型有誤,報(bào)錯(cuò)
p1 = People('alex', 13, 13.3)
# set方法
# set方法
# set方法
print(p1.__dict__)
# {'name': 'alex', 'age': 13, 'salary': 13.3}
print(p1.name)
# get方法
# alex
p1.name = 'egon'
# set方法
print(p1.__dict__)
# {'name': 'egon', 'age': 13, 'salary': 13.3}
del p1.name
# delete方法
print(p1.__dict__)
# {'age': 13, 'salary': 13.3}
# print(p1.name) # 相應(yīng)的鍵值對(duì)已在底層字典中刪除了童芹,報(bào)錯(cuò)
# KeyError: 'name'
4. 構(gòu)造自定義容器(Container)
- getitem(self,key):返回鍵對(duì)應(yīng)的值涮瞻。
- setitem(self,key,value):設(shè)置給定鍵的值
- delitem(self,key):刪除給定鍵對(duì)應(yīng)的元素。
- len():返回元素的數(shù)量
【例】從無(wú)限數(shù)據(jù)中自定義生成一個(gè)a1為1假褪,d為2的等差數(shù)列署咽,輸出前十個(gè)數(shù)
class ArithemeticSequence:
def __init__(self, start, step):
print('__init__')
self.start = start
self.step = step
self.datas = {}
def __getitem__(self, index):
# if index in self.datas:
# return self.datas[index]
# else:
# return self.start + index * self.step
try:
return self.datas[index]
except KeyError:
return self.start + index * self.step
def __setitem__(self, key, value):
self.datas[key] = value
def __len__(self):
return len(self.datas)
def __delitem__(self, key):
del self.datas[key]
am = ArithemeticSequence(1,3)
# print('__init__')
am[1] = 32
for i in range(10):
print(am[i])
# 1
# 32
# 7
# 10
# 13
# 16
# 19
# 22
# 25
# 28
print(len(am))
# 1
del am[1]
print(len(am))
# 0
# del只刪除字典里面的值,而對(duì)原本的無(wú)限序列沒(méi)有任何影響
for i in range(10):
print(am[i])
# 1
# 4
# 7
# 10
# 13
# 16
# 19
# 22
# 25
# 28
【例】從無(wú)限數(shù)據(jù)中自定義斐波那契數(shù)列
class feibo:
def __init__(self, start=0, end=1, index=1):
self.start = start
self.end = end
self.index = index
# self.myData = {}
# 遞推法
def __getitem__(self, index):
for i in range(self.index):
self.start, self.end = self.end, self.start + self.end
return self.start
# 1 1 2 3 5 8
s = feibo()
print(s)
print(s[0])
print(s[1])
print(s[2])
print(s[3])
print(s[4])
print(s[5])
# <__main__.feibo object at 0x011EEDD0>
# 1
# 1
# 2
# 3
# 5
# 8
5. 上下文管理
操作文本對(duì)象的時(shí)候,我們要用 with open 宁否,這就是一個(gè)上下文管理的例子窒升。
with open('test.txt') as f:
print f.readlines()
注:
(1)上下文表達(dá)式:with open('test.txt') as f:
(2)上下文管理器:open('test.txt')
(3) f 不是上下文管理器,應(yīng)該是資源對(duì)象慕匠。
上下文管理協(xié)議:
在一個(gè)類里饱须,如果實(shí)現(xiàn)了enter和exit方法,這個(gè)類的實(shí)例就是一個(gè)上下文管理器台谊。
方法一:
定義一個(gè)類蓉媳,在類中定義enter() 和 exit()方法
- enter():
- exit():
方法二:
不定義類,直接定義生成器函數(shù)锅铅,在函數(shù)名上面添加@contextlib.contextmanager裝飾器酪呻,在函數(shù)中加入yield關(guān)鍵字(作用跟方法一一樣)
系統(tǒng)默認(rèn)調(diào)用exit(),如果retrun True盐须,則不會(huì)拋出異常
【例】上下文管理器(但不支持處理異常)
class Resource:
def __enter__(self):
print('__enter__')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('__exit__')
def output(self):
print('output')
with Resource() as r:
r.output()
# __enter__
# output
# __exit__
【例】上下文管理器(允許異常處理)
class Resource:
def __enter__(self):
print('__enter__')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('__exit__')
print(exc_type)
print(exc_val)
print(exc_tb)
return True # 不寫return True時(shí)同樣會(huì)報(bào)錯(cuò)
def output(self):
# 1 / 0
print('output')
def main2():
r = Resource()
# r.output() # 直接調(diào)用output方法時(shí)會(huì)報(bào)錯(cuò)号杠,但經(jīng)過(guò)with后就不會(huì)報(bào)錯(cuò)了
with Resource() as r:
r.output()
print('finish')
main2()
# 輸出結(jié)果:
# (1)當(dāng)出現(xiàn)異常時(shí)(1 / 0):
# __enter__
# __exit__
# <class 'ZeroDivisionError'> # exc_type的值
# division by zero # exc_val的值
# <traceback object at 0x016B3468> # exc_tb的值
# finish
# (2)當(dāng)沒(méi)出現(xiàn)異常時(shí)(正常輸出):
# __enter__
# output
# __exit__
# None
# None
# None
# finish
# 過(guò)程:
# main2() --> 進(jìn)入main2()函數(shù)中,創(chuàng)建Resource類的實(shí)例對(duì)象r
# --> 執(zhí)行with丰歌,調(diào)用output方法
# --> 執(zhí)行1/0時(shí)捕獲異常
# --> 回到class Resource類,進(jìn)入__enter__()方法屉凯,輸出__enter__立帖,返回實(shí)例對(duì)象r
# --> 進(jìn)入__exit__()方法,輸出__exit__悠砚,分別輸出exc_type晓勇、exc_val、exc_tb的值灌旧,返回True
# --> output方法中的output不會(huì)輸出
# --> with執(zhí)行完畢后绑咱,輸出finish
【例】按照 contextlib 的協(xié)議實(shí)現(xiàn)一個(gè)文件讀取的上下文管理器(但不支持處理異常)
import contextlib
@contextlib.contextmanager
def open_func(filename):
f = open(filename)
yield f
f.close()
with open_func('mytest.txt') as file_in:
for line in file_in:
print(line, end='')
# hello world
# hello kitty
【例】按照 contextlib 的協(xié)議實(shí)現(xiàn)一個(gè)文件讀取的上下文管理器(允許異常處理)
import contextlib
@contextlib.contextmanager
def open_func(filename):
print('open file' + filename)
f = open(filename)
# 異常處理
try:
yield f
except Exception as e:
print(e)
finally:
f.close()
print('file closed')
with open_func('mytest.txt') as file_in:
for line in file_in:
# 1 / 0
print(line)
# 輸出結(jié)果:
# (1)當(dāng)出現(xiàn)異常時(shí)(1 / 0):
# open filemytest.txt
# division by zero
# file closed
# (2)當(dāng)沒(méi)出現(xiàn)異常時(shí)(正常輸出):
# open filemytest.txt
# hello world
#
# hello kitty
# file closed
6. 比較運(yùn)算符
- lg():判斷
- lt():判斷
- eq():判斷等于
- le():判斷
- ge():判斷
- ne():判斷不等于
【例】比較狗年齡
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def __lt__(self, other):
print('lt')
return self.name < other.name
def __le__(self, other):
print('lt')
return self.name < other.name
d1 = Dog('aa',1)
d2 = Dog('lisi',2)
print(d1 < d2) # d1 < d2 == d2.__lt__(d1)
print(d1 > d2) # d1 > d2 == d2.__gt__(d1)
print(d1 == d2) # d1 == d2 == d2.__eq__(d1)
print(d1 != d2) # d1 != d2 == d2.__ne__(d1)
print(d1 <= d2) # d1 <= d2 == d2.__le__(d1)
print(d1 >= d2) # d1 >= d2 == d2.__ge__(d1)
【例】創(chuàng)建房間和屋子類,計(jì)算屋子面積和比較不同屋子面積的大小
class Room:
def __init__(self, name, length, width):
self.name = name
self.length = length
self.width = width
self.area = length*width
class House:
def __init__(self, name):
self.name = name
self.rooms = []
def add_room(self, room):
self.rooms.append(room)
@property # 保護(hù)類的封裝特性,又要讓開(kāi)發(fā)者可以使用“對(duì)象.屬性”的方式操作操作類屬性
def house_area(self):
lst = [room.area for room in self.rooms]
return sum(lst)
# 定義__eq__()方法后才能進(jìn)行等值比較
# def __eq__(self, other):
# return self.house_area == other.house_area
# def __lt__(self,other):
# return self.house_area < other.house_area
# def __gt__(self,other):
# return self.house_area > other.house_area
# 定義__lt__()方法后才能進(jìn)行比較
__lt__ = lambda self, other: self.house_area < other.house_area
# __le__ = lambda self,other : self < other or self == other
# __gt__ = lambda self,other : not (self<=other)
def __repr__(self):
return self.name
h1 = House('1號(hào)')
h2 = House('2號(hào)')
h3 = House('3號(hào)')
print(h2)
# 2號(hào)
h1.add_room(Room('廁所', 3, 3))
h1.add_room(Room('南屋', 6, 3))
print(f'{h1}屋子的面積為{h1.house_area}')
# 1號(hào)屋子的面積為27
h2.add_room(Room('廁所', 2, 4))
h2.add_room(Room('南屋', 7, 3))
print(f'{h2}屋子的面積為{h2.house_area}')
# 2號(hào)屋子的面積為29
h3.add_room(Room('廁所', 2, 3))
h3.add_room(Room('南屋', 1, 5))
print(f'{h3}屋子的面積為{h3.house_area}')
# 3號(hào)屋子的面積為11
lst = [h1, h2, h3]
print(f'排序前:{lst}')
# 排序前:[1號(hào), 2號(hào), 3號(hào)]
# 一般的實(shí)例不能進(jìn)行比較枢泰,但由于上面定義了__lt__()方法描融,這里就可以進(jìn)行比較和排序
lst.sort()
print(f'排序前:{lst}')
# 排序前:[3號(hào), 1號(hào), 2號(hào)]
print(h1 != h2)
# True
if h1 > h2:
print('1號(hào)小于2號(hào)')
else:
print('1號(hào)大于等于2號(hào)')
# 1號(hào)大于等于2號(hào)
【拓展】lambda表達(dá)式
# def __eq__(self, other):
# pass
# 上面這個(gè)__eq__()方法用lambda表達(dá)式可以寫成:
__eq__ = lambda self, other: self == other
# 同理:
__le__ = lambda self, other: self < other or self == other
__gt__ = lambda self, other: not (self < other or self == other)
__ge__ = lambda self, other: not (self < other)
__ne__ = lambda self, other: not self == other
7. _srt() 和 repr()方法
最好每個(gè)類都有一個(gè)repr()方法
參考: https://blog.csdn.net/z_feng12489/article/details/89708907
調(diào)用順序:
先在當(dāng)前類型中找str() 和 repr(),如果都沒(méi)有定義的話衡蚂,會(huì)到父類object中找str() 和 repr()
【例】對(duì)比str() 和 repr()方法的不同
class Car(object):
def __init__(self, color, mileage):
self.color = color
self.mileage = mileage
def __repr__(self):
print('__repr__')
return f'{self.color} {self.mileage}'
def __str__(self):
print('__str__')
return f'{self.color} {self.mileage}'
car = Car('red', 333)
# 直接打印實(shí)例時(shí)窿克,會(huì)顯示內(nèi)存地址,要想print()打印出有用的內(nèi)容毛甲,就得定義__str__() 或 __repr__()方法
print(car)
# __str__
# red 333
str(car)
# __str__
repr(car)
# __repr__
print(str(car))
# __str__
# red 333
print(repr(car))
# __repr__
# red 333
print(f'this is a {repr(car)}')
# __repr__
# this is a red 333
lst = [car]
print(lst)
# __repr__
# [red 333]
【例】打印日期
import datetime
today = datetime.date(2019, 12, 5) # 實(shí)例化
print(today)
# 2019-12-05
print(str(today))
# 2019-12-05
print(repr(today))
# datetime.date(2019, 12, 5)
# 結(jié)論:object中帶有__str__() 和 __repr__()方法
8. call()
實(shí)現(xiàn)call()方法年叮,把一個(gè)類實(shí)例變成一個(gè)可調(diào)用對(duì)象
【例】call()方法的使用:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __call__(self, friend, best_friend):
print('My name is %s...' % self.name)
print('My friend is %s...' % friend)
print('My best_friend is %s...' % best_friend)
p = Person('xuebi', 'male')
p('zhipeng', 'xiaoru') # 直接調(diào)用Person實(shí)例,括號(hào)中這兩個(gè)值分別傳給了__call__()中的friend和best_friend
# My name is xuebi...
# My friend is zhipeng...
# My best_friend is xiaoru...
# 【例】把實(shí)例對(duì)象用類似函數(shù)的形式表示
class Fib(object):
def __init__(self):
pass
def __call__(self, num):
a, b = 0, 1
self.lst = []
for i in range(num):
self.lst.append(a)
a, b = b, a+b
return self.lst
def __str__(self):
return str(self.lst)
f = Fib()
print(f(10))
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]