魔术方法
属性 | 含义 |
---|---|
__name__ | 类、函数、方法等的名字 |
__module__ | 类定义所在的模块名 |
__class__ | 对象或类所属的类 |
__bases__ | 类的基类的元组,舒徐为他们在基类列表中出现的顺序 |
__doc__ | 类、函数的文档字符串,如果没有定义则为None |
__mro__ | 类的mro,class.mro()返回的结果保存在__mro__中 |
__dict__ | 类或实例的属性,可写的字典 |
查看属性
方法:__dict__,返回类或者对象的所有成员列表。dir函数就是调用__dir__()。如果提供__dir__(),则返回属性的列表,否则会尽量从__dict__属性中收集信息。
- 如果dir([object])参数包含方法__dir__,该方法会被调用。如果参数不包含,该方法将会最大限度的收集参数信息。
- 对于不同类型的对象有不同的行为:如果对象是模块对象,列表包含模块的属性名;如果对象时类型或者类对象,列表包含类的属性名,以及它的基类的属性名;否则列表包含对象的属性名,它的类的属性名和类的基类的属性名。
魔术方法的分类
- 创建和销毁:__init__和__del__
- hash
- bool
- 可视化
- 运算符重载
- 可调用对象
- 上下文管理
- 反射
- 描述器
- 其他
hash
- __hash__内建函数hash调用的返回值,返回一个整数。如果定义这个方法该类的实例就可hash
- __eq__:对应==操作符,判断2个对象值是否相等,返回bool值 __hash__方法只是返回一个hash值作为set的key,但是去重,先要用is判断是不是同一个,然后再用__eq__来判断是否值相等。 hash值相等,只是hash冲突,不能说明两个对象是相等的。因此一般来说提供__hash__方法是为了作为set或者dict的key的,所以去重要同时是提供__eq__方法。 可hash对象必须提供__hash__方法,没有的话,isinstance(p1,collections,HASHABLE)一定为False。
class Point():
def __init__(self,x,y):
self.x = x
self.y = y
def __repr__(self):
return 'Point({},{})'.format(self.x,self.y)
def __eq__(self,other):
return self.x == other.x and self.y == other.y
a = Point(3,4)
b = Point(3,4)
print(a.__repr__(b))
print({a,b})
bool
内建(函数bool(),或者对象放在逻辑表达式的位置,调用这个函数返回布尔值。没有定义__bool__(),就找__len__()返回长度,非0为真。如果__len__()也没有定义,那么所有实例都返回真。
class A:
pass
print(bool(A()))
class B:
def __bool__(self):
return False
print(bool(B))
print(bool(B()))
class C:
def __len__(self):
return 0
print(bool(A()))
可视化 方法|意义 ———-|———— __repr__ |内建函数repr()对一个对象获取字符串表达。如果一个类定义了__repr__但没有有__str__,那么再请求该类的实例的非正式的字符串标识是,也将调用__repr__ __str__ | str函数、内建函数format、print函数调用,需要返回对象的字符串表达 __bytes__ | bytes的时候,返回一个对象的bytes表达,即返回bytes对象
class A:
def __init__(self):
self.a ='a'
self.b = 'b'
def __eq__(self, other):
return False
def __repr__(self):
return 'REPR:{}({},{})'.format(self.__class__.__name__,self.a,self.b)
def __str__(self):
return 'STR:{}({},{})'.format(self.__class__.__name__,self.a,self.b)
print(A())
print([A()])
print(([str(A())]))
运算符重载
operator模块提供一下的特殊方法,可以将类的实例使用下面的操作付来操作:
运算符 | 特殊方法 | 含义 |
---|---|---|
<,<=,==,>,>=,!= | __lt__,__le__,__eq__,__gt__,__ge__,__ne__ | 比较运算符 |
+,-,*, /,%,//,**,divmod | __add__,__sub__,__mul__,__truediv__,__mod__,__floordiv__,__pow__,__divmod__ | 算数运算符,位运算符也有对应的方法 |
+=,-=,*=,/=,%=,//=,**= | __iadd__,__isub__,__imul__,__itruediv__,__imod__,__floordiv__,__ipow__ |
- 练习:完成point类设计,实现判断点相等的方法,病完成向量的加法
class Point:
def __init__(self,x,y):
self.x = x
self.y = y
def __eq__(self,other):
return self.x == other.x and self.y == other.y
def __add__(self,other):
return Point(self.x + other.x,self.y + other.y)
def __repr__(self):
return 'Point({},{})'.format(self.x,self.y)
p1 = Point(3,4)
p2 = Point(5,9)
p3 = Point(5,9)
print(p1 + p2)
print(p2 == p3,p2 is p3)
- 运算符重载应用场景 在做大量连续的运算时,使用运算符这种数学上常见的表达方式非常简单、方便。提供运算符重载,比直接提供加方法更加适合程序员的变成习惯。 int类中几乎实现了所有操作符,可以作为设计类的参考。
容器相关方法
方法 | 意义 |
---|---|
__len__ | 內建函数len(),返回对象的长度(>=0的证书),其实即使把对象当做容器类型看,就如同list或者dict。bool()函数调用的时候,如果没有__bool__()方法,就会查看__len__()方法是否存在,存在返回非0为真 |
__iter__ | 迭代容器时调用,返回一个新的迭代器对象 |
__contains__ | in成员运算符,没有实现就调用iter方法遍历 |
__getitem__ | 实现self[key]访问。序列对象,可以接受整数为索引,或者切片。对于set和dict,key就是可hash的类型。可以不存在引发KeyError异常 |
__setitem__ | 和getitem的访问能累死,是设置值的方法 |
__missing__ | 字典使用getitem调用时,key不存在执行此方法 |
- 练习:将购物车改造成方便操作的容器类
class Item:
def __init__(self,**kwargs):
self._property = kwargs
def __repr__(self):
return 'name:{} $:{} color:{}'.format(*self._property.values())
class ShopCar:
def __init__(self):
self.items = []
def additem(self,item:Item):
self.items.append(item)
def __iter__(self):
return iter(self.items)
def __len__(self):
return len(self.items)
def __getitem__(self, index):
return self.items[index]
def __setitem__(self,index, value):
self.items[index] = value
myshopcar = ShopCar()
myphone = Item(name='sumsung',price=4898,color='black')
mycar = Item(name='meserati',price=288,color='red')
myshopcar.additem(myphone)
myshopcar.additem(mycar)
print(myshopcar[1])
# myshopcar[0] = None
for item in myshopcar:
print(item)
print(len(myshopcar))
print(myshopcar.__dict__)
print(mycar.__dict__,myphone.__dict__)
可调用对象
Python中一切皆对象,函数也是一样。
def foo():
print(foo.__name__,foo.__name__)
foo() #等价于foo.__call__()
函数就是对象,对象foo加上(),就是调用对象的__call__方法,在自定义类中定义该方法,那么类的实例就可以像函数对象一样加个括号调用。
- 练习:定义一个斐波那契数列的类,方便调用,计算第n项
class Fib:
def __init__(self):
self.seq = [0,1,1]
def __len__(self):
return len(self.seq)
def __call__(self,count):
if count < len(self):#此处的判断是为了减少计算,当已经算过的时候就不再需要重新计算
return self.seq[count]
for n in range(len(self)-1,count):#使用实例数列长度来作为起点可以减少计算,不必每次都从头算起。
self.seq.append(self.seq[n-1]+self.seq[n])
return self.seq[count]
def __getitem__(self,index):
if index < 0:
raise IndexError('Wrong Index')
if index > len(self):#此处的判断是如果需要的项没有计算出来就立即计算
return self(index)
return self.seq[index]
def __iter__(self):
return iter(self.seq)
上下文管理
文件IO操作可以对文件对象使用上下文管理,使用with…as语法。
with open('test') as f:
pass
- 上下文管理对象:当一个对象同事实现了__enter__()和__exit__()方法,他就属于上下文管理的对象
方法 | 意义 |
---|---|
__enter__ | 进入与此对象相关的上下文。如果存在该方法,with语法会把该方法的返回值作为绑定到as子句中指定的变量上 |
__exit__ | 退出与此对象相关的上下文。 |
class Point:
def __init__(self):
print('init')
def __enter__(self):
print('enter')
def __exit__(self, exc_type, exc_val, exc_tb):
print('exit')
with Point() as f:
print("let's do it")
根据上例发现,实例化对象的时候,并不会调用enter,当进入with语句块调用enter方法,然后执行语句块,最后退出上下文管理时,调用exit方法。由此表明,with可以开启一个上下文运行环境,在执行前做一些操作,执行后做一些收尾工作。
- 上下文管理的安全性 分别看下异常和极端情况下对下上下文管理的影响
import sys
class Point:
def __init__(self):
print('init')
def __enter__(self):
print('enter')
def __exit__(self, exc_type, exc_val, exc_tb):
print('exit')
with Point() as f:
raise Exception('error')
# sys.exit() #sys模块方法,可直接退出当前解释器
print("let's do it")
print('outer le')
上个例子的返回值中观察发现,不管是报错还是强制退出解释器,enter和exit方法都会执行,说明上下文管理很安全
- enter和exit方法
- enter方法没有其他参数,他的返回值就是上下文中使用的对象,with语法会把他的返回值赋给as子句的变量。
- exit方法有3个参数,exctype、excvalue、tracback,这三个参数都与异常有关,分别是异常类型、异常的返回值、异常的追踪消息。
- exit方法返回一个等效True的值,则压制异常;否则继续抛出异常
- 练习:使用上下文管理显示加法函数的执行时长
import datetime,time
class TimeIt:
def __init__(self,func):
self._fn = func
def __enter__(self):
self.start = datetime.datetime.now()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print((datetime.datetime.now() - self.start).total_seconds())
def add(x,y):
time.sleep(3)
return x+y
with TimeIt() as f:
print(add(7,9))
- 如果要求写成如下形式,代码要如何修改
with TimeIt(add) as foo:
foo(4,5)
实现如下:
import datetime,time
class TimeIt:
def __init__(self,func):
self._fn = func
def __call__(self,x,y):
return self._fn(x,y)
def __enter__(self):
self.start = datetime.datetime.now()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print((datetime.datetime.now() - self.start).total_seconds())
def add(x,y):
time.sleep(3)
return x+y
with TimeIt() as foo:
print(foo(7,9))
根据以上代码,将类当做装饰器来装饰函数
import datetime,time
from functools import wraps
class TimeIt:
def __init__(self,func):
self._fn = func
wraps(func)(self) #复制add的函数该有的属性到实例add,等价于@warps(func)
def __enter__(self):
self.start = datetime.datetime.now()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print((datetime.datetime.now() - self.start).total_seconds())
def __call__(self,x,y): #使实例可调用的魔法方法
return self._fn(x,y)
@TimeIt #等价于add = TimeIt(add)
def add(x,y):
"""This is a add function"""
time.sleep(3)
return x+y
add(4,5)
print(add.__dict__)
- 上下文应用场景
- 增强功能:在代码执行的前后增加代码,以增强其功能。类死装饰器的功能
- 资源管理:打开的资源需要关闭,例如文件对象、网络连接、数据库连接等
- 权限验证:在执行代码前,在enter方法中做权限的验证
- context.contextmanager 它是一个装饰器,装饰一个函数也实现了上下文管理,而且不用像类一样实现enter和exit方法。 对下面的函数有要求,必须有yield,也就是这个函数必须返回一个生成器,且只有yield一个值。
import contextlib
@contextlib.contextmanager
def foo():
print('enter')
yield #yield的值只能有一个,作为enter方法的返回值
print('exit')
with foo() as f:
#raise EXCEPTION
print(f)
上面的代码看起来很正常,当时增加一个异常后发现不能保证exit的执行。
import contextlib
@contextlib.contextmanager
def foo():
print('enter')
try:
yield #yield的值只能有一个,作为enter方法的返回值
finaly:
print('exit')
with foo() as f:
raise EXCEPTION
print(f)
当增加了try\finaly语句之后发现exit又可以执行。在yield发生处为生成器函数增加了上下文管理。
- 总结:如果业务逻辑简单可以使用函数加装饰器方式,如果业务复杂,用类的方式加enter和exit更加方便。
本文来自投稿,不代表Linux运维部落立场,如若转载,请注明出处:http://www.178linux.com/88766