python魔术方法
魔术方法
一、实例化
方法 | 意义 |
---|---|
__new__ |
实例化一个对象,该方法需要返回一个值,如果该值不是cls的实例,则不会调用__init__ 该方法永远都是静态方法 |
class A:
def __new__(cls, *args, **kwargs):
print(cls)
print(args)
print(kwargs)
return None
def __init__(self,name):
self.name = name
a = A(1,2)
print(a)
#输出
<class '__main__.A'>
(1, 2)
{}
None
__new__
方法很少使用,即使创建了该方法,也会使用return super().__new__(cls)
基类obejct的__new__
方法来创建实例并返回。
二、可视化
方法 | 意义 |
---|---|
__str__ |
str()函数、format()函数、print()函数调用,需要返回对象的字符串表达,如果没有定义,就去调用__repr__ 方法返回字符串表达,如果__repr__ 也没有定义,就直接返回对象的内存地址信息。 |
__repr__ |
内建函数repr() 对一个对象获取字符串表达。 调用 __repr__ 方法返回字符串表达,如果__repr__ 也没有定义,就直接返回object的定义就是显示内存地址信息 |
__bytes__ |
bytes()函数调用,返回一个对象的bytes表达,即返回bytes对象 |
class A:
def __init__(self,name,age=16):
self.name = name
self.age = age
def __str__(self):
return "str = {}--{}".format(self.name,self.age)
def __repr__(self):
return "repr = {}__{}".format(self.name,self.age)
def __bytes__(self):
import json
return json.dumps(self.__dict__).encode()
print(A('tom')) # print函数使用__str__
print('{}'.format(A('tom')))
print([A('tom')]) # []使用__str__,但其内部使用__repr__
print([str(A('tom'))]) # []使用__str__,其中的元素使用str()函数也调用__str__
print(bytes(A('tom')))
#输出
str = tom--16
str = tom--16
[repr = tom__16]
['str = tom--16']
b'{"name": "tom", "age": 16}'
三、bool
方法 | 意义 |
---|---|
__bool__ |
内建函数bool(),或者对象放在逻辑表达式的位置,调用这个函数返回布尔值。 没有定义 __bool ** ,就找__len__() 返回长度,非0为真。如果__len__()** 也没有定义,那么所有实例都返回真 |
class A:pass
a = A()
print(bool(A))
print(bool(a))
class B:
def __bool__(self):
return False
print(bool(B))
print(bool(B()))
if B():
print('Real B instance')
class C:
def __len__(self):
return 1
print(bool(C))
print(bool(C()))
if C():
print("Real C instance")
#输出
True
True
True
False
True
True
Real C instance
注意:__bool__
等函数只会影响实例,不会影响类
四、运算符重载
operator模块提供以下的特殊方法,可以将类的实例使用下面的操作符来操作
运算符 | 特殊方法 | 含义 |
---|---|---|
<,<=,==,>,>=,!= | __lt__ ,__le__ ,__eq__ ,__gt__ ,__ge__ ,__ne__ |
比较运算符 |
+,-,*,/,%,//, **,divmod | __add__ ,__sub__ ,__mul__ , __truediv__ ,__mod__ ,__floordiv__ ,__pow__ ,__divmod__ |
算数运算符,移位、位运算也有对应的方法 |
+=,-+,*=,/=,%=,//=,**= | __iadd ** ,__isub ,**__imul__ ,__itruediv__ ,__imod__ ,__ifloordiv__ ,__ipow__ |
实现自定义类的实例的大小比较(非常重要,排序时使用)
class A:
pass
print(A() == A()) #可以
print(A() > A()) #不可以
class A:
def __init__(self, name ,age =18):
self.name = name
self.age = age
def __eq__(self, other):
print('~~~~~eq')
return self.name == other.name and self.age == other.age
def __gt__(self, other):
print("~~~~gt")
return self.age > other.age
def __ge__(self, other):
print("~~~~ge")
return self.age >= other.age
tom = A('tom')
jerry = A('jerry', 16)
print(tom == jerry, tom != jerry)
print(tom > jerry, tom < jerry)
print(tom >= jerry, tom <= jerry)
#输出
~~~~~eq
~~~~~eq
False True
~~~~gt
~~~~gt
True False
~~~~ge
~~~~ge
True False
__eq__
等于可以推断不等于
__gt__
大于可以推断小于
__ge__
大于等于可以推断小于等于
也就是用三个方法就可以把所有比较解决了
实现两个学生成绩差
class A:
def __init__(self, name ,score):
self.name = name
self.score = score
tom = A('tom',67)
jerry = A('jerry',62)
print(tom.score - jerry.score)
5
class A:
def __init__(self, name ,score):
self.name = name
self.score = score
def __sub__(self, other):
print("~~~~~sub")
return self.score - other.score
def __isub__(self, other):
print("~~~~~isub")
self.score -= other.score
return self
def __str__(self):
print("~~~~~str")
return "<{}--{}>".format(self.name,self.score)
tom = A('tom',87)
jerry = A('jerry',62)
print(tom.score - jerry.score)
print(tom-jerry)
jerry -= tom
print(tom)
print(jerry)
#输出
25
~~~~~sub
25
~~~~~isub
~~~~~str
<tom--87>
~~~~~str
<jerry---25>
list的+和+=区别。tuple呢
五、容器相关方法
方法 | 意义 |
---|---|
__len__ |
内建函数len(),返回对象的长度(>=0的整数),如果把对象当做容器类型看,就如同list或dict。 bool()函数调用的时候,如果没有 __bool__() 方法, 则会看__len__() 方法是否存在,存在返回非0为真 |
__iter__ |
迭代容器时,调用,返回一个新的迭代器对象 |
__container__ |
in成员运算符,没有实现,就调用__iter__方法遍历 |
__getitem__ | 实现sefl[key]访问,序列对象,key接受整数位索引,或者切片。对于set和dict,key位hashable。key不存在引发keyerror异常 |
__setitem__ | 和__getitem__ 的访问类似,是设置值的方法 |
__missing__ | 字典或其子类使用__getitem__() 调用时,key不存在执行该方法 |
class A(dict):
def __missing__(self,key):
print('Missing key :', key)
return 0
a = A()
print(a['k'])
应用
设计一个购物车,能够方便增加商品,能够方便的遍历
class Cart:
def __init__(self):
self.items = []
def __len__(self):
return len(self.items)
def __str__(self):
return str(self.items)
def __iter__(self):
return iter(self.items)
def __missing__(self, key):
print("missing :",key)
return 0
def __getitem__(self, item):
return self.items[item]
def __setitem__(self, key, value):
self.items[key] = value
def additem(self,*args):
self.items.extend(args)
def __contains__(self, item):
print("~~~~~~~~~~~~~~~~",item)
if item in self.items:
return True
else:
return False
cart = Cart()
cart.additem('香蕉','电视',23,"haha")
print(cart)
cart[0] = "葫芦波"
print(cart)
for n in cart:
print(n)
print(len(cart),bool(cart))
print("haha" in cart)
#输出
['香蕉', '电视', 23, 'haha']
['葫芦波', '电视', 23, 'haha']
葫芦波
电视
23
haha
4 True
~~~~~~~~~~~~~~~~ haha
True
#购物车
class Cart:
def __init__(self):
self.items = []
def __len__(self):
return len(self.items)
def additem(self, item):
self.items.append(item)
def __iter__(self):
# yield from self.items
return iter(self.items)
def __getitem__(self, index):
# 索引访问
return self.items[index]
def __setitem__(self, key, value):
# 索引赋值
self.items[key] = value
def __str__(self):
return str(self.items)
def __add__(self, other):
# +
self.items.append(other)
return self cart = Cart()
# 长度、bool
print(cart, bool(cart), len(cart))
cart.additem(1)
cart.additem('abc')
cart.additem(3)
# 长度、bool
print(cart, bool(cart), len(cart))
# 迭代
for x in cart:
print(x)
# in
print(3 in cart)
print(2 in cart)
# 索引操作
print(cart[1])
cart[1] = 'xyz'
print(cart)
# 链式编程实现加法
print(cart + 4 + 5 + 6)
print(cart.__add__(17).__add__(18))
可调用对象
def foo():
print(foo.__module__,foo.__name__)
foo()
#等价于
foo.__call__()
函数即对象,对象foo加上()就是调用此函数对象的__call__()方法
方法 | 意义 |
---|---|
__call__ | 类中定义一个该方法,实例就可以像函数一样调用 |
可调用对象:定义一个类,并实例化得到其 实例,将实例像函数一样调用
class Point:
def __init__(self,x,y):
self.x = x
self.y = y
def __call__(self, *args, **kwargs):
return '<point {}:{}>'.format(self.x,self.y)
p = Point(4,6)
print(p)
print(p())
#输出
<__main__.Point object at 0x7f9fa805bfa0>
<point 4:6>
#累加
class Adder:
def __call__(self, *args):
self.result = sum(args)
return self.result
adder = Adder()
print(adder(*range(4,7)))
print(adder.result)
#输出
15
15
应用
定义一个斐波那契数列的类,方便调用,计算第n项。
增加迭代数列的方法、返回数列长度、支持索引查找数列项的方法。
class Fib:
def __init__(self):
self.items = [0,1,1]
def __getitem__(self, item):
print(item)
if item < 0:
raise IndexError('下标越界')
if item >= len(self):
for n in range(len(self.items),item+1):
self.items.append(self.items[n-1] + self.items[n-2])
print("数组:{}".format(self.items))
return self.items[item]
def __call__(self, index):
return self[index]
def __str__(self):
return str(self.items)
def __setitem__(self, key, value):
self.items[key] = value
def __iter__(self):
return iter(self.items)
def __len__(self):
return len(self.items)
f = Fib()
print(f[9],f(4))
#输出
9
数组:[0, 1, 1, 2]
数组:[0, 1, 1, 2, 3]
数组:[0, 1, 1, 2, 3, 5]
数组:[0, 1, 1, 2, 3, 5, 8]
数组:[0, 1, 1, 2, 3, 5, 8, 13]
数组:[0, 1, 1, 2, 3, 5, 8, 13, 21]
数组:[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
4
34 3
上下文管理
文件IO操作可以对文件对象使用上下文管理,使用with…as 语法
with open('path') as f:
pass
仿照上例写一个自己的类,实现上下文管理
class Point:
pass
with Point() as p: #这里会报错
pass
#报错信息
Traceback (most recent call last):
File "/Users/dujie/PycharmProjects/t1/context.py", line 3, in <module>
with Point() as p:
AttributeError: __enter__
提示属性错误,没有__enter__或__exit__属性
上下文管理对象
当一个对象同时实现的__enter__() 和__exit__() 方法,她就属于上下文管理的对象
方法 | 意义 |
---|---|
__enter__ | 进入与此对象相关的上下文。如果存在该方法,with语法会吧该方法的返回值作为绑定到as子句中指定的变量上 |
__exit__ | 退出与此对象相关的上下文 |
__enter__ return self. 的时候a1才是a的别名,a==a1 ,a is a1
__exit__ return 值如果等价为True,压制异常,不报异常
mport time
class Point:
def __init__(self):
print('1,init~~~~~~')
time.sleep(1)
def __enter__(self):
print('2,enter~~~~')
time.sleep(1)
def __exit__(self, exc_type, exc_val, exc_tb):
print('3,exit~~~~~')
time.sleep(1)
with Point() as f:
print('4,with start')
time.sleep(1)
print('5,with end')
#输出
1,init~~~~~~
2,enter~~~~
4,with start
5,with end
3,exit~~~~~
实例化对象的时候,并不会调用enter,进入with语句块调用__enter__方法,然后狗执行语句体,最后离开with语句块的时候,调用__exit__方法。
with可以开启一个上下文运行环境,在执行前做一些准备工作,执行后做一些收尾工作。注意,with并不开启一个新的作用域
上下文管理的安全性
看看异常对上下文的影响
import time
class Point:
def __init__(self):
print('1,init~~~~~~')
time.sleep(1)
def __enter__(self):
print('2,enter~~~~')
time.sleep(1)
def __exit__(self, exc_type, exc_val, exc_tb):
print('3,exit~~~~~')
time.sleep(1)
with Point() as f:
print('4,with start')
raise Exception('error')
time.sleep(1)
print('5,with end')
#输出
1,init~~~~~~
2,enter~~~~
4,with start
3,exit~~~~~
Traceback (most recent call last):
File "/Users/dujie/PycharmProjects/t1/context.py", line 15, in <module>
raise Exception('error')
Exception: error
可以看到,在抛出异常的情况下,with的__exit__ 照样执行,上下文管理是安全的
with语句
class Point:
def __init__(self):
print('1,init~~~~~~')
def __enter__(self):
print('2,enter~~~~')
def __exit__(self, exc_type, exc_val, exc_tb):
print('3,exit~~~~~')
f = open('111.py')
with f as p:
print(f)
print(p)
print(f is p) #打印True
print(f == p) #打印True
p = f = None
p = Point()
with p as f :
print("in with~~~~")
print(p == f) #打印False,因为问题在与__enter__方法,这个方法会将返回值赋给f,因为上面enter 返回值为None所以这里是False
print('with over')
修改上例
class Point:
def __init__(self):
print('1,init~~~~~~')
def __enter__(self):
print('2,enter~~~~')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('3,exit~~~~~')
p = Point()
with p as f :
print("in with~~~~")
print(p == f) #此时就是True,因为enter 方法返回值为self,即是p,会将p赋给f,所以相等
print('with over')
with语法,会调用with后的对象的__enter__方法,如果有as,则将该方法的返回值赋给as 子句的变量。
上例,可以等价为f = p.__enter__()
上下文应用场景
- 增强功能
- 在代码执行的前后增加代码,以增加其功能。类似装饰器功能。
- 资源管理
- 打开了资源需要关闭,例如文件对象、网络连接、数据库连接等
- 权限验证
- 在执行代码之前,做权限的验证,在__enter__中处理
上下文应用
如何用支持上线文的类来对add函数计时
import time
import datetime
def add(x, y):
return x + y
class Point:
def __init__(self,fn):
self.__fn = fn
print('1,init~~~~~~')
def __enter__(self):
print('2,enter~~~~')
self.start = datetime.datetime.now()
return self.__fn
def __exit__(self, exc_type, exc_val, exc_tb):
print(exc_tb,exc_type,exc_val)
print('3,exit~~~~~')
result = (datetime.datetime.now() - self.start).total_seconds()
print("tooks: {}s".format(result))
def __call__(self, *args, **kwargs):
return self.__fn(*args,**kwargs)
with Point(add) as f:
print(f(4,6))
print(add(4,7))
使用contextmanager实现上下文管理
from contextlib import contextmanager
import datetime
import time
def add(x, y):
return x + y
@contextmanager
def test(fn):
print('开始前')
start = datetime.datetime.now()
try:
yield fn
finally:
print('开始后')
end = (datetime.datetime.now() - start).total_seconds()
print('结束了 {}'.format(end))
with test(add) as f:
print(f(4,6))
反射
运行时,runtime,区别于编译时,指的是程序被加载到内存中执行的时候。
反射,reflection,指的是运行时获取类型定义信息。
一个对象能后在运行时,像照镜子一样,反射出其类型信息。
简单说,在Python中,能够通过一个对象,找出其type、class、attribute或method的能力,称为反射或自省。
具有反射能力的函数有type()、isinstance()、callable()、dir()、getattr()等
内建函数 | 意义 |
---|---|
getattr(object,name[,defalut]) | 通过name返回object的属性值。当属性不存在时,将使用defalut返回,如果没有defalut,则抛出AttributeError。name必须为字符串 |
setattr(object,name,value) | object的属性存在,则覆盖,不存在,新增 |
hasattr(object,name) | 判断对象是否有这个名字的属性,name必须为字符串 |
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p1 = Point(4,6)
print(p1.x ,p1.y)
4 6
print(getattr(p1, 'x'))
4
print(getattr(p1, 'y'))
6
print(getattr(p1, 'z',100))
100
setattr(p1, 'x', 1000)
print(p1.x)
1000
print(getattr(p1, 'x'))
1000
反射相关的魔术方法
__getattr__()、__setattr__()、__delattr__() 这三个魔术方法,分别测试这三个方法
__getattr__()
当属性找不到的时候调用,返回值就是该属性的值,实例字典z?mro中从左到右所有类开始找
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __getattr__(self, item):
print('getattr')
print(item)
return 100
p1 = Point(4,6)
print(p1.x ,p1.y)
4 6
print(p1.g)
getattr
g
100
实例属性查找顺序为:
instance.__dict__ –> instance.__class__.__dict
__setattr__()
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __getattr__(self, item):
print('getattr')
print(item)
return 100
def __setattr__(self, key, value):
print('setattr~~ {} = {}'.format(key,value))
p1 = Point(4,6)
print(p1.x ,p1.y)
print(p1.__dict__)
setattr~~ x = 4
setattr~~ y = 6
getattr
x
getattr
y
100 100
{}
p1 的实例字典里什么都没有,而且访问x和y属性的时候竟然访问到了__getattr__()
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __getattr__(self, item):
print('getattr')
print(item)
return 100
def __setattr__(self, key, value):
print('setattr~~ {} = {}'.format(key,value))
self.__dict__[key] = value
p1 = Point(4,6)
print(p1.x ,p1.y)
print(p1.__dict__)
#输出
setattr~~ x = 4
setattr~~ y = 6
4 6
{'x': 4, 'y': 6}
__setattr__() 方法,可以拦截对实例属性的增加、修改操作,如果要设置生效,需要自己操作实例的__dict__
__delattr__()
class Point:
z = 100
def __init__(self, x, y):
self.x = x
self.y = y
def __delattr__(self, item):
print('delattr,{}'.format(item))
p1 = Point(4,6)
del p1.x
del p1.z
print(p1.__dict__)
print(Point.__dict__)
del Point.z
print(Point.__dict__)
#输出
delattr,x
delattr,z
{'x': 4, 'y': 6}
{'__module__': '__main__', 'z': 100, '__init__': <function Point.__init__ at 0x7fee9811c820>, '__delattr__': <function Point.__delattr__ at 0x7fee9811c790>, '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None}
{'__module__': '__main__', '__init__': <function Point.__init__ at 0x7fee9811c820>, '__delattr__': <function Point.__delattr__ at 0x7fee9811c790>, '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None}
Process finished with exit code 0
通过实例删除属性,就会尝试调用该魔术方法
__getattribute__
class Point:
Z = 100
def __init__(self, x, y):
self.x = x
self.y = y
p1 = Point(4, 5)
print(p1.x, p1.y)
print(Point.Z, p1.Z)
print('-' * 30)
# 为Point类增加__getattribute__,观察变化
class Point:
Z = 100
def __init__(self, x, y):
self.x = x
self.y = y
def __getattribute__(self, item):
print(item)
p1 = Point(4, 5)
print(p1.x, p1.y)
print(Point.Z, p1.Z)
print('-' * 30)
######输出
4 5
100 100
------------------------------
x
y
None None
Z
100 None
------------------------------
实例的所有属性访问,第一个都会调用__getattribute__ 方法,它阻止了属性的查找,该方法应该返回(计算后的)值或者抛出一个AttributeError异常。
- 它的return值将作为属性查找的结果。
- 如果抛出AttributeError异常,则会直接调用__getattr__ 方法,因为表示属性没有找到
class Point:
Z = 100
def __init__(self, x, y):
self.x = x
self.y = y
def __getattr__(self, item):
return 'missing {}'.format(item)
def __getattribute__(self, item):
print(item)
#return object.__getattribute__(self,item)
# return super().__getattribute__(item)
p1 = Point(4, 5)
print(p1.x, p1.y)
print(p1.ss)
print(Point.Z, p1.Z)
print(p1.__dict__)
print('-' * 30)
#输出
x
y
4 5
ss
missing ss
Z
100 100
__dict__
{'x': 4, 'y': 5}
------------------------------
__getattribute__ 方法中为了避免在该方法中无限的递归,它的实现应该永远调用基类的同名方法以访问需要的任何属性,例如object.__getattribute__(self,name)。或者super().__getattribute__(item)
一般不要使用它
总结:
魔术方法 | 意义 |
---|---|
__getattr__() | 当通过搜索实例、实例的类及祖先类查不到属性,就会调用此方法 |
__setattr__() | 通过 . 访问实例属性,进行增加、修改都要调用它 |
__delattr__() | 当通过实例来删除属性时调用此方法 |
__getattribute__ | 实例所有的属性调用都从这个方法开始 |
实例属性查找顺序:
实例调用__getattribute__() ——> 实例字典(instance.__dict__) ——> 实例的类的类字典(instance.__class__.__dict__) ——>继承的祖先类(直到object)的__dict__ ——>调用__getattr__()
__hash__
方法 | 意义 |
---|---|
__hash__ | 内建函数hash()调用的返回值,返回一个整数。如果定义这个方法该类的实例就可hash |
print(hash(1))
print(hash('tom'))
print(hash(('tom',)))
#输出
1
8137844429138158502
-7052210149325465434
class A:
def __init__(self,name):
self.name = name
def __hash__(self):
return 1
def __repr__(self):
return self.name
a1 = A('tom')
a2 = A('tom')
print(a1,a2,hash(a1),hash(a2))
#输出
tom tom 1 1
print([a1,a2])
print((a1,a2))
print({a1,a2})
print({a1,a1})
#输出
[tom, tom]
(tom, tom)
{tom, tom}
{tom}
#元组
t1 = ('tom',)
t2 = ('tom',)
print(t1 is t2)
print(t1 == t2)
print({t1,t2})
#输出
True
True
{('tom',)}
上例子中,A的实例放在set中,他们hash值是相同的,为什么不能去重?
hash值相同就会去重吗?
class A:
def __init__(self,name):
self.name = name
def __hash__(self):
return 1
def __repr__(self):
return self.name
def __eq__(self, other):
return self.name == other.name
a1 = A('tom')
a2 = A('tom')
print({a1,a2})
print({A('jerry'),A('jerry')})
方法 | 意义 |
---|---|
__eq__ | 对应==操作符,判断2个对象内容是否相等,返回bool值 定义了这个方法,如果不提供__hash__方法,那么实例将不可hash |
__hash__ 方法只是返回一个hash值作为set的key,但是去重,还需要__eq__来判断2个对象是否相等。
hash值相等,只是hash冲突,不能说明两个对象是相等的
因此,一般来说实现__hash__方法,要同时实现__eq__方法。去重依赖__eq__方法
不可hash对象isinstancee(p1,collections.Hashable)一定为False
思考:
1. list类实例为什么不可hash?
源码中有一句 __hash__ = None,也就是如果调用 __hash__ ()相当于None(),一定报错。
所有类都继承object,而这个类是具有 __hash__ ()方法的,如果一个类不能被hash,就把
__hash__ 设置为None。
2. functools.lru_cache使用到的functools._HashedSeq类继承自list,为什么可hash?
_HashedSeq类提供了__hash__方法,这个方法实际上计算的是元组的hash值
练习
设计二维坐标类Point,使其成为可hash类型,并比较2个坐标的实例是否相等?
class Point:
def __init__(self, x , y):
self.x = x
self.y = y
def __hash__(self):
return hash((self.x,self.y))
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __repr__(self):
return "<Point: {}:{}>".format(self.x,self.y)
a = Point(4,5)
b = Point(4,5)
print(a == b)
print(a is b )
print(hash(a))
print(hash(b))
print(a,b)
#输出
True
False
-1009709641759730766
-1009709641759730766
<Point: 4:5> <Point: 4:5>