python函数参数_深入理解Python特性(4)

装饰接受参数的函数

如何装饰带有参数的函数呢?

Python中用于变长参数的*args和**kwargs特性就能派上用场了:

9de42a890a13886108df7ff71b66bdfa.png

这个装饰器有两个值得注意的地方:

  • 它在wrapper闭包定义中使用*和**操作符收集所有位置参数和关键字参数,并将其存储在变量args和kwargs中;

  • 接着,wrapper闭包使用*和** “参数解包”操作符将收集的参数转发到原输入函数。

现在将proxy装饰器中介绍的技术扩展成更有用的示例。下面的trace装饰器在执行时会记录函数参数和结果:

72f6dda8517ae04f3ab001884da9b85a.png

使用trace对函数进行装饰后,调用该函数会打印传递给装饰函数的参数及其返回值:

f42380c4f98604d96c6da44767433c74.png

4f11b1b6ae1506e21bc14dc0ef7f7c22.png

这仍然是一个简单的演示示例,不过有助于调试程序。

如何编写“可调式”的装饰器

在使用装饰器时,实际上是使用一个函数替换另一个函数。这个过程的一个缺点就是“隐藏”了(未装饰)原函数所附带的一些元数据。例如,包装闭包隐藏了原函数的名称,文档字符串和参数列表:

1e6b0d3c471b1a377526ddec519a24b4.png

如果试图访问这个函数的任何元数据,看到的都是包装闭包的元数据:

575abe8302f2665b4dae85506ca3c5ce.png

这增加了调试程序和使用Python解释器的难度。幸运的是,有一个方法能避免这个问题:使用Python标准库中functools.wraps装饰器。

在自己的装饰器中使用functools.wraps能够将丢失的元数据从被装饰的函数复制到装饰器闭包中:

2ccd11e7774a5a66723ba65f1966f616.png

将functools.wraps应用到由装饰器返回的封装闭包中,会获得原函数的文档字符串和其他元数据:

9831168a2b7e6de7c60939bc64ab15cb.png

建议最好在自己编写的所有装饰器中都使用functools.wraps。可以减少自己和其他人的调试时间。

有关装饰器的内容就此完结!

有趣的*args和**kwargs

*args和**kwargs参数到底有什么用呢?它们能让函数接受可选参数,因此能在模块和类中创建灵活的API:

9eac342ca64e0476f17b5679e8c45810.png

上述函数至少需要一个名为required的参数,但也可以接受额外的位置参数和关键字参数。如果用额外的参数调用该函数,args将收集额外的位置参数组成元组,因为这个参数名称带有*前缀。kwargs会收集额外的关键字参数组成字典,因为参数名称带有**前缀。如果不传递额外的参数,那么args和kwargs都为空。

04b324099fc80639ed6523575e46ed08.png

参数args和kwargs只是一个命名约定。哪怕将其命名为*parms和**argv,前面的例子也能正常工作。实际起作用的是*和**操作符

传递可选参数或关键字参数

可选参数或关键字参数还可以从一个函数传递到另一个函数。这需要用到解包操作符*和**将参数传递给被调用的函数。

参数在传递之前还可以修改:

865ccced41be45c0f1120f1891bae128.png

这种技术适用于创建子类和编写包装函数。例如在扩展父类的行为时,子类中的构造函数不用再带有完整的参数列表,因而适用于处理那些不受我们控制的API:

e1395c419ff2769fe9f4fe59ddfc94ef.png

AlwaysBlueCar构造函数只是将所有参数传递给它的父类,然后重写一个内部属性。这意味着如果父类的构造函数发生变化,AlwaysBlueCar仍然可以按预期运行。

不过缺点是对于AlwaysBlueCar构造函数如果不查看父类,就不知道函数接收哪些参数。一般情况下,自己定义的类层次中并不会用到这种技术。这通常用于修改或覆盖某些外部类中的行为,而自己又无法控制这些外部类。

该技术可能有用的另一个场景是编写包装函数,如装饰器。这种情况下,我们通常也想接受所有传递给包装函数的参数:

469008419d0587e0977c2718b98abdb3.png

*和**操作符有一个非常棒但有点神秘的功能,那就是用来从序列和字典中“解包”函数参数。

bc381be1048fedb0374ac7849f84a4d4.png

452db2ac25f2b57cf998b93d71d6a5ad.png

而使用*操作符进行函数参数解包能更好的地处理这种情况:

aa1b9c5d39d7aac32d8eee644c754200.png

在函数调用时,在可迭代对象前面放一个*能解包这个参数,将其中的元素作为单独的位置参数传递给被调用的函数。

这种技术适用于任何可迭代对象,包括生成器表达式。在生成器上使用*操作符会消耗生成器中的所有元素,并将它们传递给函数:

bfac76a866a80181babda29ec6c25d82.png

类似的还有**操作符,用于从字典中解包关键字参数:

01fbc1eea7328d399bd04dd05a0cb1d3.png

由于字典时无序的,因此解包时会匹配字典键和函数参数:x参数接受字典中与“x”键相关联的值。如果使用*操作符解包字典,则所有的键将以随机顺序传递给函数:

e8954c5f3842532ea0d345c162d495ff.png

Python的函数参数解包功能带来了很多灵活性。不一定要为程序所需的数据类型实现一个类,使用简单的内置数据结构(如元组或列表)就足够了,这样有助于降低代码的复杂度。

今天就到这了,感谢阅读,下节见246f98814d76b0c3a651b25f41cc696e.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值