魔术方法

查看类的魔术方法

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

这里多说一句,小编是一名python开发工程师,这里有我自己整理了一套最新的python系统学习教程,包括从基础的python脚本到web开发、爬虫、数据分析、数据可视化、机器学习等。想要这些资料的可以进裙930900780领取。

创建/销毁

  • __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 0x7f4a84767978>
# 输出
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)
<ipython-input-5-a919dcea3eae> in <module>()
----> 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 0x7f19d4073320>, 12} In [8]: Point.__hash__ = None In [9]: set([Point(), 12]) # 不能放在集合里面,因为不能hash
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-10-25999920b521> in <module>()
----> 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 0x7f286092d588>}
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)
<ipython-input-80-4efbb03885fe> in <module>()
----> 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 0x7fd254367470> 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)
<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)
复制代码

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

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

以下以计时器为例

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)
<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 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增加属性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)
<ipython-input-228-cc7049c6eeec> in <module>()
----> 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
复制代码

最后多说一句,小编是一名python开发工程师,这里有我自己整理了一套最新的python系统学习教程,包括从基础的python脚本到web开发、爬虫、数据分析、数据可视化、机器学习等。想要这些资料的可以进裙930900780领取。

本文章素材来源于网络,如有侵权请联系删除。

零基础小白Python入门必看:面向对象之典型魔术方法的更多相关文章

  1. 学习笔记(1):零基础掌握 Python 入门到实战-列表与元祖到底该用哪个?(二)...

    立即学习:https://edu.csdn.net/course/play/26676/338778?utm_source=blogtoedu 列表不能通过增加索引增加元素 可以使用list中的app ...

  2. 学Java必看!零基础小白再也不用退缩了

    程序员们!请往这儿看 对于JAVA的学习,可能你还会有许多的顾虑 不要担心 接着往下看吧 学Java前 一.数学差,英语也不好是不是学不好Java? 答案是:是~ 因为你在问这个问题的时候说明你对自己 ...

  3. 零基础学习 Python 之前期准备

    写在之前 从今天开始,我将开始新的篇章 -- 零基础学习 Python,在这里我将从最基本的 Python 写起,然后再慢慢涉及到高阶以及具体应用方面.我是完全自学的 Python,所以很是明白自学对 ...

  4. 零基础学习 Python 之数字与运算

    写在之前 大家好,这里是零基础学习 Python 系列,在这里我将从最基本的 Python 写起,然后再慢慢涉及到高阶以及具体应用方面.我是完全自学的 Python,所以很是明白自学对于一个人的考验, ...

  5. Java编程学习知识点分享 入门必看

    Java编程学习知识点分享 入门必看 阿尔法颜色组成(alpha color component):颜色组成用来描述颜色的透明度或不透明度.阿尔法组成越高,颜色越不透明. API:应用编程接口.针对软 ...

  6. 零基础学习Python数据分析

    网上虽然有很多Python学习的教程,但是大多是围绕Python网页开发等展开.数据分析所需要的Python技能和网页开发等差别非常大,本人就是浪费了很多时间来看这些博客.书籍.所以就有了本文,希望能 ...

  7. Liunx新手入门必看

    安装CentOS(Linux的一个常用发行版本,互联网公司经常使用这个发行版)用到的软件: VMware_workstation_full_12.5.2.exe 虚拟机软件,虚拟机由这个软件安装.管理 ...

  8. 零基础学习 Python 之字符串

    初识字符串 维基百科对于字符串的定义式:字符串是由零个或者多个字符组成的有限串行.你之前学会敲的第一行 print 代码里的 "Hello World",就是一个字符串.字符串的本 ...

  9. 《零基础学习Python制作ArcGIS自定义工具》课程简介

    Python for ArcGIS Python for ArcGIS是借助Python语言实现ArcGIS自动化行为的综合,它不止是如课程标题所述的“制作ArcGIS自定义工具”,还包括使用Pyth ...

随机推荐

  1. Java中的IO操作和缓冲区

    目录 Java中的IO操作和缓冲区 一.简述 二.IO流的介绍 什么是流 输入输出流的作用范围 三.Java中的字节流和字符流 字节流 字符流 二者的联系 1.InputStreamReader 2. ...

  2. 用于测试XmlAnalyzer 1.00版的八个测试用例

    XmlAnalyzer 工程目的:将XML的属性和子节点按字母序排列,然后整形输出: 08. 原文=<project xmlns="http://maven.apache.org/PO ...

  3. Tomcat cluster方案共享session配置成功,yeah....

    后继版本:https://www.cnblogs.com/xiandedanteng/p/12143112.html 参考网文: 1.Tomcat官方文档 2.https://blog.51cto.c ...

  4. CentOS如何设置IP连接网络

    1.登录系统,进入:cd /etc/sysconfig/network-scripts 目录下,如下图: 找到 ifcfg-ens33(文件) 第二步:使用vi编辑器打开ifcfg-ens33文件,使 ...

  5. sqlserver语句的执行顺序

    执行顺序: 1.from 2.where 3.group by 4.having 5.根据select 关键之后的要显示的字段,进行结果集显示 6.order by 对最终结果集进行排序 7.top/ ...

  6. Linux:使用SecureCRT来上传和下载文件

    SecureCRT自带的有几种上传下载功能,SecureCRT下的文件传输协议有以下几种:ASCII.Xmodem.Ymodem.Zmodem. ASCII:这是最快的传输协议,但只能传送文本文件.  ...

  7. python语法,基础

    1.用缩进表达代码逻辑:推荐缩进4个空格 2.注释和续行与shell一样 在pycharm中,按ctrl + / 可以快速添加/取消注释 3.多个语句书写在同一行,可以使用分号进行分割.但不推荐 输出 ...

  8. linux下禁止root和匿名用户登录ftp

    1.ftp通过root或其他用户进入可视化界面权限过大,容易导致系统文件误删 windows下输入ftp://IP去访问,不需要账号密码就可以访问到ftp文件夹 刚进去pub是空的,在linux上新增 ...

  9. xss的变形--如何绕过xss过滤

    我们可以通过构造xss代码进行各种变形,以绕过xss过滤器的检测 1.大小写检测 将payload进行大小写转化 如<Img SRC='#'  Onerror="alert(/xss/ ...

  10. [LeetCode]230. 二叉搜索树中第K小的元素(BST)(中序遍历)、530. 二叉搜索树的最小绝对差(BST)(中序遍历)

    题目230. 二叉搜索树中第K小的元素 给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素. 题解 中序遍历BST,得到有序序列,返回有序序列的k-1号元素. 代 ...