文章目录
前言
元编程是一种编写计算机程序技术·,这些程序可以将自己看作数据,因此你可以在运行时,对它进行内省、生成等操作。
一、元编程
元编程两种主要方法:
- 类的特殊方法
- 像装饰器一样允许向现有函数、方法、或类添加附加功能。
本文将对二者都有涉猎。
装饰器
装饰器本质上就是一个python函数,它可以让其它函数在不需要做任何代码改动的前提下增加额外的功能。
特点
- 函数作为另一个函数参数出现的
- 要有闭包特点。
调用
使用 “@装饰器名”的格式调用,调用后先将被装饰函数直接传给装饰函数再执行装饰器函数
import time
from functools import wraps
def timethis(func):
'''计算代码运行时间'''
@wraps(func)#保存元数据
def wrapper(*args,**kwargs):
start=time.time()
result=func(*args,**kwargs)
end=time.time()
print(func.__name__,end-start)
return result#函数的返回结果
return wrapper
@timethis
def countdown(n):
'''减一'''
while n>0:
n-=1
return n
q=countdown(100000)
print(q)
当然,装饰器还有一种调用方式:
countdown=timethis(countdown)
这种方式清楚的展示了装饰器的作用,这说明装饰器可以被看做元编程的一种工具。
装饰器的一些扩展和运用
对装饰器进行解包装
我们已经把装饰器添加到一个函数上,但是想要撤销它,访问未经过包装的原始函数。如果装饰器使用了@wraps装饰器,我们可以访问__wrapped__属性来获取对原始函数的访问。
from functools import wraps
def decoratorwraps(func):
'''此装饰器使用了wraps'''
@wraps(func)
def wrapper(*args,**kwargs):
print('Do something')
result=func(*args,**kwargs)
return result
return wrapper
def decorator_no_wraps(func):
'''此装饰器未使用wraps'''
def wrapper(*args,**kwargs):
print('Do something')
result=func(*args,**kwargs)
return result
return wrapper
@decoratorwraps
def add(x,y):
print(x+y)
return x+y
@decorator_no_wraps
def add1(x,y):
print(x+y)
return x+y
add(1,2)#Do something;3
add1(1,2)#Do something;3
add.__wrapped__(1,2)#3
add1.__wrapped__(1,2)#AttributeError: 'function' object has no attribute '__wrapped__'
编写装饰器为包装的函数添加参数
我们想要编写一个装饰器为包装的函数添加额外的参数,但添加的参数不能影响到该函数已有的调用约定。
from functools import wraps
def option_debug(func):
def wrapper(*args,debug=False,**kwargs):
if debug:
print('Calling ',func.__name__)
return func(*args,**kwargs)
return wrapper
@option_debug
def spam(a,b,c):print(a,b,c)
spam(1,2,3)
这样编写是为了解决装饰一堆有相同值参数的函数而设计的,但是这有一个缺点需要指出,函数签名问题(以后会提到)。
import inspect
print(inspect.signature(spam))#(*args, debug=False, **kwargs)
在类中定义装饰器
查看装饰器的特点,知道我们还可以在类中定义装饰器,在类中定义装饰器具有两种方法
- 1.实例方法
- 2.类方法
from functools import wraps
class A:
def decorator1(self,func):
@wraps(func)
def wrapper(*args,**kwargs):
print('Do something1')
return func(*args,**kwargs)
return wrapper
@classmethod
def decorator2(cls,func):
@wraps(func)
def wrapper(*args,**kwargs):
print('Do something 2')
return func(*args,**kwargs)
return wrapper
a=A()
@a.decorator1
def spam():
print('Spam')
@A.decorator2
def grok():
pass
spam()#Do something1;Spam
grok()#Do something 2
装饰器定义成类
我们已经知道了能在类中定义成装饰器,能不能把装饰器定义成类呢?答案是肯定的,把装饰器定义成类,需要实现__call__和__get__方法。
import types
from functools import wraps
class Profiled():
def __init__(self,func):
wraps(func)(self)
self.ncalls=0
def __call__(self, *args, **kwargs):
self.ncalls+=1
return self.__wrapped__(*args,**kwargs)
def __get__(self, instance, owner):
if instance is None:
return self
else:
return types.MethodType(self,instance)#将属性方法动态地添加到实例上
class Spam:
@Profiled
def bar(self):
pass
s=Spam()
s.bar()
print(Spam.bar.ncalls)#1
如果希望得到一个结果是一个可以调用的实例且需要装饰器既能在类中工作也可以在外部工作。那么你就可以使用它。
谈到类与装饰器需要知道当把装饰器作用到类方法、静态方法上时,一般自定义的装饰器要写在类方法装饰器或静态方法装饰器下面。
类装饰器
python有一个语法特性——类装饰器。(不是上面拓展的两种)其语法的规则与普通装饰器相同,不同的是,它的返回值是一个类。
def short_repr(cls):
cls.__repr__=lambda self:super(cls,self).__repr__()[:8]
return cls
@short_repr
class ClassWithRelativelyLongName():
pass
print(ClassWithRelativelyLongName())#<__main_
上面的装饰器并没有符合标准写法,但可以看出装饰器的核心思想。下面进行重写
def parametrized_short_repr(max_width=8):
'''缩短表示的参数化装饰器'''
def parametrized(cls):
class ShortlyRepresented(cls):
def __repr__(self):
return super(cls,self).__repr__()[:max_width]
return ShortlyRepresented
return parametrized
@parametrized_short_repr(10)
class ClassWithRelativelyLongName():
pass
print(ClassWithRelativelyLongName())#<__main__.
类装饰器中这样使用闭包具有缺点:会影响元数据,这并不是使用一个warps能解决的。
可接受参数的装饰器
我们想要编写一个可接受参数的装饰器函数,以此来实现一些细节。这时可以使用三层函数的装饰器:第一层接收装饰器参数,第二层接收函数,第三层接收函数参数。以一个例子展示:编写一个为函数添加日志功能的装饰器,但是又允许用户指定日志等级以及一些其他的细节作为参数。
from functools import wraps
import logging
def logged(level,name=None,message=None):
'''
编写一个为函数添加日志功能的装饰器,
但是又允许用户指定日志等级以及一些其他的细节作为参数
'''
def decorate(func):
logname=name if name else func.__module__#此内置类属性将打印在其中定义了功能/对象的模块的名称,如果不可用,则显示None。
log=logging.getLogger(logname)
logms=message if message else func.__name__#__name__对于类和函数而言,__name__的值总是与类和函数的名称一致。
@wraps(func)
def wrapper(*args,**kwargs):
log.log(level,logms)
return func(*args,**kwargs)
return wrapper
return decorate
@logged(logging.DEBUG)
def add(x,y):
return x+y
@logged(logging.CRITICAL,'example')
def spam():
print('Spam!')
add(1,2)
spam()
'''spam
Spam!'''
利用装饰器给类定义打补丁
想要修改类的行为,但不想通过继承或者元类的方式进行就可以用类装饰器
def log_getattribute(cls):
orig_getattribute=cls.__getattribute__
def new_getattribute(self,name):
print('geting',name)
return orig_getattribute(self,name)
cls.__getattribute__=new_getattribute
return cls
@log_getattribute
class A:
def __init__(self,x):
self.x=x
def spam(self):
pass
a=A(42)
a.x#geting x
元类
元类是一个python的特性,定义其他类的一种类。class关键字定义的类本身也是一个对象,负责产生该对象的类称之为元类,元类可以简称为类的类,元类的主要目的是为了控制类的创建行为。type是Python的一个内建元类,用来直接控制生成类,在python当中任何class定义的类其实都是type类实例化的结果。只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类,自定义元类可以控制类的产生过程,类的产生过程其实就是元类的调用过程。
一般语法:
利用内置的type()类可以作为class语句的等价
语法格式:type(name,bases,namespace)
由格式可以看出一个类由三大组成部分,分别是
- 类名name
- 继承关系bases
- 类的名称空间namespace
def method(self):
pass
klass=type('MyClass',(object,),{'method':method})
a=klass()
a.method
#等价于
class MyClass(object):
def method(self):
pass
定义元类
class语句创建的类都在隐式调用type,当然也可以显示调用格式如下:
class Metaclass(type):
def __new__(cls, *args, **kwargs):
'''与普通类作用相同'''
pass
def __init__(self):
'''普通类相同'''
pass
@classmethod
def __prepare__(metacls, name, bases):
'''创建一个空的命名空间对象。默认默认返回一个空的dict.但可以覆写并使其
返回其他任何映射类型'''
def __call__(self, *args, **kwargs):
'''创建类的新实例会调用他'''
下面通过实例来运用一下
目的:实现单例模式
class Singleton(type):
def __init__(self,*args,**kwargs):
self.__instance=None
super(Singleton, self).__init__(*args,**kwargs)
def __call__(self, *args, **kwargs):
if self.__instance is None:
self.__instance=super(Singleton, self).__call__(*args,**kwargs)
return self.__instance
else:
return self.__instance
class Spam(metaclass=Singleton):
def __init__(self):
print('create Spam')
a=Spam()#create Spam
b=Spam()#
元类是一个非常强大的特性,但是会使代码复杂,也会降低代码的可读性。不建议滥用,可以寻找装饰器、槽、描述符等方法解决。
关于一些代码的生成提示。
动态代码生成是最难的代码生成方法python有些工具可以你生成并执行。python中提供了三个内置函数,用于手动执行、求值、编译任意python代码
- exec:动态执行字符串代码,返回值为空
- eval:只能计算数学表达式
- compile:有点像re模块中的compile,,将一个字符串编译为字节代码.
exec ('a=1')
print(a)#1
a1=eval('a+1')
print(a1)#2
stra='for i in range(10):print(i)'
c=compile(stra,'','exec')
print(c)#<code object <module> at 0x00000197508B2F50, file "", line 1>
exec (c)
补充:
- 对于eval、exec的频繁使用可能会导致安全漏洞,但在有些情况下使用exec、eval是合理的。
- 使用exec语句要考虑全局与局部命名空间的问题
a=13
exec ('b=a+1')
print(b)#14
#---------------
def test():
a=13
exec ('b=a+1')
print(b)
test()#NameError: name 'b' is not defined
#--------解决方案
def test():
a=13
loc=locals()
exec ('b=a+1')
b=loc['b']
print(b)
test()#14
对元编程部分进行补充
函数签名
定义函数的输入或输出包含以下内容:参数类型、返回值类型、可能抛出的异常、可以方面的注解或信息。
在python中任何操作函数调用签名问题,都与inspect模块中的方法有关,下面通过给*args、**kwargs规定一种参数签名为例来讲解一下inspect中的Signature,Parameter
from inspect import Signature,Parameter
parms=[
Parameter('x',Parameter.POSITIONAL_OR_KEYWORD),
Parameter('Y', Parameter.POSITIONAL_OR_KEYWORD,default=42),
Parameter('z', Parameter.KEYWORD_ONLY,default=None),
]
sig=Signature(parms)
print(sig)#(x, Y=42, *, z=None)
def func(*args,**kwargs):
bound_value=sig.bind(*args,**kwargs)
for name,value in bound_value.arguments.items():
print(name,value)
func(1,2,z=3)
func(1)#x 1
func(1,2,3,4)#TypeError: too many positional arguments
参考书籍:高级编程,python食谱等