python创建实例会调用哪些魔术方法_Python面向对象的魔术方法

魔术方法

查看类的魔术方法

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__ 。

创建/销毁

__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>

# 输出

new

init

a = 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 + b

c += 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 - value

p = 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]) # 可hash

Out[7]: {<__main__.point at>, 12}

In [8]: Point.__hash__ = None

In [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]: 8757059543567

In [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.y

p1 = Point(3, 5)

p2 = Point(3, 5)

set([p1, p2]) # 返回 {<__main__.point at>}

hash(p1) == hash(p2) # 返回True

p1 == p2 # 返回True

复制代码

大小

当对象实现了__len__方法时,可以使用内置方法len求对象的长度, __len__方法必须返回非负整数

lst = [1, 2, 3]

len(lst) # 返回3

lst.__len__() # 返回3

复制代码

因此内置函数和__len__方法的效果相同。

class Sized:

def __len__(self):

return 10

len(Sized()) # 返回10

复制代码

bool

当对象o实现了__bool__ 方法时, bool(o)返回值为o.__bool__()

class F:

def __bool__(self):

return False

bool(F()) # 返回False

class T:

def __bool__(self):

return True

bool(T()) # 返回True

复制代码

当对象o没有实现__bool__方法时,如果o实现了__len__方法, bool(o)返回值为 len(o) != 0

class L:

def __len__(self):

return 3

bool(L()) # 返回True

class Q:

def __len__(self):

return 0

bool(Q()) # 返回False

复制代码

当对象o既没有实现__bool__方法,也没有实现 __len__方法的时候, bool(o)返回值为True

class Boolean:

pass

bool(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 == 0

bool(Sized(0)) # 返回True

bool(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 + 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__来实现可调用对象,和闭包是殊途同归的,通常是为了封装一些内部状态

上下文管理

支持上下文管理的对象

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 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)

in ()

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)

复制代码

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 context

140541332713712

140541332713712

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 context

None

exit 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 context

exit context

(None, None, None)

{}

复制代码

args输出三个None,表示三个位置参数,kwargs为空,表示没有关键字参数。

with Context():

raise Exception()

enter context

exit 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 True

with Context():

raise TypeError('hahaha')

# 输出

enter context

exit context

exception type:

exception value: hahaha

exception traceback:

复制代码

上下文管理的应用场景

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

复制代码

总共实现了两种计时方式,既可以对语句块计时,也可以对函数计时。

contextmanager的使用

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)

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增加属性haha

p.haha # 返回abcd

hasattr(p, 'print') # 返回True

复制代码

setattr的对象是实例,如果要给实例动态增加方法,需要先把函数转化为方法,转化的方法如下:

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__ __setattr__ __delattr__

当一个类定义了__getattr__方法时,如果访问不存在的成员,会调用__getattr__方法

class A:

def __init__(self):

self.x = 3

a = A()

a.x # 返回3

a.y # 如果没有实现__getattr__方法,当访问不存在的成员时会报错

---------------------------------------------------------------------------

AttributeError Traceback (most recent call last)

in ()

----> 1 a.y

AttributeError: '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 # 返回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

复制代码

记得帮我点赞哦!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值