魔术方法

查看类的魔术方法

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 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。即凡是在代码块前后插入代码的场景统统适用

  1. 资源管理
  2. 权限验证

以下以计时器为例

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

三个函数的原型:

  1. getattr:getattr(object, name[, default]) -> value。getattr(x, 'y')等效于x.y
  2. setattr:setattr(obj, name, value, /)。setattr(x, 'y', v)等效于x.y = v
  3. 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入门必看:面向对象之典型魔术方法相关推荐

  1. python 舍去小数_零基础小白Python入门必看——编程基础概念

    1. 程序的构成 程序由模块组成,一个模块对应python的源文件 ,一般后缀为:.py 模块由语句构成 语句是python程序的构造单元,用于创建对象.变量赋值.调用函数.控制语句等. 2. 对象 ...

  2. 小白到学会python要多久_零基础小白多久能学会python

    学习任何一门编程语言,都是为了去实现一个个项目,来解决实际的问题.无论项目是大还是小,都关联着许多知识与技能. 例如要写一个「文件资源管理器」的应用,就需要MVC设计模式.组件化构建.对象集合及操作. ...

  3. 类似零基础学python的小说_零基础小白十分钟用Python搭建小说网站!Python真的强!...

    零基础小白十分钟用Python搭建小说网站!Python真的强!-1.jpg (128.29 KB, 下载次数: 0) 2018-10-8 18:51 上传 Python 和放大镜的二进制代码 人生苦 ...

  4. 0基础学好python难不难_零基础学习Python难不难?Python有什么优势?

    原标题:零基础学习Python难不难?Python有什么优势? Python是一种计算机程序设计语言.首先,我们普及一下编程语言的基础知识.用任何编程语言来开发程序,都是为了让计算机干活,比如下载一个 ...

  5. 明日科技的python书籍怎么样_零基础学习Python不可错过的5本书籍

    3.Python基础教程(第3版) 作者:[挪]芒努斯·利·海特兰德(Magnus Lie Hetland) 出版社:人民邮电出版社 Python3.5编程从入门到实践,Python入门佳作,机器学习 ...

  6. python数据参数_零基础学习python数据分析——函数的参数

    原标题:零基础学习python数据分析--函数的参数 上一节课中我们讲了python的函数定义,Python的函数定义非常简单,但灵活度却非常大.除了正常定义的必选参数外,还可以使用默认参数.可变参数 ...

  7. python 智能造句_[零基础学Python]正规地说一句话

    小孩子刚刚开始学说话的时候,常常是一个字一个字地开始学,比如学说"饺子",对他/她来讲,似乎有点难度,大人也聪明,于是就简化了,用"饺饺"来代替,其实就是让孩子 ...

  8. 有c语言基础学python容易吗_零基础学Python之前需要学c语言吗

    Python本身是比较适合作为入门编程语言来学习的,一方面Python的语法结构比较简单清晰,实验也相对比较容易完成,这会逐渐增强初学者的学习信心,另一方面Python属于全场景编程语言,未来在很多领 ...

  9. python实现表格_零基础小白怎么用Python做表格?

    用Python操作Excel在工作中还是挺常用的,因为毕竟不懂Excel是一个用户庞大的数据管理软件.本文用Python3!在给大家分享之前呢,小编推荐一下一个挺不错的交流宝地,里面都是一群热爱并在学 ...

最新文章

  1. 敏捷过程、极限编程和SCRUM的关系
  2. 更新elementui图标不显示_超简单elementui主题及变量修改方案
  3. 从命令行列出所有环境变量?
  4. mysql join 算法_【MySQL】之join算法详解
  5. oracle 11g函数包缓存,Oracle11新特性——PLSQL函数缓存结果(一)
  6. C# 8.0 默认接口实现
  7. python输入多个坐标点_判断多个坐标是否在同一条直线上|Python练习系列[13]
  8. 用一句话证明你是程序员,你会怎么说
  9. [翻译] Haneke(处理图片缓存问题)
  10. 送你一个Python 数据排序的好方法
  11. 四种转换方式:自动,强制,Parse,Convert
  12. 14种模式解决面试算法编程题(PART II)
  13. android中上拉下滑布局,3年以上勿进!最简单的Android自定义ListView下拉刷新与上拉加载,代码直接拿去用~...
  14. C++for循环经典九九乘法表打印
  15. ORA-00932: 数据类型不一致:应为-,但却获得NCLOB
  16. HUAS Summer Trainning #3 M
  17. git 同时连接云效平台和github
  18. uni-app开发小程序并运行起来(使用ColorUI)
  19. mount point / 挂载点
  20. TCP/IP网络编程复习(上)

热门文章

  1. TensorFlow基础11-(小批量梯度下降法和梯度下降法的优化)
  2. python print 的使用方法
  3. 七、使用栈实现综合计算器(中缀表达式)
  4. 云环境上如何使用tensorboard
  5. Linux/Debian/Ubuntu报错解决:W: Target Packages (main/binary-amd64/Packages) is configured multiple times
  6. Linux下VS Code中C/C++开发环境的includePath设置
  7. 中北大学c语言程序设计作业答案,2016年中北大学软件学院程序设计基础考研复试题库...
  8. php开发编程中心,Php编程
  9. 台式机BIOS被加密后密码忘记解决办法
  10. java主动抛出400异常_400个线程同时查询数据,抛出一个异常