本节最主要搞懂魔法方法什么时候被触发(被调用)
调用分为隐式调用和显式调用,而魔法方法都属于隐式调用。
1. 构造和初始化
- new():在实例创建之前被调用的,它的任务就是创建实例然后返回该实例,是个静态方法
- init():当实例对象创建完成后被调用的,然后设置对象属性的一些初始值
当自己创建了__new__()和__init__()同时存在时,会只调用__new__()
解决方法:在__new__()创建实例+返回:
def __new__(cls,...):
instance = object.__new__(cls)
return instance
self是当前实例本身
cls是type类的实例(类对象)
print(type(cls))
——
执行顺序: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__
# # 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. 属性访问控制
- getattr(self, name):访问不存在的属性时调用
- getattribute(self, name):访问存在的属性时调用(先调用该方法,查看是否存在该属性,若不存在,接着去调用①)
- setattr(self, name, value):设置实例对象的一个新的属性时调用
- delattr(self, name):删除一个实例对象的属性时调用
当自己创建了__getattribute__()时,实例名.__dict__得到的时None
解决方法:在__getattribute__()中返回调用父类的__getattribute__():
def __getattribute__(self, item):
return super().__getattribute__(item)
执行顺序:实例化 --> 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 # 这就无限递归了
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 # 这就无限递归了
self.__dict__[key] = value # 正确做法
def __delattr__(self, item):
print('__delattr__')
# del self.item # 无限递归了
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 # 调用__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 # 调用__delattr__()方法
print('-'*50)
# --------------------------------------------------
# __delattr__
# --------------------------------------------------
print('-'*50)
print(f.__dict__)
print('-'*50)
# --------------------------------------------------
# __getattribute__
# __dict__
# {'y': 100, 'z': 3}
# --------------------------------------------------
print('-'*50)
print(f.xxxxxxxxx) # 在__getattribute__()方法中尝试返回时失败,调用__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. 描述符
实现了以下三个方法之一,就是一个描述符
- get():用于访问属性。它返回属性的值,若属性不存在、不合法等都可以抛出对应的异常。
- set():将在属性分配操作中调用。不会返回任何内容。
- delete():控制删除操作。不会返回内容。
【例】成绩处理
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 "".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))
#
print(stu.__dict__)
# {'name': 'xuebi'}
print(stu)
# __get__
# __get__
# __get__
# __get__
#
# 过程:
# 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.?时 --> __get__() --> print('__get__()') --> return self.__score
【例】
class Typed:
def __init__(self, key, expected_type): # 构造函数接收所传入的参数和参数类型
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__(),触发描述符__set__方法,设置参数类型传给构造函数
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)#类型有误,报错
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) # 相应的键值对已在底层字典中删除了,报错
# KeyError: 'name'
4. 构造自定义容器(Container)
- getitem(self,key):返回键对应的值。
- setitem(self,key,value):设置给定键的值
- delitem(self,key):删除给定键对应的元素。
- len():返回元素的数量
【例】从无限数据中自定义生成一个a1为1,d为2的等差数列,输出前十个数
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只删除字典里面的值,而对原本的无限序列没有任何影响
for i in range(10):
print(am[i])
# 1
# 4
# 7
# 10
# 13
# 16
# 19
# 22
# 25
# 28
【例】从无限数据中自定义斐波那契数列
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. 上下文管理
操作文本对象的时候,我们要用 with open ,这就是一个上下文管理的例子。
with open('test.txt') as f:
print f.readlines()
注:
(1)上下文表达式:with open('test.txt') as f:
(2)上下文管理器:open('test.txt')
(3) f 不是上下文管理器,应该是资源对象。
上下文管理协议:
在一个类里,如果实现了enter和exit方法,这个类的实例就是一个上下文管理器。
方法一:
定义一个类,在类中定义enter() 和 exit()方法
- enter():
- exit():
方法二:
不定义类,直接定义生成器函数,在函数名上面添加@contextlib.contextmanager装饰器,在函数中加入yield关键字(作用跟方法一一样)
系统默认调用exit(),如果retrun True,则不会抛出异常
【例】上下文管理器(但不支持处理异常)
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时同样会报错
def output(self):
# 1 / 0
print('output')
def main2():
r = Resource()
# r.output() # 直接调用output方法时会报错,但经过with后就不会报错了
with Resource() as r:
r.output()
print('finish')
main2()
# 输出结果:
# (1)当出现异常时(1 / 0):
# __enter__
# __exit__
# # exc_type的值
# division by zero # exc_val的值
# # exc_tb的值
# finish
# (2)当没出现异常时(正常输出):
# __enter__
# output
# __exit__
# None
# None
# None
# finish
# 过程:
# main2() --> 进入main2()函数中,创建Resource类的实例对象r
# --> 执行with,调用output方法
# --> 执行1/0时捕获异常
# --> 回到class Resource类,进入__enter__()方法,输出__enter__,返回实例对象r
# --> 进入__exit__()方法,输出__exit__,分别输出exc_type、exc_val、exc_tb的值,返回True
# --> output方法中的output不会输出
# --> with执行完毕后,输出finish
【例】按照 contextlib 的协议实现一个文件读取的上下文管理器(但不支持处理异常)
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 的协议实现一个文件读取的上下文管理器(允许异常处理)
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)
# 输出结果:
# (1)当出现异常时(1 / 0):
# open filemytest.txt
# division by zero
# file closed
# (2)当没出现异常时(正常输出):
# open filemytest.txt
# hello world
#
# hello kitty
# file closed
6. 比较运算符
- 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)
【例】创建房间和屋子类,计算屋子面积和比较不同屋子面积的大小
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 # 保护类的封装特性,又要让开发者可以使用“对象.属性”的方式操作操作类属性
def house_area(self):
lst = [room.area for room in self.rooms]
return sum(lst)
# 定义__eq__()方法后才能进行等值比较
# 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__()方法后才能进行比较
__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号')
h2 = House('2号')
h3 = House('3号')
print(h2)
# 2号
h1.add_room(Room('厕所', 3, 3))
h1.add_room(Room('南屋', 6, 3))
print(f'{h1}屋子的面积为{h1.house_area}')
# 1号屋子的面积为27
h2.add_room(Room('厕所', 2, 4))
h2.add_room(Room('南屋', 7, 3))
print(f'{h2}屋子的面积为{h2.house_area}')
# 2号屋子的面积为29
h3.add_room(Room('厕所', 2, 3))
h3.add_room(Room('南屋', 1, 5))
print(f'{h3}屋子的面积为{h3.house_area}')
# 3号屋子的面积为11
lst = [h1, h2, h3]
print(f'排序前:{lst}')
# 排序前:[1号, 2号, 3号]
# 一般的实例不能进行比较,但由于上面定义了__lt__()方法,这里就可以进行比较和排序
lst.sort()
print(f'排序前:{lst}')
# 排序前:[3号, 1号, 2号]
print(h1 != h2)
# True
if h1 > h2:
print('1号小于2号')
else:
print('1号大于等于2号')
# 1号大于等于2号
【拓展】lambda表达式
# def __eq__(self, other):
# pass
# 上面这个__eq__()方法用lambda表达式可以写成:
__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()方法
最好每个类都有一个repr()方法
参考: https://blog.csdn.net/z_feng12489/article/details/89708907
调用顺序:
先在当前类型中找str() 和 repr(),如果都没有定义的话,会到父类object中找str() 和 repr()
【例】对比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)
# 直接打印实例时,会显示内存地址,要想print()打印出有用的内容,就得定义__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) # 实例化
print(today)
# 2019-12-05
print(str(today))
# 2019-12-05
print(repr(today))
# datetime.date(2019, 12, 5)
# 结论:object中带有__str__() 和 __repr__()方法
8. call()
实现call()方法,把一个类实例变成一个可调用对象
【例】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') # 直接调用Person实例,括号中这两个值分别传给了__call__()中的friend和best_friend
# My name is xuebi...
# My friend is zhipeng...
# My best_friend is xiaoru...
# 【例】把实例对象用类似函数的形式表示
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]