Python装饰器和元类

什么是单例模式

1.装饰器

参考:Python 函数装饰器
50道硬核python面试题
装饰器可以修饰类或函数,在不修改已有函数的代码和调用方式,为函数或类提供额外的功能。这些功能与正常的业务没有必然联系,可以动态添加和删除。本质上是一个闭包函数(内部函数调用外部函数的变量)。

1.1装饰器使用场景

Q1:记录函数执行时间的装饰器

1)用函数实现装饰器

from functools import wraps
from time import time

def record_time(func):
	@wraps(func)
	def wrapper(*args,**kwargs):
		start = time()
		result = func(*args,**kwargs)
		print(f'{func.__name__}执行时间:{time()-start}秒')
		return result
	return wrapper

2)用类实现装饰器
类有魔术方法__call__,该类对象就是可调用对象,可以当做装饰器来使用

from functools import wraps
from time import time
class Record:
	def __call__(self,func):
		@wraps(func)
		def wrapper(*args,**kwargs):
			start = time()
			result = func(*args,**kwargs)
			print(f'{func.__name__}执行时间:{time()-start}')
			return result
	return wrapper
Q2:装饰器打印日志

1)使用函数实现装饰器

from functools import wraps

def logit(logfile='out.log'):
	def logging_decorator(func):
		@wraps(func)
		def wrapper(*args,**kwargs):
			log_string = func.__name__ +"was called"
			print(log_string)
			with open(logfile,'a') as opened_file:
				opened_file.write(log_string+'/n')
			return func(*args,**kwargs)
		return wrapper
	return logging_decorator

@logit(logfile='func2.log')
def myfunc2():
    pass
#输出:myfunc2 was called

2)使用类实现装饰器

from functools import wraps

class logit(object):
	def __init__(self,logfile='out.log'):
		self.logfile = logfile
	def __call__(self,func):
		@wraps(func)
		def wrapper(*args,**kwargs):
			log_string = func.__name__+"was called"
			print(log_string)
			with open(logfile,'a') as opened_file:
				opened_file.write(log_sting+'\n')
		return func(*args,**kwargs)
	return wrapper
Q3:访问网络出现指定异常,重试指定次数

1) 使用函数实现装饰器

from functools import wraps
from time import time

def retry(*,retry_times=3,max_wait_secs=5,errors=(Exception,)):
	def decorate(func):
		@wraps(func)
		def wrapper(*args,**kwargs):
			for _ in range(self.retry_times):  # _ 表示可丢弃变量
			try:
				return func(*args,**kwargs)
			except errors:
				sleep(max_wait_secs)
			return None
		return wrapper
	return decorate

2) 使用类实现装饰器

from functools import wraps
from time import time

class Retry(object):
	def __init__(self,*,retry_times=3,max_wait_secs=5,errors=(Exception,)):
		self.retry_times = retry_times
		self.max_wait_secs = max_wait_secs
		self.errors = errors
	def __call__(self,func):
		@wraps(func)
		def wrapper(*args,**kwargs):
			for _ in range(self.retry_times):
				try:
					return func(*args,**kwargs):
				except self.errors:
					sleep(max_wait_secs)
			return None
		return wrapper
		
Q4:装饰器实现单例

如下装饰器实现的单例不是线程安全的,如果要线程安全,需要对创建对象的代码加锁。
加锁方式:使用threading模块的RLock对象提供锁,使用锁对象的acquire和release来加锁和解锁
也可以使用所对象的with上下文语法来隐身加解锁

def sigleton(cls):
    instance={}
    @wraps(cls)
    def cls_need_decorator(*args,**kwargs):
        if cls not in instance:
            instance[cls]=cls(*args,**kwargs)
        return instance[cls]
    return cls_need_decorator

@sigleton
class President:
    pass

print(President())
#President = President.__wrapped__
print(President())

#注释President = President.__wrapped__输出
<__main__.President object at 0x00000213236F3B80>
<__main__.President object at 0x00000213236F3B80>
#注释President = President.__wrapped__输出
<__main__.President object at 0x0000020C9A4F3B80>
<__main__.President object at 0x0000020C9A4F3FD0>
Q5:装饰器使用方式

1)装饰器使用方式一
装饰器函数的参数传入函数,装饰器函数的返回值赋值给被装饰的函数名

def new_decorator(func):
    def wrapTheFunc():
        print("before")
        func()
        print("after")
    return wrapTheFunc

def func_need_decorator():
    print("doing sth")

func_need_decorator()
print("-----------------------")
func_need_decorator=new_decorator(func_need_decorator)
func_need_decorator()
#输出结果
doing sth
-----------------------
before
doing sth
after

2)装饰器函数使用方式二
函数上一行添加 @装饰器函数名

def new_decorator(func):
    def wrapTheFunc():
        print("before")
        func()
        print("after")
    return wrapTheFunc

@new_decorator
def func_need_decorator():
    print("doing sth")

func_need_decorator()

#输出结果
before
doing sth
after

1.2 相关知识:@wraps

使用@wraps定义的装饰器,对被装饰的函数或类添加__wrapped__属性,会保留被装饰前的函数或类,当不需要装饰器是可以通过__wrapped__来取消装饰器

使用@wraps定义函数装饰器,接受一个函数进行装饰,并加入复制函数名,参数列表,注释文档的功能,事例

def new_decorator(func):
    @wraps(func)
    def wrapTheFunc(*args,**kwargs):
        print("before")
        func()
        print("after")
        return func(*args,**kwargs)
    return wrapTheFunc

@new_decorator
def fun_need_decorator():
    """Docstring"""
    print("doing sth")

print(fun_need_decorator.__name__)
print(fun_need_decorator.__doc__)

#不注释@wraps(func)输出
fun_need_decorator
Docstring

#注释@wraps(func)输出
wrapTheFunc
None
1.2.1 wraps官方文档解释

参考文献
@functools.wraps

@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

这是一个装饰器工厂函数,用于在定义装饰器函数时发起调用 update_wrapper() 作为函数装饰器。 它等价于** partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)**。 例如:

>>> from functools import wraps
>>> def my_decorator(f):
...     @wraps(f)
...     def wrapper(*args, **kwds):
...         print('Calling decorated function')
...         return f(*args, **kwds)
...     return wrapper
...
>>> @my_decorator
... def example():
...     """Docstring"""
...     print('Called example function')
...
>>> example()
Calling decorated function
Called example function
>>> example.__name__
'example'
>>> example.__doc__
'Docstring'

如果不使用这个装饰器工厂函数,则 example 函数的名称将变为 ‘wrapper’,并且 example() 原本的文档字符串将会丢失。

工厂函数:能够产生类实例的内建函数。比如, list(),tuple():生成列表或者元组
装饰器工厂函数:生产装饰器的函数

1.2.3 functools库

functools 模块应用于高阶函数,即——参数或(和)返回值为其他函数的函数。通常来说,此模块的功能适用于所有可调用对象。

1.2.4 functools.partial()

partial 函数的功能就是:把一个函数的某些参数给固定住,返回一个新的函数。
如果不固定参数,输入参数会赋给输入函数最左边的参数

def multiply(x, y):
    return x * y
def double(x, y=2):
    return multiply(x, y)
#可以不用自己定义 double,利用 partial,我们可以这样
from functools import partial
double = partial(multiply, y=2)
double(3) #输出6

参考python partial函数

1.2.5 update_wrapper()

@functools.update_wrapper(wrapper, wrapped, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES)

参数:
wrapper:包装函数
wrapped:被包装的函数
assigned:包装函数的属性以元组(可选参数)的形式分配给包装函数的匹配属性。
updated:包装函数的属性相对于原始函数属性(作为元组(可选参数))进行了更新。

update_wrapper作用使被包装函数的模块级常量(例如__name doc__等)保留原始数据
参考:update_wrapper()用法及代码示例

1.3 相关知识__call__的理解

参考python中的 call()
一个类实例要变成可调用对象(callable),就需要实现方法__call__()

2.元类

参考:Python中metaclass解释
mateclass的实例是class,class的实例是instance

语句class*MyObject(object): 表示python将在内存中创建一个名称为MyObject的对象
str函数用于创建字符串对象,int函数用于创建整型对象

type是一个metaclass用于在后台创建类
type可以将类的描述(类名称,继承元组,属性键值对)作为参数,然后生成一个类。

  type(name of the class, 
       tuple of the parent class (for inheritance, can be empty), 
       dictionary containing attributes names and values)

MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>>print MyShinyClass
#输出<class '__main__.MyShinyClass'>

2.1 如何使用metaclass

浅析python的metaclass
python将使用mateclass创建类时,会在新建的类中寻找__metaclass__如果找到了就用它创建,没有将在parent中找,没有就去module级别找,还是没有就用type创建类

1)用类的形式

首先,类继承type;
其次,将需要使用metaclass来构建class的类的__metaclass__属性赋值为Meta(继承于type的类)

class UpperAttribute(type):
	def __new__(cls,class_name,class_parents,class_attr):
		upper_attr={}
		for name,val in class_attr.items():
			if not name.startswith('__'):
				upper_attr[name.upper()] = val
			else:
				upper_attr[name]=val
		# return type.__new__(cls,class_name,class_parents,pper_attr)
		return super(UpperAttribute,cls).__new__(cls,class_name,class_parents,pper_attr)

__new__是在__init__之前调用的方法,
__new__是创建并返回对象的方法
__init__初始化对象

2)用函数的形式

首先,构建一个函数metaclas_new,需要3个参数:name, bases, attrs
name:类的名字
bases:基类,通常是tuple类型
attrs:类的属性或者函数,字典类型
其次,将需要使用mateclass来构建class的类的__mateclass__属性赋值为函数metaclas_new

def upper_attr(class_name,class_parents,class_attr):
	upper_attr={}
	for name,val in class_arrt.items():
		if not name.startswith('__'):
			upper_attr[name.upper()]=val
		else:
			upper_attr[name]=val
	return type(class_name,class_parents,upper_attr)
__mateclass__=upper_attr#影响模块中所有类
class Foo():
	bar = 'bip'
print hasattr(Foo,'bar')
#输出False
print hasattr(Foo,'BAR')
#输出True

2.2 使用元类实现单例模式

class SingletonMeta(type):
	def __init__(cls,*args,**kwargs):
		cls.__instance=None
		super().__init__(*args,**kwargs)
	def __call__(cls,*args,**kwargs):
		if cls.__instance is None:
			cls.__instance=super().__call__(*args,**kwargs)
		return cls.__instance
class President(metaclass=SingletonMeta):
	pass

版权声明:本文为lovefantasy2010原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。