利用装饰器对函数参数强制执行类型检查

在给出解决方案之前,本节的目标是提供一种手段对函数的输入参数类型做强制性的类型检查。下面这个简短的示例说明了这种思想:

>>> @typeassert(int, int)

... def add(x, y):

... return x + y

...

>>>

>>> add (2, 3)

5

>>> add(2, ‘HELLO')

Traceback (most recent call last):

File "", line 1, in

TypeError: Arguement y must be

>>>

现在,让我们看看装饰器@typeassert的实现:

from inspect import signature

from functools import wraps

def typeassert(*ty_args, **ty_kwargs):

def decorate(func):

# If in optimized mode, disable type checking

if not __debug__:

return func

# Map function argument names to supplied types

sig = signature(func)

bound_types = sig.bind_partial(*ty_args, **ty_kwargs).arguments

@wraps(func)

def wrapper(*args, **kwargs):

bound_values = sig.bind(*args, **kwargs)

# Enforce type assertions across supplied arguments

for name, value in bound_values.arguments.items():

if name in bound_types:

if not isinstance(value, bound_types[name]):

raise TypeError(

'Argument{}must be{}'.format(name, bound_types[name]))

return func(*args, **kwargs)

return wrapper

return decorate

@typeassert(int, int)

def add(x, y):

return x + y

我们会发现这个装饰器相当灵活,既允许指定函数参数的所有类型,也可以只指定一部分子集。此外,类型既可以通过位置参数来指定,也可以通过关键字参数来指定。

@typeassert(int, list)

def bar(x, items=None):

if items is None:

items = []

items.append(x)

return items

bar(2)

bar(2, [1, 2, 3])

这一节展示了一个高级的装饰器的例子,这里面有一些重要并且非常有用的概念。

首先,装饰器的一个特性就是它们只会在函数定义的时候应用一次。在某些情况下,我们可能想禁止由装饰器添加的功能。为了做到这点,我们只需要让装饰器函数返回那个未经过包装的函数就好

在类中定义装饰器

我们想在类中定义一个装饰器,但是首先我们需要理清装饰器将以什么方式来应用,看下面这个示例:

from functools import wraps

class A:

# Decorator as an instance method

def decorator1(self, func):

@wraps(func)

def wrapper(*args, **kwargs):

print('Decorator 1')

return func(*args, **kwargs)

return wrapper

# Decorator as a class method

@classmethod

def decorator2(cls, func):

@wraps(func)

def wrapper(*args, **kwargs):

print('Decorator 2')

return func(*args, **kwargs)

return wrapper

在类中定义装饰器看起来可能有点古怪,但是在标准库中也可以找到这样的例子。特别是当内建的装饰器@property实际上是一个拥有getter(), setter()和deleter()方法的类,每个方法都可以作为装饰器:

class Person:

# Create a property instance

first_name = property()

# Apply decorator methods

@first_name.getter

def first_name(self):

return self._first_name

@first_name.setter

def first_name(self, value):

if not isinstance(value, str):

raise TypeError('Expected a string')

self._first_name = value

至于为什么要定义成这样的形式,关键原因就在于这里的多个装饰器方法都在操纵property实例的状态。因此,如果需要装饰器在背后记录或者合并信息,这就是一个非常好的方法。

在类中定义装饰器有个难理解的地方就是对于额外参数 self 或 cls 的正确使用。 尽管最外层的装饰器函数比如 decorator1() 或 decorator2() 需要提供一个 self 或 cls 参数, 但是在两个装饰器内部被创建的 wrapper() 函数并不需要包含这个 self 参数。 你唯一需要这个参数是在你确实要访问包装器中这个实例的某些部分的时候。其他情况下都不用去管它。

对于类里面定义的包装器还有一点比较难理解,就是在涉及到继承的时候。 例如,假设你想让在A中定义的装饰器作用在子类B中。你需要像下面这样写:

class B(A):

@A.decorator2

def bar(self):

pass

也就是说,装饰器要被定义成类方法并且你必须显式的使用父类名去调用它。 你不能使用 @B.decorator2 ,因为在方法定义时,这个类B还没有被创建。

把装饰器定义为类

我们想用装饰器来包装函数,但是希望得到的结果是一个可以调用的实例。为了将装饰器定义成一个实例,我们需要确保它实现了__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, cls):

if instance is None:

return self

else:

return types.MethodType(self, instance)

你可以将它当做一个普通的装饰器来使用,在类里面或外面都可以:

@Profiled

def add(x, y):

return x + y

class Spam:

@Profiled

def bar(self, x):

print(self, x)

在Shell中:

>>> add(2, 3)

5

>>> add(4, 5)

9

>>> add.ncalls

2

>>> s = Spam()

>>> s.bar(1)

<__main__.spam object at> 1

>>> s.bar(2)

<__main__.spam object at> 2

>>> s.bar(3)

<__main__.spam object at> 3

>>> Spam.bar.ncalls

3

将装饰器定义成类通常是很简单的。但是这里还是有一些细节需要解释下,特别是当你想将它作用在实例方法上的时候。

首先,使用 functools.wraps() 函数的作用跟之前还是一样,将被包装函数的元信息复制到可调用实例中去。

其次,通常很容易会忽视上面的 __get__() 方法。如果你忽略它,保持其他代码不变再次运行, 你会发现当你去调用被装饰实例方法时出现很奇怪的问题。例如:

>>> s = Spam()

>>> s.bar(3)

Traceback (most recent call last):

...

TypeError: bar() missing 1 required positional argument: 'x'

出错原因是当方法函数在一个类中被查找时,它们的__get__()方法依据描述器协议被调用, 在前面的小节已经讲述过描述器协议了。在这里,__get__()的目的是创建一个绑定方法对象 (最终会给这个方法传递self参数)。下面是一个例子来演示底层原理:

>>> s = Spam()

>>> def grok(self, x):

... pass

...

>>> grok.__get__(s, Spam)

>

>>>

__get__() 方法是为了确保绑定方法对象能被正确的创建。 type.MethodType() 手动创建一个绑定方法来使用。只有当实例被使用的时候绑定方法才会被创建。 如果这个方法是在类上面来访问, 那么 __get__() 中的instance参数会被设置成None并直接返回 Profiled 实例本身。 这样的话我们就可以提取它的 ncalls 属性了。

如果你想避免一些混乱,也可以考虑另外一个使用闭包和 nonlocal 变量实现的装饰器,这个在之前几节有讲到。例如:

import types

from functools import wraps

def profiled(func):

ncalls = 0

@wraps(func)

def wrapper(*args, **kwargs):

nonlocal ncalls

ncalls += 1

return func(*args, **kwargs)

wrapper.ncalls = lambda: ncalls

return wrapper

# Example

@profiled

def add(x, y):

return x + y

这个方式跟之前的效果几乎一样,除了对于ncalls的访问现在是通过一个被绑定为属性的函数来实现

参考书目:

《Python CookBook》作者:【美】 David Beazley, Brian K. Jones

Github地址:yidao620c/python3-cookbook​github.com

python 元编程有多强_马克的Python学习笔记#元编程 3相关推荐

  1. python画聚类树状图_影像组学学习笔记(36)-聚类树状图Dendrogram的python实现

    本笔记来源于B站Up主: 有Li 的影像组学系列教学视频 本节(36)主要介绍: 聚类树状图Dendrogram的python实现 应该注意一下scipy版本的问题:scipy 1.5.0版本画聚类树 ...

  2. python学习笔记——多线程编程

    python学习笔记--多线程编程 基础不必多讲,还是直接进入python. Python代码代码的执行由python虚拟机(也叫解释器主循环)来控制.Python在设计之初就考虑到要在主循环中,同时 ...

  3. 【编程不良人】MongoDB最新实战教程学习笔记

    简介 视频链接:01.简介和历史_哔哩哔哩_bilibili 文档地址: https://docs.mongodb.com/manual/ MongoDB教程:MongoDB 教程 | 菜鸟教程 注意 ...

  4. 学习笔记之编程达到一个高的境界就是自制脚本语言(图)

    学习笔记之编程达到一个高的境界就是自制脚本语言(图) 编程达到一个高的境界就是自制脚本语言,通过这可以精通编程里面的高深的技术,如编译原理.语言处理器.编译器与解释器,这些都是代表一个程序员实力的技术 ...

  5. 【初阶】unity3d官方案例_太空射击SpacingShooter 学习笔记 显示分数时,如何让函数之间相互交流...

    [初阶]unity3d官方案例_太空射击SpacingShooter 学习笔记 显示分数时,如何让函数之间相互交流 一.关于 显示分数时,如何让函数之间相互交流 这是一个非常好的逻辑问题 1 思路:主 ...

  6. ubuntu 安装Pangolin 过程_余辉亮的学习笔记的博客-CSDN博客_pangolin安装

    ubuntu 安装Pangolin 过程_余辉亮的学习笔记的博客-CSDN博客_pangolin安装

  7. ufldl学习笔记与编程作业:Multi-Layer Neural Network(多层神经网络+识别手写体编程)...

    ufldl学习笔记与编程作业:Multi-Layer Neural Network(多层神经网络+识别手写体编程) ufldl出了新教程,感觉比之前的好,从基础讲起,系统清晰,又有编程实践. 在dee ...

  8. 面向对象的编程思想写单片机程序——(3)学习笔记 之 程序分层、数据产生流程

    系列文章目录 面向对象的编程思想写单片机程序--(1)学习笔记 之 程序设计 面向对象的编程思想写单片机程序--(2)学习笔记 之 怎么抽象出结构体 面向对象的编程思想写单片机程序--(3)学习笔记 ...

  9. python编程的常用工具_小白学Python(2)——常用Python编程工具,Python IDE

    下载好Python,但是如何开始编程呢? 有几种方法, 1.第一个就是command lind 即为命令行的方式,也就是我们常说的cmd. 输入 win+ cmd 在命令行中再输入 python,即可 ...

  10. python类方法需要传入cls参数_如何从Python 3.x中的类定义传递参数到元类?

    这是如何从类定义向元类传递参数的python 3.x版本?问题,根据请求单独列出,因为答案与python 2.x明显不同. 在python 3.x中,如何将参数传递给元类的__prepare__.__ ...

最新文章

  1. 《从0到1学习Flink》—— Flink 读取 Kafka 数据批量写入到 MySQL
  2. 传统数据中心升级方案
  3. Yii2.X 多语言-类图
  4. phpcms9.6 ueditor_PHPCMS V9.6.6 修改版
  5. HDU-6470 Count (构造矩阵+矩阵快速幂)
  6. Spring Enable* 注解
  7. c语言中*用于指针,关于C语言中指针的理解
  8. TimeJot – Last Time 改名,新增中文界面、数字属性,还是那个时间线管理神器[Android]
  9. OpenLDAP 2.4.44 安装 + phpLDAPadmin 安装
  10. python 栈和队列_python实现栈和队列
  11. 冒死曝光这个软件,希望不要被封杀!
  12. 算术编码数据压缩Matlab报告,用matlab实现算术编码
  13. 4.14 在数字的左侧进行补零 [原创Excel教程]
  14. 电芯容量在前期循环中容量增加_锂电池随着使用次数增加而最大容量下降,为什么...
  15. 蜀门linux一键端,【蜀门】网游单机版 蜀门镜像一键端 送全套GM口令工具刷金币钻石...
  16. esayExcel 获取值 null 去除excel中换行 回车 水平制表符
  17. 10019---【Java并发之】BlockingQueue
  18. 计算机硬盘对考,台式机怎样让进行硬盘对拷(快速批量装机) 台式机让进行硬盘对拷(快速批量装机)的方法...
  19. 弱网测试究竟要怎么做,才能防止漏测?
  20. 巴比特 | 元宇宙每日必读:云南首个元宇宙产业园落户昆明,预计总投资 2600 万元,将探索开发NFT产品...

热门文章

  1. MYSQL数据库基础概念
  2. 一周第二次课(12月12日)
  3. 苹果该怎么办?特朗普誓言将会严厉惩罚外迁公司
  4. 关于 MRC 开发中的一些细节
  5. HDU多校练习第一场4608——I_Number
  6. 如何计算两向量的夹角
  7. rabbits php实现文件下载!
  8. 如何做软件需求分析(个人工作经验总结)
  9. cookie里面用到的关键字_Java的理解角度-关键字篇
  10. win8安装mysql出现2503_win8.1 安装MSI 出现问题,2502,2503!求高手解答,万分感谢!