1.可调用对象
python中一切皆对象,函数也不例外。
def foo():
print(foo.__module__, foo.__name__)
foo() # 等价于 foo.__call__()
函数即对象,对象foo加上(),就是调用此函数对象的__call__()方法
可调用对象:定义一个类,并实例化得到其实例,将实例像函数一样调用。
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __call__(self, *args, **kwargs):
return "< Point {} {}>".format(self.x, self.y)
p = Point(4, 5)
print(p)
print(p()) # 类中定义了__call__方法,实例可以像函数一样调用
class Adder:
def __call__(self, *args):
ret = 0
for x in args:
ret += x
self.ret = ret
return ret
adder = Adder()
print(adder(4, 5, 6))
print(adder.ret)
练习
定义一个斐波那契数列的类,方便调用,计算第n项;增加迭代的方法,返回容器长度,支持索引的方法。
class Fib:
def __init__(self):
self.items = [0, 1, 1]
def __len__(self):
return len(self.items)
def __iter__(self):
yield from self.items
def __getitem__(self, index):
if index < 0:
raise KeyError('Wrong index')
if index < len(self.items):
return self.items[index]
for i in range(len(self.items), index + 1):
self.items.append(self.items[i - 2] + self.items[i - 1])
return self.items[index]
def __call__(self, index):
return self[index]
def __repr__(self):
return "<{} {}>".format(self.__class__.__name__, self.items)
f = Fib()
print(f(5))
for x in f:
print(x)
print(f[6])
print(len(f))
可以看出,使用类类实现菲波那切数列也是非常好的实现,还可以缓存数据,便于检索。
2.上下文管理
2.1上下文管理对象
当一个对象实现了__enter__()和__exit__()方法,它就属于上下文管理对象。
with操作的对象,必须支持上下文管理,即必须要有enter方法和exit方法
没有as子句并不影响上下文管理
with语句不会开辟新的作用域
import time
class A: # 定义了进入和退出方法的对象叫上下文管理对象
def __init__(self):
print('1, init ~~~~~~~~~~~')
time.sleep(1)
print('2, init over')
def __enter__(self): # 返回值会给a; 进入和退出的方法必须同时存在,它们都是实例的,不是类的
print('3, enter ~~~~~~~~~~~~')
def __exit__(self, exc_type, exc_val, exc_tb): # 默认的返回值为None
print('6, exit ~~~~~~~~~~~~~')
print(exc_type) # <class 'ZeroDivisionError'>
print(exc_val) # division by zero
print(exc_tb) # <traceback object at 0x000000000221EC48>
return 'abc' # 有返回值时,会压制异常
with A() as a:
print('4, enter with ~~~~~~~~~')
import sys
sys.exit(100)
# 1 / 0 # 有异常时,又没有捕获并处理异常,异常下的语句不会再执行
# 注意即便异常没有处理,__exit__方法下的语句还是要执行
time.sleep(1)
print('5, exit with ~~~~~~~~~~') # 注意执行顺序 ,标注的序号就是执行色顺序
实例化对象的时候,并不会调用enter方法,进入with语句块调用__enter__方法,然后执行语句体,最后离开with语句块的时候,调用__exit__方法。
with语句开启一个上下文运行环境,在执行前做一些准备工作,执行后做一些收尾工作。with语句并不开辟一个新的作用域。
2.2 上下文管理的安全性
上例中,虽然出现了异常,但是enter和exit照样执行,上下文管理是安全的。
极端的例子,调用sys.exit(),它会退出当前解释器。打开python解释器,在里面敲入sys.exit(),窗口直接就关闭了,也就是说,python运行环境直接退出了。从执行结果来看, 依然执行了__exit__方法,哪怕是退出python环境。
2.2.1 with语句
import time
class A: # 定义了进入和退出方法的对象叫上下文管理对象
def __init__(self):
print('1, init ~~~~~~~~~~~')
time.sleep(1)
print('2, init over')
def __enter__(self): # 返回值会给a; 进入和退出的方法必须同时存在,它们都是实例的,不是类的
print('3, enter ~~~~~~~~~~~~')
def __exit__(self, exc_type, exc_val, exc_tb): # 默认的返回值为None
print('6, exit ~~~~~~~~~~~~~')
print(exc_type) # <class 'ZeroDivisionError'>
print(exc_val) # division by zero
print(exc_tb) # <traceback object at 0x000000000221EC48>
return 'abc' # 有返回值时,会压制异常
with A() as a:
print('4, enter with ~~~~~~~~~')
# import sys
# sys.exit(100)
# 1 / 0 # 有异常时,又没有捕获并处理异常,异常下的语句不会再执行
# 注意即便异常没有处理,__exit__方法下的语句还是要执行
time.sleep(1)
print('5, exit with ~~~~~~~~~~') # 注意执行顺序 ,标注的序号就是执行色顺序
b = A()
with b as c: # ——> c = b.__enter__()即c为b调用__enter__方法的返回值
print(1, b)
print(2, c) # None ——> __enter__方法的默认返回值为None
print(b is c) # False
print(b == c) # False
print(id(b), id(c))
with语法,会调用with后对象的__enter__方法,如果有as,则将该方法的返回值赋给as子句的变量。
2.2.2 方法的参数
__enter__方法没有其他参数;
__exit__方法有三个参数:
exit(self, exc_type, exc_value, exc_tb)
这三个参数都与异常相关,如果该上下文退出时,没有异常,这三个参数都是None,如果有异常,参数意义如下:
exc_type: 异常类型
exc_val:异常的值
exc_tb: 异常的追踪信息
__exit__返回一个等效为True的值,则压制异常,否则继续向外抛出异常。
练习
为加法函数计时:
方法1、使用装饰器显示该函数的执行时长
方法2、使用上下文管理方法来显示该函数的执行时长
import time
from datetime import datetime
from functools import wraps, update_wrapper
class Timeit:
"""This is Timeit class"""
def __init__(self, fn):
self.fn = fn
self.__doc__ = fn.__doc__ # 设置实例的属性
self.__name__ = fn.__name__
def __enter__(self):
self.start = datetime.now()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
delta = (datetime.now() - self.start).total_seconds()
print("{} took {}s in class".format(self.fn.__name__, delta))
def timeit(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
start = datetime.now()
ret = fn(*args, **kwargs)
delta = (datetime.now() - start).total_seconds()
print("{} took {}s in dec".format(fn.__name__, delta))
return ret
return wrapper
@timeit
def add(x, y):
"""this is add function"""
time.sleep(2)
return x + y
add(4, 5)
with Timeit(add) as t: # ——> t = Timeit(add)实例化
add(4, 5)
print(add.__doc__) # this is add function
print(add.__name__) # add
对上述的代码稍加改造,可以使用可调用对象实现计时,还可以把类作为装饰器使用。
import time
from datetime import datetime
from functools import wraps, update_wrapper
class Timeit:
"""This is Timeit class"""
def __init__(self, fn):
self.fn = fn
# self.__doc__ = fn.__doc__ # 设置实例的属性
# self.__name__ = fn.__name__
# update_wrapper(self, fn)
wraps(fn)(self) # 解决文档字符串的问题,三种方法都可以
def __enter__(self):
self.start = datetime.now()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
delta = (datetime.now() - self.start).total_seconds()
print("{} took {}s in class".format(self.fn.__name__, delta))
def __call__(self, *args, **kwargs):
start = datetime.now()
ret = self.fn(*args, **kwargs)
delta = (datetime.now() - start).total_seconds()
print("{} took {}s in class".format(self.fn.__name__, delta))
return ret
# def timeit(fn):
#
# @wraps(fn)
# def wrapper(*args, **kwargs):
# start = datetime.now()
# ret = fn(*args, **kwargs)
# delta = (datetime.now() - start).total_seconds()
# print("{} took {}s in dec".format(fn.__name__, delta))
# return ret
# return wrapper
#
#
# @timeit
# def add(x, y):
# """this is add function"""
# time.sleep(2)
# return x + y
# add(4, 5)
@Timeit # add = Timeit(add)实例化,add为Timeit 的实例
def add(x, y):
"""this is a function"""
time.sleep(1)
return x + y
add(4, 5) # add took 1.014002s in class
# with Timeit(add) as t: # ——> t = Timeit(add)
# # add(4, 5)
# t(5, 6) # 实例调用
#
print(add.__doc__) # this is add function
print(add.__name__) # add
2.3 上下文应用场景
- 增强功能: 在代码执行的前后增加代码,一增强其功能,类似装饰器的功能。
- 资源管理:打开了资源需要关闭,例如文件对象、网络连接、数据库连接等。
- 权限验证:在执行代码之前,做权限的验证,在__enter__中处理。
2.4 contextlib.contextmanager
contextlib.contextmanager
它是一个装饰器实现上下文管理,装饰一个函数,而不用像类一样实现__enter__和__exit__方法。对下面的函数有要求:必须有yield,也就是这个函数必须返回一个生成器,且只有一个yield 值。也就是这个装饰器接收一个生成器对象作为参数。
from contextlib import contextmanager
@contextmanager # 上下文管理的装饰器
def a():
print('enter~~~~~~') # 进入代码
try:
yield 123
except ZeroDivisionError:
pass
finally: # 退出的代码放在finally语句中
print('exit~~~~~~~~') # 退出代码
with a() as f: # f = a().__enter__()
print('f = ', f) # f = 123
print(1 / 0)
print('with over~~~~')
print('+++++++++++++++')
当yield发生处为生成器函数增加了上下文管理。这是为函数增加上下文机制的方式。
- 把yield之前的当做__enter__方法执行
- 把yield之后的当做__exit__方法执行
- 把yield的值作为__enter__方法的返回值
import contextlib
import time
import datetime
@contextlib.contextmanager
def add(x, y): # 为生成器函数增加了上下文管理
start = datetime.datetime.now()
try:
time.sleep(2)
yield x + y
finally:
delta = (datetime.datetime.now() - start).total_seconds()
print(delta)
with add(4, 5) as f: # f = x + y
print(f)
总结:如果业务逻辑简单可以使用函数加contextlib.contextmanager装饰器方法,如果业务复杂,用类的方式加__enter__和__exit__方法方便。
3.反射
运行时,区别于编译时,指的是程序被加载到内存中执行的时候。==反射,reflection,指的是运行时获取类型定义的信息。==简单说,在python中,能够通过一个对象,找出其type、class、attribute或method的能力,称为反射或自省。具有反射能力的函数有type()、isinstance、callable()、dir()、getattr()等
3.1反射相关的函数和方法
from functools import update_wrapper
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
# def show(self):
# print(self.x, self.y)
def __repr__(self):
return "<Point {} {}>".format(self.x, self.y)
__str__ = __repr__
p1 = Point(4, 5)
print(p1.x, p1.y)
p1.x = 20
print(p1.__dict__)
p1.z = 100
p1.__dict__['x'] = 200
print(p1.__dict__)
print(dir(p1))
print(sorted(p1.__dir__()))
p2 = Point(4, 5)
print(p2.x)
print(getattr(p2, 'y', 2000)) # 2000为缺省值
# print(getattr(p2, 'z'))
if not hasattr(p2, 'z'):
print(getattr(p2, 'z', 3000)) # 3000为缺省值
setattr(p2, 'z', 3000)
print(p2.__dict__) # {'x': 4, 'y': 5, 'z': 3000}
# print(getattr(p2, '__dict__'))
setattr(Point, 'XYZ', 1000) # 给类动态增加属性
print(Point.__dict__)
if not hasattr(Point, 'show'):
# 为类动态的增加方法
setattr(Point, 'show', lambda self: print(self.x, self.y))
p3 = Point(4, 5)
p3.show()
Point.show(p3)
p4 = Point(5, 6)
if not hasattr(p4, 'showy'):
setattr(p4, 'showy', lambda self: print(self.y)) # 给实例动态的增加方法,但是没有绑定,就是要手动注入实例,不推荐这么增加方法
# TypeError: <lambda>() missing 1 required positional argument: 'self'
# setattr(p4, 'showy', lambda: print(123))
p4.showy(p4) # 6
# p4.showy() # 123
print(p4.show) # <bound method <lambda> of <__main__.Point object at 0x0000000002209748>>;即会将第一参数绑定为实例
print(p4.showy) # <function <lambda> at 0x0000000002260BF8>
p5 = Point(10, 10)
p6 = Point(4, 5)
setattr(Point, '__add__', lambda self, other: self.__class__(self.x + other.x, self.y + other.y))
print(p5 + p6) # <Point 14 15>
练习
命令分发器:通过名称找对应的函数执行
思路:名称找对象的方法
# 命令分发器
class Dispatcher:
def __init__(self):
pass
def reg(self, name, fn):
setattr(self, name, fn)
def run(self):
while True:
cmd = input('>>>').strip()
if cmd == "quit":
break
getattr(self, cmd, lambda: print('Unknown cmd {}'.format(cmd)))()
dis = Dispatcher()
dis.reg('ls', lambda: print('ls'))
dis.run()
3.2反射相关的魔术方法
3.2.1 getattr()
class Base:
n = 0
class Point(Base):
z = 6
def __init__(self, x, y):
self.x = x
self.y = y
def show(self):
print(self.x, self.y)
def __getattr__(self, item):
return "missing {}".format(item)
p1 = Point(4, 5)
print(p1.x)
print(p1.z)
print(p1.n)
print(p1.t) # 实例没有t属性
实例属性会按照继承关系找,如果找不到,就会执行__getattr__()方法,如果没有这个方法,就会抛出AttributeError异常表示找不到属性。
查找属性的顺序为:
instance.dict --> instance.class.dict --> 继承的祖先类(直到object)的__dict__,找不到 --> 调用__getattr__()。
3.2.2 setattr()
class Base:
n = 0
class Point(Base):
z = 6
def __init__(self, x, y):
self.x = x
self.y = y
def show(self):
print(self.x, self.y)
def __getattr__(self, item):
print(item, '~~~~~~~~~~~~')
print(type(item)) # item是字符串
return "missing {}".format(item)
def __setattr__(self, key, value):
print('setattr {}={}'.format(key, value))
# self.__dict__[key] = value # 操作实例字典,将属性赋值放在字典中
# super().__setattr__(key, value) # 调用父类object的__setattr__方法
# self.key = value # 无限递归
# setattr(self, key, value) # 无限递归
p1 = Point(4, 5)
print(p1.x)
print(p1.z)
print(p1.n)
print(p1.t) # 实例没有t属性
p1.x = 50 # setattr x=50;实例通过点号设置属性,就会调用__setattr__()
print(p1.x) # missing x
print(p1.__dict__) # {}
p1.__dict__['x'] = 60
print(p1.__dict__) # {'x': 60}
print(p1.x)
实例通过点号设置属性,例如self.x = x属性赋值,就会调用__setattr__()方法,属性要加到__dict__字典中,就需要自己完成。setattr()方法,可以拦截对实例属性的增加、修改操作,如果要设置生效,需要自己操作实例的字典。
class Base:
n = 0
class Point(Base):
z = 6
d = {}
def __init__(self, x, y):
# self.x = x
# self.y = y
# setattr(self, 'x', x)
# setattr(self, 'y', y)
self.__dict__['x'] = x
self.__dict__['y'] = y
def show(self):
print(self.x, self.y)
def __getattr__(self, item):
print(item, '~~~~~~~~~~~~')
# print(type(item)) # item是字符串
# return "missing {}".format(item)
return self.d[item]
def __setattr__(self, key, value):
print('setattr {}={}'.format(key, value))
if key not in self.__dict__.keys():
self.d[key] = value
else:
self.__dict__[key] = value # 操作实例字典,将属性赋值放在字典中
# super().__setattr__(key, value) # 调用父类object的__setattr__方法
# self.key = value # 无限递归
# setattr(self, key, value) # 无限递归
def __delattr__(self, item):
print('del attribute {}'.format(item))
p2 = Point(4, 5)
# del p2.x
# del p2.z
print(p2.__dict__) # {'x': 4, 'y': 5}
print(p2.x, p2.y)
p2.tt = 2000
print(p2.tt) # tt ~~~~~~~~~~~~ 2000
3.2.3 delattr()
class Point:
Z = 5
def __init__(self, x, y):
self.x = x
self.y = y
def __delattr__(self, item): # 该方法会阻止通过实例来删除属性
print('Can not del {}'.format(item))
p = Point(12, 2)
del p.x # Can not del x
del p.Z # Can not del Z
p.z = 17
del p.z # Can not del z
print(Point.__dict__)
print(p.__dict__) # {'x': 12, 'y': 2, 'z': 17},通过实例删除属性并没有实现
del Point.Z
print(Point.__dict__) # 类的字典中Z属性被删除了
== 可以阻止通过实例来删除属性的操作,但是通过类依然可以删除类属性。==
3.2.4 getattribute()
class Base:
n = 0
class Point(Base):
z = 6
d = {'tt': '20000'}
def __init__(self, x, y):
self.x = x
self.y = y
# setattr(self, 'x', x)
# setattr(self, 'y', y)
# self.__dict__['x'] = x
# self.__dict__['y'] = y
def show(self):
print(self.x, self.y)
def __getattribute__(self, item): # 默认的返回值为None
# print(type(item))
# print(item)
# return 1
# return super().__getattribute__(item)
# return object.__getattribute__(self, item)
raise AttributeError('tt')
def __getattr__(self, item):
print(item, '~~~~~~~~~~~~')
# print(type(item)) # item是字符串
# return "missing {}".format(item)
# return self.d[item]
return __class__.d[item] # 注意不能写成self.d[item],否则会无限递归
# def __setattr__(self, key, value):
# print('setattr {}={}'.format(key, value))
# if key not in self.__dict__.keys():
# self.d[key] = value
# else:
# self.__dict__[key] = value # 操作实例字典,将属性赋值放在字典中
# # super().__setattr__(key, value) # 调用父类object的__setattr__方法
# # self.key = value # 无限递归
# # setattr(self, key, value) # 无限递归
def __delattr__(self, item):
print('del attribute {}'.format(item))
p3 = Point(4, 5)
# print(p3.__dict__) # {'x': 4, 'y': 5}
# print(p3.x) # 实例访问的第一道关是__getattribute__方法
print(p3.tt) # 20000
实例的所有属性访问,第一个都会调用__getattribute__方法,它阻止了属性的查找,该方法应该返回(计算后的)值或抛出一个AttributeError异常。
- 它的return值将作为属性查找的结果
- 如果抛出AttributeError异常,则会直接调用__getattr__方法,因为表示属性没有找到。
getattribute__方法中为了避免在该方法中无限递归,它的实现则永远调用基类的同名方法以访问需要的任何属性,例如object.getattribute(self, name)。
注意,除非你明确知道__getattriabute__方法用来做什么,否则不要使用它。
总结:
属性查找顺序:
实例调用__getattribute() --> instance.dict() --> instance.class.dict() --> 继承的祖先类直到object.dict() --> 调用__getattr__()