查看类的魔术方法
class A: pass dir(A) # 可以得到类所有公有成员输出结果如下
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']在Python中,所有以__双下划线包起来的方法,都统称为魔术方法。比如最常见的 __init__ 。
每当实例空间被收回时(在垃圾收集时),__del__就会自动执行。
类的对象之间可以进行加减运算,只要类实现了加减运算对应的魔术方法即可。加法的具体实现是__add__,减法的具体实现是__sub__。
具体运算符对应的重载函数可以参考int类中运算符重载的实现:help(int)不要过度使用运算符重载
Point.__add__ = lambda self, value: self - value p = Point(3, 5) + Point(4, 6) print(p.x, p.y) # 输出-1, -1__add__的具体实现如果写成了减法,这种类型的错误非常不容易发现,因此如果不是在写库给第三方使用的时候,基本用不上运算符重载。
当对象实现了__len__方法时,可以使用内置方法len求对象的长度, __len__方法必须返回非负整数
lst = [1, 2, 3] len(lst) # 返回3 lst.__len__() # 返回3因此内置函数和__len__方法的效果相同。
class Sized: def __len__(self): return 10 len(Sized()) # 返回10repr:返回对象的规范化的字符串表示
一个对象,只要实现了__call__方法, 就可以通过小括号来来调用, 这一类对象,称之为可调用对象
给对象加上函数也就是对__call__方法加上参数:
class Add: def __call__(self, x, y): return x + y Add()(3, 5) # 返回8,等价于 add =Add() add(3, 5)可调用对象的应用实例:实现可过期可换出的cache装饰器
import inspect import datetime from functools import wraps class Cache: def __init__(self, size=128, expire=0): self.size = size self.expire = 0 self.data = {} @staticmethod def make_key(fn, args, kwargs): ret = [] names = set() params = inspect.signature(fn).parameters keys = list(params.keys()) for i, arg in enumerate(args): ret.append((keys[i], arg)) names.add(keys[i]) ret.extend(kwargs.items()) names.update(kwargs.keys()) for k, v in params.items(): if k not in names: ret.append((k, v.default)) ret.sort(key=lambda x: x[0]) return '&'.join(['{}={}'.format(name, arg) for name, arg in ret]) def __call__(self, fn): @wraps(fn) def wrap(*args, **kwargs): key = self.make_key(fn, args, kwargs) now = datetime.datetime.now().timestamp() if key in self.data.keys(): value, timestamp, _ = self.data[key] if expire == 0 or now - timestamp < expire: self.data[key] = (value, timestamp, now) return value else: self.data.pop(key) value = fn(*args, **kwargs) if len(self.data) >= self.size: # 过期清理 if self.expire != 0: expires = set() for k, (_, timestamp, _) in self.data.items(): if now - timestamp >= self.expire: expires.add(k) for k in expires: self.data.pop(k) if len(self.data) >= self.size: # 换出 k = sorted(self.data.items(), key=lambda x: x[1][2])[0][0] self.data.pop(k) self.data[key] = (value, now, now) return value return wrap @Cache() def add(x, y): return x + y add(1, 2) # 返回3用__call__来实现可调用对象,和闭包是殊途同归的,通常是为了封装一些内部状态
当一个对象同时实现了__enter__和__exit__方法,那么这个对象就是支持上下文管理的对象。
支持上下文管理的对象可以使用以下语句块进行处理:
with obj: pass比如
with Context(): print('do somethings') print('out of context') # 输出 enter context do somethings exit context out of context所以,with开启一个语句块, 执行这个语句块之前,会执行 __enter__方法, 执行这个语句块之后,会执行__exit__ 方法,也就是说在这个语句块的前后会执行一些操作,因此也叫上下文。
即使with块抛出异常,__enter__和__exit__也会被执行,所以上下文管理是安全的。 with Context(): raise Exception() enter context exit context --------------------------------------------------------------------------- Exception Traceback (most recent call last) <ipython-input-126-c1afee4bfdab> in <module>() 1 with Context(): ----> 2 raise Exception() Exception: 即使with块中主动退出解释器, __enter__ 和__exit__也能保证执行 import sys with Context(): sys.exit() enter context exit context An exception has occurred, use %tb to see the full traceback. SystemExit /home/clg/.pyenv/versions/3.5.2/envs/normal/lib/python3.5/site-packages/IPython/core/interactiveshell.py:2889: UserWarning: To exit: use 'exit', 'quit', or Ctrl-D. warn("To exit: use 'exit', 'quit', or Ctrl-D.", stacklevel=1)args和kwargs都是空的,因此上下文管理的时候__enter__函数除self外,不带任何参数。
args输出三个None,表示三个位置参数,kwargs为空,表示没有关键字参数。
with Context(): raise Exception() enter context exit context (<class 'Exception'>, Exception(), <traceback object at 0x7f28608fdc88>) {} --------------------------------------------------------------------------- Exception Traceback (most recent call last) <ipython-input-145-c1afee4bfdab> in <module>() 1 with Context(): ----> 2 raise Exception() Exception: 使用变量接受__exit__的三个参数:exc_type,exc_value,traceback class Context: def __enter__(self): print('enter context') def __exit__(self, exc_type, exc_value, traceback): print('exit context') print('exception type: {}'.format(exc_type)) print('exception value: {}'.format(exc_value)) print('exception traceback: {}'.format(traceback)) return True with Context(): raise TypeError('hahaha') # 输出 enter context exit context exception type: <class 'TypeError'> exception value: hahaha exception traceback: <traceback object at 0x7fd257c18608>with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。即凡是在代码块前后插入代码的场景统统适用
资源管理权限验证以下以计时器为例
from functools import wraps class Timeit: def __init__(self, fn=None): wraps(fn)(self) def __call__(self, *args, **kwargs): start = datetime.datetime.now() ret = self.__wrapped__(*args, **kwargs) cost = datetime.datetime.now() - start print(cost) return ret def __enter__(self): self.start = datetime.datetime.now() def __exit__(self, *args): cost = datetime.datetime.now() - self.start print(cost) with Timeit(): z = 3 + 8 # 输出0:00:00.000037 @Timeit def add(x, y): return x + y add(3, 8) # 输出0:00:00.000044 返回11总共实现了两种计时方式,既可以对语句块计时,也可以对函数计时。
contextlib是个比with优美的东西,也是提供上下文管理机制的模块,它是通过Generator装饰器实现的,不再是采用__enter__和__exit__。contextlib中的contextmanager作为装饰器来提供一种针对函数级别的上下文管理机制。
import contextlib @contextlib.contextmanager def context(): print('enter context') # 初始化部分 相当于 __enter__ 方法 try: yield 'haha' # 相当于__enter__的返回值 finally: print('exit context') # 清理部分, 相当于 __exit__ 方法 with context() as c: print(c) raise Exception() # 输出 enter context haha exit context --------------------------------------------------------------------------- Exception Traceback (most recent call last) <ipython-input-189-4c1dae6b647a> in <module>() 1 with context() as c: 2 print(c) ----> 3 raise Exception() Exception:yield后面必须配合finally使用,否则如果抛出异常,程序不会执行yield后面的部门,也就是不会执行__exit__部分。
python的反射,核心本质其实就是利用字符串的形式去对象(模块)中操作(查找/获取/删除/添加)成员,就是一种基于字符串的事件驱动!
关于模块的python反射以及反射机制分析参见:python反射机制深入分析
以下主要分析类对象的反射机制
三个函数的原型:
getattr:getattr(object, name[, default]) -> value。getattr(x, 'y')等效于x.ysetattr:setattr(obj, name, value, /)。setattr(x, 'y', v)等效于x.y = vhasattr:hasattr(obj, name, /)主要作用是通过对象的成员名称获取对象的成员
class Point: def __init__(self, x, y): self.x = x self.y = y def print(self, x, y): print(x, y) p = Point(3, 5) p.__dict__['x'] # 返回3, 对于属性来说,可以通过 __dict__ 获取 getattr(p, 'print')(3, 5) # 成员方法无法通过__dict__获取,但是可以通过getattr函数获取 # p.print(3, 5) getattr(p, 'x') # getattrr 也可以获取到属性 setattr(p, 'haha', 'abcd') # p.haha = 'abcd',给对象p增加属性haha p.haha # 返回abcd hasattr(p, 'print') # 返回Truesetattr的对象是实例,如果要给实例动态增加方法,需要先把函数转化为方法,转化的方法如下:
import types def mm(self): print(self.x) setattr(p, 'mm', types.MethodType(mm, p)) # 将mm函数转化为对象p的方法之后,再给p增加 p.mm() # 输出3使用getattr setattr hasattr 实现一个命令路由器:
class Command: def cmd1(self): print('cmd1') def cmd2(self): print('cmd2') def run(self): while True: cmd = input('>>>').strip() if cmd == 'quit': return getattr(self, cmd, lambda :print('not found cmd {}'.format(cmd)))() command = Command() command.run() # 输出 >>>cmd1 cmd1 >>>cmd2 cmd2 >>>cmd3 not found cmd cmd3 >>>quit增加__getattr__方法
class A: def __init__(self): self.x = 3 def __getattr__(self, name): return 'missing property {}'.format(name) a = A() a.x # 返回3 a.y # 返回'missing property y'。即访问不存在的成员,会调用__getattr__方法 当一个类实现了__setattr__时, 任何地方对这个类的对象增加属性,或者对现有属性赋值,都会调用__setattr__ class A: def __init__(self): self.x = 3 def __setattr__(self, name, value): print('set {} to {}'.format(name, value)) setattr(self, name, value) a = A() a.x # 返回3 a.y = 5 # 输出set y to 5 当一个类实现了__delattr__ 方法时,删除其实例的属性,会调用此方法 class A: def __init__(self): self.x = 3 def __delattr__(self, name): print('you cannot delete property: {}'.format(name)) a = A() a.x # 返回3 del a.x # 输出you cannot delete property: x记得帮我点赞哦!
精心整理了计算机各个方向的从入门、进阶、实战的视频课程和电子书,按照目录合理分类,总能找到你需要的学习资料,还在等什么?快去关注下载吧!!!
念念不忘,必有回响,小伙伴们帮我点个赞吧,非常感谢。
我是职场亮哥,YY高级软件工程师、四年工作经验,拒绝咸鱼争当龙头的斜杠程序员。
听我说,进步多,程序人生一把梭
如果有幸能帮到你,请帮我点个【赞】,给个关注,如果能顺带评论给个鼓励,将不胜感激。
职场亮哥文章列表:更多文章
本人所有文章、回答都与版权保护平台有合作,著作权归职场亮哥所有,未经授权,转载必究!