python 运算符重载_零基础小白Python入门必看:面向对象之典型魔术方法
魔术方法
查看类的魔术方法
class A: passdir(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__ 。
这里多说一句,小编是一名python开发工程师,这里有我自己整理了一套最新的python系统学习教程,包括从基础的python脚本到web开发、爬虫、数据分析、数据可视化、机器学习等。想要这些资料的可以关注小编,并在后台私信小编:“01”即可领取。
创建/销毁
- __new__: object.__new__(cls) 创建类的方法:构造函数
- __del__:删除类:析构函数
- __init__:初始化函数
class A: def __new__(cls, *args, **kwargs): print('new') return object.__new__(cls) def __init__(self): print('init') self.x = 3 def __del__(self): print('del')A() # 返回一个类<__main__.a at># 输出newinita = A()del a # 输出del复制代码
每当实例空间被收回时(在垃圾收集时),__del__就会自动执行。
运算符重载
class Point: def __init__(self, x, y): self.x = x self.y = y def __add__(self, other): return Point(self.x + other.x, self.y + other.y) def __sub__(self, other): return Point(self.x - other.x, self.y - other.y)a = Point(0, 0)b = Point(3, 5)c = a + bc += Point(4, 6)print(c.x, c.y) # 7, 11 p = Point(3, 5) - Point(2, 1)print(p.x, p.y) # 1, 4复制代码
类的对象之间可以进行加减运算,只要类实现了加减运算对应的魔术方法即可。加法的具体实现是__add__,减法的具体实现是__sub__。
- 具体运算符对应的重载函数可以参考int类中运算符重载的实现:help(int)
不要过度使用运算符重载
Point.__add__ = lambda self, value: self - valuep = Point(3, 5) + Point(4, 6)print(p.x, p.y) # 输出-1, -1复制代码
__add__的具体实现如果写成了减法,这种类型的错误非常不容易发现,因此如果不是在写库给第三方使用的时候,基本用不上运算符重载。
hash
- 使用内置函数hash对某个对象求hash值时, 会调用对象的__hash__方法,示例代码如下
In [1]: class Point: ...: def __hash__(self): ...: return 1 ...: In [2]: hash(Point())Out[2]: 1复制代码
- __hash__方法必须返回int,否则会抛出TypeError
In [1]: class Point: ...: def __hash__(self): ...: return 'aaa' ...: In [2]: hash(Point())---------------------------------------------------------------------------TypeError Traceback (most recent call last) in ()----> 1 hash(Point())TypeError: __hash__ method should return an integer复制代码
- 可hash对象,就是具有__hash__方法的对象
In [6]: class Point: ...: def __hash__(self): ...: return 1 ...: In [7]: set([Point(), 12]) # 可hashOut[7]: {<__main__.point at>, 12}In [8]: Point.__hash__ = NoneIn [9]: set([Point(), 12]) # 不能放在集合里面,因为不能hash---------------------------------------------------------------------------TypeError Traceback (most recent call last) in ()----> 1 set([Point(), 12])TypeError: unhashable type: 'Point'复制代码
- 一个类如果没有重写__hash__方法的话,这个类的每个对象,通常具有不同的hash
In [1]: class Point: ...: pass ...: In [2]: p1 = Point()In [3]: p2 = Point()In [4]: hash(p1)Out[4]: 8757059543567In [5]: hash(p2)Out[5]: 8757059543756复制代码
- 通常 __hash__ 会和 __eq__一起使用, 因为解释器通常同时判断hash是否相等以及实例是否相等
class Point: def __init__(self, x, y): self.x = x self.y = y def __hash__(self): return hash('{}:{}'.format(self.x, self.y)) def __eq__(self, other): return self.x == other.x and self.y == other.yp1 = Point(3, 5)p2 = Point(3, 5)set([p1, p2]) # 返回 {<__main__.point at>}hash(p1) == hash(p2) # 返回Truep1 == p2 # 返回True复制代码
大小
当对象实现了__len__方法时,可以使用内置方法len求对象的长度, __len__方法必须返回非负整数
lst = [1, 2, 3]len(lst) # 返回3lst.__len__() # 返回3复制代码
因此内置函数和__len__方法的效果相同。
class Sized: def __len__(self): return 10len(Sized()) # 返回10复制代码
bool
- 当对象o实现了__bool__ 方法时, bool(o)返回值为o.__bool__()
class F: def __bool__(self): return Falsebool(F()) # 返回Falseclass T: def __bool__(self): return Truebool(T()) # 返回True复制代码
- 当对象o没有实现__bool__方法时,如果o实现了__len__方法, bool(o)返回值为 len(o) != 0
class L: def __len__(self): return 3bool(L()) # 返回Trueclass Q: def __len__(self): return 0bool(Q()) # 返回False复制代码
- 当对象o既没有实现__bool__方法,也没有实现 __len__方法的时候, bool(o)返回值为True
class Boolean: passbool(Boolean()) # 返回True复制代码
- __bool__优先级比__len__更高
class Sized: def __init__(self, size): self.size = size def __len__(self): return self.size def __bool__(self): return self.size == 0bool(Sized(0)) # 返回Truebool(Sized(10)) # 返回False复制代码
- __bool__方法必须返回bool类型
class B: def __bool__(self): return None # 返回非bool类型的值时会出错,即使返回int型的也会报错bool(B())---------------------------------------------------------------------------TypeError Traceback (most recent call last) in ()----> 1 bool(B())TypeError: __bool__ should return bool, returned NoneType复制代码
可视化
- __str__方法,print函数本质是调用对象的__str__方法,用于给人读
- __repr__方法,repr函数本质是调用对象的__repr__方法,用于给机器读
class Point: def __init__(self, x, y): self.x = x self.y = y def __str__(self): # 给人来读 return 'Point'.format(self.x, self.y) def __repr__(self): # 给机器读的 return 'Point({}, {})'.format(self.x, self.y)print(Point(3, 5)) # Point<3, 5>print(repr(Point(3, 5))) # Point(3, 5)复制代码
repr:返回对象的规范化的字符串表示
可调用对象
class Fn: def __call__(self): print('{} called'.format(self))f = Fn()f()# 输出<__main__.fn object at> called复制代码
一个对象,只要实现了__call__方法, 就可以通过小括号来来调用, 这一类对象,称之为可调用对象
给对象加上函数也就是对__call__方法加上参数:
class Add: def __call__(self, x, y): return x + yAdd()(3, 5) # 返回8,等价于 add =Add() add(3, 5)复制代码
可调用对象的应用实例:实现可过期可换出的cache装饰器
import inspectimport datetimefrom functools import wrapsclass 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 + yadd(1, 2) # 返回3复制代码
用__call__来实现可调用对象,和闭包是殊途同归的,通常是为了封装一些内部状态
上下文管理
支持上下文管理的对象
class Context: def __enter__(self): print('enter context') def __exit__(self, *args, **kwargs): print('exit context')复制代码
当一个对象同时实现了__enter__和__exit__方法,那么这个对象就是支持上下文管理的对象。
支持上下文管理的对象可以使用以下语句块进行处理:
with obj: pass复制代码
比如
with Context(): print('do somethings')print('out of context')# 输出enter contextdo somethingsexit contextout of context复制代码
所以,with开启一个语句块, 执行这个语句块之前,会执行 __enter__方法, 执行这个语句块之后,会执行__exit__ 方法,也就是说在这个语句块的前后会执行一些操作,因此也叫上下文。
- 即使with块抛出异常,__enter__和__exit__也会被执行,所以上下文管理是安全的。
with Context(): raise Exception()enter contextexit context---------------------------------------------------------------------------Exception Traceback (most recent call last) in () 1 with Context():----> 2 raise Exception()Exception: 复制代码
- 即使with块中主动退出解释器, __enter__ 和__exit__也能保证执行
import syswith Context(): sys.exit()enter contextexit contextAn 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)复制代码
with块的as字句
- as子句可以获取__enter__方法的返回值
class Context: def __enter__(self): print('enter context') return self # __enter__函数的返回值 def __exit__(self, *args, **kwargs): print('exit context')ctx = Context()with ctx as c: print(id(ctx)) print(id(c)) print(c)# 输出结果enter context140541332713712140541332713712<__main__.context object at>exit context复制代码
__enter__方法
- __enter__方法的返回值可以被as字句捕获到
- __enter__ 除self之外,不带任何参数
class Context: def __enter__(self, *args, **kwargs): print('enter context') print(args) print(kwargs) def __exit__(self, *args, **kwargs): print('exit context')# 输出enter context(){}exit context复制代码
args和kwargs都是空的,因此上下文管理的时候__enter__函数除self外,不带任何参数。
__exit__方法
- __exit__的返回值,没有办法获取到,如果with块中抛出异常 __exit__返回False的时候,会向上抛出异常,返回True, 会屏蔽异常
class Context: def __enter__(self): print('enter context') def __exit__(self, *args, **kwargs): print('exit context') return 'haha'with Context() as c: print(c)# 输出enter contextNoneexit context复制代码
- __exit__的三个参数 异常类型, 异常, traceback
class Context: def __enter__(self): print('enter context') def __exit__(self, *args, **kwargs): print('exit context') print(args) print(kwargs)with Context(): pass# 输出enter contextexit context(None, None, None){}复制代码
args输出三个None,表示三个位置参数,kwargs为空,表示没有关键字参数。
with Context(): raise Exception()enter contextexit context(, Exception(), ){}---------------------------------------------------------------------------Exception Traceback (most recent call last) in () 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 Truewith Context(): raise TypeError('hahaha')# 输出enter contextexit contextexception type: exception value: hahahaexception traceback: 复制代码
上下文管理的应用场景
with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。即凡是在代码块前后插入代码的场景统统适用
- 资源管理
- 权限验证
以下以计时器为例
from functools import wrapsclass 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@Timeitdef add(x, y): return x + yadd(3, 8) # 输出0:00:00.000044 返回11复制代码
总共实现了两种计时方式,既可以对语句块计时,也可以对函数计时。
contextmanager的使用
contextlib是个比with优美的东西,也是提供上下文管理机制的模块,它是通过Generator装饰器实现的,不再是采用__enter__和__exit__。contextlib中的contextmanager作为装饰器来提供一种针对函数级别的上下文管理机制。
import contextlib@contextlib.contextmanagerdef context(): print('enter context') # 初始化部分 相当于 __enter__ 方法 try: yield 'haha' # 相当于__enter__的返回值 finally: print('exit context') # 清理部分, 相当于 __exit__ 方法with context() as c: print(c) raise Exception()# 输出enter contexthahaexit context---------------------------------------------------------------------------Exception Traceback (most recent call last) in () 1 with context() as c: 2 print(c)----> 3 raise Exception()Exception: 复制代码
yield后面必须配合finally使用,否则如果抛出异常,程序不会执行yield后面的部门,也就是不会执行__exit__部分。
反射
python的反射,核心本质其实就是利用字符串的形式去对象(模块)中操作(查找/获取/删除/添加)成员,就是一种基于字符串的事件驱动!
关于模块的python反射以及反射机制分析参见:python反射机制深入分析
以下主要分析类对象的反射机制
getattr setattr hasattr
三个函数的原型:
- getattr:getattr(object, name[, default]) -> value。getattr(x, 'y')等效于x.y
- setattr:setattr(obj, name, value, /)。setattr(x, 'y', v)等效于x.y = v
- hasattr: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增加属性hahap.haha # 返回abcdhasattr(p, 'print') # 返回True复制代码
setattr的对象是实例,如果要给实例动态增加方法,需要先把函数转化为方法,转化的方法如下:
import typesdef 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()# 输出 >>>cmd1cmd1>>>cmd2cmd2>>>cmd3not found cmd cmd3>>>quit复制代码
__getattr__ __setattr__ __delattr__
- 当一个类定义了__getattr__方法时,如果访问不存在的成员,会调用__getattr__方法
class A: def __init__(self): self.x = 3a = A()a.x # 返回3a.y # 如果没有实现__getattr__方法,当访问不存在的成员时会报错---------------------------------------------------------------------------AttributeError Traceback (most recent call last) in ()----> 1 a.yAttributeError: 'A' object has no attribute 'y'复制代码
增加__getattr__方法
class A: def __init__(self): self.x = 3 def __getattr__(self, name): return 'missing property {}'.format(name)a = A()a.x # 返回3a.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 # 返回3a.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 # 返回3del a.x # 输出you cannot delete property: x复制代码
最后多说一句,小编是一名python开发工程师,这里有我自己整理了一套最新的python系统学习教程,包括从基础的python脚本到web开发、爬虫、数据分析、数据可视化、机器学习等。想要这些资料的可以关注小编,并在后台私信小编:“01”即可领取。
python 运算符重载_零基础小白Python入门必看:面向对象之典型魔术方法相关推荐
- python 舍去小数_零基础小白Python入门必看——编程基础概念
1. 程序的构成 程序由模块组成,一个模块对应python的源文件 ,一般后缀为:.py 模块由语句构成 语句是python程序的构造单元,用于创建对象.变量赋值.调用函数.控制语句等. 2. 对象 ...
- 小白到学会python要多久_零基础小白多久能学会python
学习任何一门编程语言,都是为了去实现一个个项目,来解决实际的问题.无论项目是大还是小,都关联着许多知识与技能. 例如要写一个「文件资源管理器」的应用,就需要MVC设计模式.组件化构建.对象集合及操作. ...
- 类似零基础学python的小说_零基础小白十分钟用Python搭建小说网站!Python真的强!...
零基础小白十分钟用Python搭建小说网站!Python真的强!-1.jpg (128.29 KB, 下载次数: 0) 2018-10-8 18:51 上传 Python 和放大镜的二进制代码 人生苦 ...
- 0基础学好python难不难_零基础学习Python难不难?Python有什么优势?
原标题:零基础学习Python难不难?Python有什么优势? Python是一种计算机程序设计语言.首先,我们普及一下编程语言的基础知识.用任何编程语言来开发程序,都是为了让计算机干活,比如下载一个 ...
- 明日科技的python书籍怎么样_零基础学习Python不可错过的5本书籍
3.Python基础教程(第3版) 作者:[挪]芒努斯·利·海特兰德(Magnus Lie Hetland) 出版社:人民邮电出版社 Python3.5编程从入门到实践,Python入门佳作,机器学习 ...
- python数据参数_零基础学习python数据分析——函数的参数
原标题:零基础学习python数据分析--函数的参数 上一节课中我们讲了python的函数定义,Python的函数定义非常简单,但灵活度却非常大.除了正常定义的必选参数外,还可以使用默认参数.可变参数 ...
- python 智能造句_[零基础学Python]正规地说一句话
小孩子刚刚开始学说话的时候,常常是一个字一个字地开始学,比如学说"饺子",对他/她来讲,似乎有点难度,大人也聪明,于是就简化了,用"饺饺"来代替,其实就是让孩子 ...
- 有c语言基础学python容易吗_零基础学Python之前需要学c语言吗
Python本身是比较适合作为入门编程语言来学习的,一方面Python的语法结构比较简单清晰,实验也相对比较容易完成,这会逐渐增强初学者的学习信心,另一方面Python属于全场景编程语言,未来在很多领 ...
- python实现表格_零基础小白怎么用Python做表格?
用Python操作Excel在工作中还是挺常用的,因为毕竟不懂Excel是一个用户庞大的数据管理软件.本文用Python3!在给大家分享之前呢,小编推荐一下一个挺不错的交流宝地,里面都是一群热爱并在学 ...
最新文章
- 敏捷过程、极限编程和SCRUM的关系
- 更新elementui图标不显示_超简单elementui主题及变量修改方案
- 从命令行列出所有环境变量?
- mysql join 算法_【MySQL】之join算法详解
- oracle 11g函数包缓存,Oracle11新特性——PLSQL函数缓存结果(一)
- C# 8.0 默认接口实现
- python输入多个坐标点_判断多个坐标是否在同一条直线上|Python练习系列[13]
- 用一句话证明你是程序员,你会怎么说
- [翻译] Haneke(处理图片缓存问题)
- 送你一个Python 数据排序的好方法
- 四种转换方式:自动,强制,Parse,Convert
- 14种模式解决面试算法编程题(PART II)
- android中上拉下滑布局,3年以上勿进!最简单的Android自定义ListView下拉刷新与上拉加载,代码直接拿去用~...
- C++for循环经典九九乘法表打印
- ORA-00932: 数据类型不一致:应为-,但却获得NCLOB
- HUAS Summer Trainning #3 M
- git 同时连接云效平台和github
- uni-app开发小程序并运行起来(使用ColorUI)
- mount point / 挂载点
- TCP/IP网络编程复习(上)
热门文章
- TensorFlow基础11-(小批量梯度下降法和梯度下降法的优化)
- python print 的使用方法
- 七、使用栈实现综合计算器(中缀表达式)
- 云环境上如何使用tensorboard
- Linux/Debian/Ubuntu报错解决:W: Target Packages (main/binary-amd64/Packages) is configured multiple times
- Linux下VS Code中C/C++开发环境的includePath设置
- 中北大学c语言程序设计作业答案,2016年中北大学软件学院程序设计基础考研复试题库...
- php开发编程中心,Php编程
- 台式机BIOS被加密后密码忘记解决办法
- java主动抛出400异常_400个线程同时查询数据,抛出一个异常