1.在函数上添加包装器

想使用额外的代码包装一个函数,可以定义一个装饰器函数

import time
from functools import wrapsdef timethis(func): #定时器装饰函数'''Decorator that reports the execution time.'''@wraps(func) # @wraps 装饰器来注解底层包装函数,确保被包装后的函数保留它的元信息def wrapper(*args, **kwargs):start = time.time()result = func(*args, **kwargs) #调用了原始函数func并将其结果返回end = time.time()print(func.__name__, end-start)return resultreturn wrapper #新的函数包装器被作为结果返回来代替原始函数>>> @timethis
... def countdown(n):
...     '''
...     Counts down
...     '''
...     while n > 0:
...         n -= 1
...
>>> countdown(100000)
countdown 0.008917808532714844
>>> countdown(10000000)
countdown 0.87188299392912
>>>

一个装饰器就是一个函数,它接受一个函数作为参数并返回一个新的函数

调用方式(1):

@timethis
def countdown(n):pass

调用方式(2):

def countdown(n):pass
countdown = timethis(countdown)

2.解除一个装饰器

一个装饰器已经作用在一个函数上,想撤销它,直接访问原始的未包装的那个函数

假设装饰器是通过 @wraps 实现的,那么可以通过访问 __wrapped__ 属性来访问原始函数

from functools import wrapsdef decorator1(func):@wraps(func)def wrapper(*args, **kwargs):print('Decorator 1')return func(*args, **kwargs)return wrapperdef decorator2(func):@wraps(func)def wrapper(*args, **kwargs):print('Decorator 2')return func(*args, **kwargs)return wrapper@decorator1
@decorator2
def add(x, y):return x + y>>> add(2, 3) #调用装饰器
Decorator 1
Decorator 2
5
>>> add.__wrapped__(2, 3) #去除装饰器
5
>>>

3.定义一个装饰器

(1)带参数的装饰器

from functools import wraps
import loggingdef logged(level, name=None, message=None):最外层的函数 logged() 接受参数并将它们作用在内部的装饰器函数decorate @wraps上面def decorate(func):logname = name if name else func.__module__log = logging.getLogger(logname)logmsg = message if message else func.__name__@wraps(func)def wrapper(*args, **kwargs): #可任意接收最外层传入的参数log.log(level, logmsg)return func(*args, **kwargs)return wrapperreturn decorate #返回对应的日志message# Example use
@logged(logging.DEBUG) #传入参数为日志level
def add(x, y):return x + y@logged(logging.CRITICAL, 'example') #传入参数为日志level和name
def spam():print('Spam!')

(2)自定义属性的装饰器

from functools import wraps, partial
import logging
# Utility decorator to attach a function as an attribute of obj
def attach_wrapper(obj, func=None): #访问函数接收对应的装饰函数if func is None:return partial(attach_wrapper, obj)setattr(obj, func.__name__, func) #访问函数设置对应装饰函数的属性值return funcdef logged(level, name=None, message=None):def decorate(func):logname = name if name else func.__module__log = logging.getLogger(logname)logmsg = message if message else func.__name__@wraps(func)def wrapper(*args, **kwargs):log.log(level, logmsg)return func(*args, **kwargs)# Attach setter functions@attach_wrapper(wrapper) #访问函数被作为一个属性赋值给装饰函数def set_level(newlevel):nonlocal level #类似于全局变量 定义可以访问外部函数的变量level = newlevel #设置新值的同时也会改变外部函数的值@attach_wrapper(wrapper)def set_message(newmsg):nonlocal logmsglogmsg = newmsgreturn wrapperreturn decorate# Example use
@logged(logging.DEBUG)
def add(x, y):return x + y@logged(logging.CRITICAL, 'example')
def spam():print('Spam!')>>> import logging
>>> logging.basicConfig(level=logging.DEBUG)
>>> add(2, 3)
DEBUG:__main__:add
5
>>> # Change the log message
>>> add.set_message('Add called')
>>> add(2, 3)
DEBUG:__main__:Add called
5
>>> # Change the log level
>>> add.set_level(logging.WARNING)
>>> add(2, 3)
WARNING:__main__:Add called
5
>>>

(3)带可选参数的装饰器

from functools import wraps, partial
import loggingdef logged(func=None, *, level=logging.DEBUG, name=None, message=None):if func is None: #如果装饰函数传入参数为空return partial(logged, level=level, name=name, message=message) #利用 functools.partial方法,它会返回一个未完全初始化的自身,除了被装饰函数外其他参数都已经确定下来了logname = name if name else func.__module__log = logging.getLogger(logname)logmsg = message if message else func.__name__@wraps(func)def wrapper(*args, **kwargs):log.log(level, logmsg)return func(*args, **kwargs)return wrapper# Example use
@logged
def add(x, y):return x + y@logged(level=logging.CRITICAL, name='example')
def spam():print('Spam!')

4.类装饰器

(1)将装饰器定义为类的一部分

在类中定义装饰器,并将其作用在其他函数或方法上

from functools import wrapsclass A:# Decorator as an instance methoddef decorator1(self, func):@wraps(func)def wrapper(*args, **kwargs):print('Decorator 1')return func(*args, **kwargs)return wrapper# Decorator as a class method@classmethoddef decorator2(cls, func):@wraps(func)def wrapper(*args, **kwargs):print('Decorator 2')return func(*args, **kwargs)return wrapper# 实例调用
a = A()
@a.decorator1
def spam():pass
# 类调用
@A.decorator2
def grok():pass

(2)将装饰器定义为类

为了将装饰器定义成一个实例,你需要确保它实现了 __call__() 和 __get__() 方法

import types
from functools import wrapsclass Profiled: #可以将它当做一个普通的装饰器来使用def __init__(self, func):wraps(func)(self)self.ncalls = 0def __call__(self, *args, **kwargs):self.ncalls += 1return self.__wrapped__(*args, **kwargs)def __get__(self, instance, cls):if instance is None:return selfelse:return types.MethodType(self, instance)@Profiled #类外调用
def add(x, y):return x + yclass Spam:@Profiled #类内使用def bar(self, x):print(self, x)>>> add(2, 3)
5
>>> add(4, 5)
9
>>> add.ncalls #调用次数
2
>>> s = Spam() #类实例化 调用 类内使用
>>> s.bar(1)
<__main__.Spam object at 0x10069e9d0> 1
>>> s.bar(2)
<__main__.Spam object at 0x10069e9d0> 2
>>> s.bar(3)
<__main__.Spam object at 0x10069e9d0> 3
>>> Spam.bar.ncalls
3

5.为类和静态方法提供装饰器

类或静态方法提供装饰器是很简单的,不过要确保装饰器在 @classmethod 或 @staticmethod 之前

import time
from functools import wraps# A simple decorator
def timethis(func):@wraps(func)def wrapper(*args, **kwargs):start = time.time()r = func(*args, **kwargs)end = time.time()print(end-start)return rreturn wrapper# Class illustrating application of the decorator to different kinds of methods
class Spam:@timethisdef instance_method(self, n):print(self, n)while n > 0:n -= 1@classmethod@timethisdef class_method(cls, n):print(cls, n)while n > 0:n -= 1@staticmethod #注意 保证两个装饰器的先后顺序 如果顺序颠倒则报错 因为@classmethod 和 @staticmethod 实际上并不会创建可直接调用的对象, 而是创建特殊的描述器对象。因此当你试着在其他装饰器中将它们当做函数来使用时就会出错@timethisdef static_method(n):print(n)while n > 0:n -= 1>>> s = Spam()
>>> s.instance_method(1000000)
<__main__.Spam object at 0x1006a6050> 1000000
0.11817407608032227
>>> Spam.class_method(1000000)
<class '__main__.Spam'> 1000000
0.11334395408630371
>>> Spam.static_method(1000000)
1000000
0.11740279197692871
>>>

6.装饰器的功能

(1)装饰器为被包装函数增加参数

可以使用关键字参数来给被包装函数增加额外参数

from functools import wrapsdef optional_debug(func):@wraps(func)def wrapper(*args, debug=False, **kwargs):if debug:print('Calling', func.__name__)return func(*args, **kwargs)return wrapper>>> @optional_debug
... def spam(a,b,c):
...     print(a,b,c)
...
>>> spam(1,2,3)
1 2 3
>>> spam(1,2,3, debug=True)
Calling spam
1 2 3
>>>

(2)使用装饰器扩充类的功能

在不希望使用继承或重写的方式来修改它的方法,使用装饰器为最佳方案

def log_getattribute(cls):# Get the original implementationorig_getattribute = cls.__getattribute__# Make a new definitiondef new_getattribute(self, name): #新方法 重写内置__getattribute__print('getting:', name)return orig_getattribute(self, name)# Attach to the class and returncls.__getattribute__ = new_getattributereturn cls# Example use
@log_getattribute #调用实例 装饰器
class A:def __init__(self,x):self.x = xdef spam(self):pass>>> a = A(42)
>>> a.x
getting: x
42
>>> a.spam()
getting: spam
>>>

7.使用元类控制实例的创建

(1)用户只能调用这个类的静态方法,而不能使用通常的方法来创建它的实例

class NoInstances(type):def __call__(self, *args, **kwargs):raise TypeError("Can't instantiate directly")class Spam(metaclass=NoInstances):@staticmethoddef grok(x):print('Spam.grok')>>> Spam.grok(42)
Spam.grok
>>> s = Spam()
>>> Traceback (most recent call last):File "<stdin>", line 1, in <module>File "example1.py", line 7, in __call__raise TypeError("Can't instantiate directly")
TypeError: Can't instantiate directly

(2)单例模式(一个类只能有一个实例化对象)

class Singleton(type):def __init__(self, *args, **kwargs):self.__instance = Nonesuper().__init__(*args, **kwargs)def __call__(self, *args, **kwargs):if self.__instance is None:self.__instance = super().__call__(*args, **kwargs)return self.__instanceelse:return self.__instance# Example
class Spam(metaclass=Singleton): #指明类型def __init__(self):print('Creating Spam')>>> a = Spam()
Creating Spam
>>> b = Spam()
>>> a is b
True
>>> c = Spam()
>>> a is c
True
>>>

8.定义有可选参数的元类

class MyMeta(type):# Optional@classmethoddef __prepare__(cls, name, bases, *, debug=False, synchronize=False):# __prepare__() 方法在所有类定义开始执行前首先被调用,用来创建类命名空间。 通常来讲,这个方法只是简单的返回一个字典或其他映射对象passreturn super().__prepare__(name, bases)# Requireddef __new__(cls, name, bases, ns, *, debug=False, synchronize=False):# __new__() 方法被用来实例化最终的类对象。它在类的主体被执行完后开始执行passreturn super().__new__(cls, name, bases, ns)# Requireddef __init__(self, name, bases, ns, *, debug=False, synchronize=False):# __init__() 方法最后被调用,用来执行其他的一些初始化工作passsuper().__init__(name, bases, ns)class Spam(metaclass=MyMeta, debug=True, synchronize=True): #使用 ``metaclass``关键字参数来指定特定的元类
#为了使元类支持这些关键字参数,必须确保在 __prepare__() , __new__() 和 __init__() 方法中 都使用强制关键字参数pass

9.在类上强制使用编程规约

如果你想监控类的定义,通常可以通过定义一个元类。一个基本元类通常是继承自 type 并重定义它的 __new__() 方法 或者是 __init__() 方法。

class NoMixedCaseMeta(type):#该元类拒绝任何有混合大小写名字作为方法的类定义def __new__(cls, clsname, bases, clsdict):for name in clsdict:if name.lower() != name:raise TypeError('Bad attribute name: ' + name)return super().__new__(cls, clsname, bases, clsdict)class Root(metaclass=NoMixedCaseMeta):#为了使用这个元类,通常要将它放到到一个顶级父类定义中,然后其他的类继承这个顶级父类passclass A(Root):def foo_bar(self): # Okpassclass B(Root):def fooBar(self): # TypeErrorpass

注意:
元类的一个关键特点是它允许你在定义的时候检查类的内容。在重新定义 __init__() 方法中, 你可以很轻松的检查类字典、父类等等。并且,一旦某个元类被指定给了某个类,那么就会被继承到所有子类中去。 因此,一个框架的构建者就能在大型的继承体系中通过给一个顶级父类指定一个元类去捕获所有下面子类的定义。

10. 在定义的时候初始化类的成员

在类定义时就执行初始化或设置操作是元类的一个典型应用场景。本质上讲,一个元类会在定义时被触发, 这时候你可以执行一些额外的操作。

import operatorclass StructTupleMeta(type): #类 StructTupleMeta 获取到类属性 _fields 中的属性名字列表, 然后将它们转换成相应的可访问特定元组槽的方法def __init__(cls, *args, **kwargs):super().__init__(*args, **kwargs)for n, name in enumerate(cls._fields):setattr(cls, name, property(operator.itemgetter(n))) #函数 operator.itemgetter() 创建一个访问器函数, 然后 property() 函数将其转换成一个属性class StructTuple(tuple, metaclass=StructTupleMeta):_fields = []def __new__(cls, *args):if len(args) != len(cls._fields):raise ValueError('{} arguments required'.format(len(cls._fields)))return super().__new__(cls,args)

11.避免重复的属性方法

class Person:def __init__(self, name ,age):self.name = nameself.age = age@propertydef name(self):return self._name@name.setterdef name(self, value):if not isinstance(value, str):raise TypeError('name must be a string')self._name = value@propertydef age(self):return self._age@age.setterdef age(self, value):if not isinstance(value, int):raise TypeError('age must be an int')self._age = value

上述代码,在类中需要重复的定义一些执行相同逻辑的属性方法

通过创建一个函数用来定义属性并返回它 来进行简化

def typed_property(name, expected_type): #生成属性并返回这个属性对象 效果跟将它里面的代码放到类定义中去是一样的storage_name = '_' + name@propertydef prop(self):return getattr(self, storage_name)@prop.setterdef prop(self, value):if not isinstance(value, expected_type):raise TypeError('{} must be a {}'.format(name, expected_type))setattr(self, storage_name, value)return prop# Example use
class Person:name = typed_property('name', str)age = typed_property('age', int)def __init__(self, name, age):self.name = nameself.age = age

12.定义上下文管理器的简单方法

实现一个新的上下文管理器的最简单的方法就是使用 contexlib 模块中的 @contextmanager 装饰器。

import time
from contextlib import contextmanager@contextmanager
def list_transaction(orig_list):working = list(orig_list)yield workingorig_list[:] = working>>> items = [1, 2, 3]
>>> with list_transaction(items) as working:
...     working.append(4)
...     working.append(5)
...
>>> items
[1, 2, 3, 4, 5]

@contextmanager 应该仅仅用来写自包含的上下文管理函数。 如果你有一些对象(比如一个文件、网络连接或锁),需要支持 with 语句,你需要定义一个类里面包含一个 __enter__() 和一个 __exit__() 方法,那么你就需要单独实现 __enter__() 方法和 __exit__() 方法

13.在局部变量域中执行代码

想在使用范围内执行某个代码片段,并且希望在执行后所有的结果都不可见。

>>> a = 13
>>> exec('b = a + 1')
>>> print(b)
14
>>>

上述代码中'b'为局部变量 无法在其他域内使用

>>> def test():
...     a = 13
...     exec('b = a + 1')
...     print(b)
...
>>> test()
Traceback (most recent call last):File "<stdin>", line 1, in <module>File "<stdin>", line 4, in test
NameError: global name 'b' is not defined
>>>

需要在调用 exec() 之前使用 locals() 函数来得到一个局部变量字典。 之后你就能从局部字典中获取修改过后的变量值了

>>> def test():
...     a = 13
...     loc = locals() #当你调用 locals() 获取局部变量时,你获得的是传递给 exec() 的局部变量的一个拷贝
...     exec('b = a + 1')
...     b = loc['b'] #每次它被调用的时候, locals() 会获取局部变量值中的值并覆盖字典中相应的变量,进行值更新 即会更新'b'的值
...     print(b)
...
>>> test()
14
>>>

14.解析与分析Python源码

Python能够计算或执行字符串形式的源代码

>>> x = 42
>>> eval('2 + 3*4 + x')
56
>>> exec('for i in range(10): print(i)')
0
1
2
3
4
5
6
7
8
9
>>>

ast 模块能被用来将Python源码编译成一个可被分析的抽象语法树(AST)

dis 模块可以被用来输出任何Python函数的反编译结果

Python(九)元编程相关推荐

  1. python之元编程

    一.什么是元编程 元编程是一种编写计算机程序的技术,这些程序可以将自己看作数据,因此你可以在运行时对它进行内省.生成和/或修改. Python在语言层面对函数.类等基本类型提供了内省及实时创建和修改的 ...

  2. python九:元祖(tuple)

    # 元祖类tuple # 元祖用小括号括起来,一般都会在最后一个元素后加个逗号,区分函数(函数也是用小括号括起来的) # 元祖的一级元素不可被修改,增加,删除.但元祖里嵌套的列表等可以被修改 tu = ...

  3. OpenERP与Python 元编程

    Python元编程被称为"黑魔法".Python界的传奇人物Tim Peters有云: 引用 Python的元编程这种黑魔法99%的人都无需了解,如果你拿不准是否应该用到它时,你不 ...

  4. Python中的元编程:一个关于修饰器和元类的简单教程

    作者 | Saurabh Kukade 译者 | 刘畅 出品 | AI科技大本营(ID:rgznai100) 最近,作者遇到一个非常有趣的概念,它就是用 Python 进行元编程.我想在本文中分享我对 ...

  5. 深入浅出Python元编程

    隔壁的Java 世界为了创建一个对象搞得鸡飞狗跳,这边的Python解释器倒是乐得清闲. (参见:<当创建对象时......>) 我作为他的第n任助手正式上岗. "老大,有程序员 ...

  6. python函数myproduct_OpenERP与Python 元编程

    Python元编程被称为"黑魔法".Python界的传奇人物Tim Peters有云: 引用 Python的元编程这种黑魔法99%的人都无需了解,如果你拿不准是否应该用到它时,你不 ...

  7. 深入浅出使用python编程_深入浅出Python元编程

    来源:  码农翻身   作者:刘欣 隔壁的Java 世界为了创建一个对象搞得鸡飞狗跳,这边的Python解释器倒是乐得清闲. 我作为他的第n任助手正式上岗. "老大,有程序员要创建对象,怎么 ...

  8. 深入浅出Python元编程,不仅仅是Metaclass

    隔壁的Java 世界为了创建一个对象搞得鸡飞狗跳,这边的Python解释器倒是乐得清闲. (参见:<当创建对象时......>) 我作为他的第n任助手正式上岗. 更多Python视频.源码 ...

  9. Python中的元编程(Meta-Programming)

    元编程:是编写出可以操作的代码的行为,即用代码来操作另一个代码. Python中的元编程:一种构建函数和类的行为,这些函数和类可以通过修改.包装现有代码或生成代码来进行操纵. Python中元学习的实 ...

  10. python元编程详解

    什么是元编程 软件开发中很重要的一条原则就是"不要重复自己的工作(Don't repeat youself)",也就是说当我们需要复制粘贴代码时候,通常都需要寻找一个更加优雅的解决 ...

最新文章

  1. c语言已知先序还原二叉树,(c++ 递归)先序 中序 还原二叉树
  2. 考考你:输入数字,判定空格和回车
  3. golang 赋值错误 no new variables on left side of :=
  4. eclipse编辑java_15个小type:教你高效使用Eclipse Java IDE
  5. 汉诺塔问题详细解析zufeoj
  6. sklearn分类器算法:逻辑回归及案例分析
  7. 招贤纳士|360WEB平台云平台部招人啦
  8. react月份选择控件_一款很实用的ReactJS日期范围选择控件
  9. python的作用域分别有几种_Python作用域和命名空间
  10. 酒水茶饮行业的门店管理系统进销存软件怎么挑选?
  11. ncnn paramdictmodelbin
  12. 【项目实训】微信公众号获取用户openid
  13. Maven项目之一号店——注册与登录
  14. ASP.NET性能调优
  15. 零基础学前端系列教程 | 和前端谈恋爱的第004天——打扮漂亮
  16. 云原生的进一步具象化
  17. html5 堆栈不足,超简单!不用PS也能玩堆栈摄影
  18. 浙工商计算机学院教师,浙工商计算机与信息工程学院导师介绍:魏贵义
  19. 病毒丨3601lpk劫持病毒分析
  20. 如何快速而准确地进行 IP 和端口信息扫描:渗透测试必备技能

热门文章

  1. 一张图看懂半导体产业链
  2. kettle java脚本_kettle 调优
  3. 错误 4 error C2039: “Sleep”: 不是“boost::this_thread”的成员
  4. 总结Linux系统压缩和解压文件指令——gzip/gunzip 指令、zip/unzip 指令、tar 指令
  5. 最长回文子串(多种解法,附马拉车算法)
  6. 朵拉云提供最简单的免费虚拟化方案:Hyper-V Server + Windows Admin Center
  7. Docker 入门实战-ssh连接容器
  8. AE调用切图GP工具
  9. python从服务器下载文件_如何用Python从本地服务器下载文件
  10. 学校的远程教育网络课程的整站ASP源代码