资讯专栏INFORMATION COLUMN

PyTips 0x0f - Python 修饰器与 functools

dingding199389 / 2926人阅读

项目地址:https://git.io/pytips

Python 的修饰器是一种语法糖(Syntactic Sugar),也就是说:

@decorator
@wrap
def func():
    pass

是下面语法的一种简写:

def func():
    pass
func = decorator(wrap(func))

关于修饰器的两个主要问题:

修饰器用来修饰谁

谁可以作为修饰器

修饰函数

修饰器最常见的用法是修饰新定义的函数,在 0x0d 上下文管理器中提到上下文管理器主要是为了更优雅地完成善后工作,而修饰器通常用于扩展函数的行为或属性:

def log(func):
    def wraper():
        print("INFO: Starting {}".format(func.__name__))
        func()
        print("INFO: Finishing {}".format(func.__name__))
    return wraper

@log
def run():
    print("Running run...")
run()
INFO: Starting run
Running run...
INFO: Finishing run
修饰类

除了修饰函数之外,Python 3.0 之后增加了对新定义类的修饰(PEP 3129),但是对于类别属性的修改可以通过 Metaclasses 或继承来实现,而新增加的类别修饰器更多是出于 Jython 以及 IronPython 的考虑,但其语法还是很一致的:

from time import sleep, time
def timer(Cls):
    def wraper():
        s = time()
        obj = Cls()
        e = time()
        print("Cost {:.3f}s to init.".format(e - s))
        return obj
    return wraper
@timer
class Obj:
    def __init__(self):
        print("Hello")
        sleep(3)
        print("Obj")
o = Obj()
Hello
Obj
Cost 3.005s to init.
类作为修饰器

上面两个例子都是以函数作为修饰器,因为函数才可以被调用(callable) decorator(wrap(func))。除了函数之外,我们也可以定义可被调用的类,只要添加 __call__ 方法即可:

class HTML(object):
    """
        Baking HTML Tags!
    """
    def __init__(self, tag="p"):
        print("LOG: Baking Tag <{}>!".format(tag))
        self.tag = tag
    def __call__(self, func):
        return lambda: "<{0}>{1}".format(self.tag, func(), self.tag)

@HTML("html")
@HTML("body")
@HTML("div")
def body():
    return "Hello"

print(body())
LOG: Baking Tag !
LOG: Baking Tag !
LOG: Baking Tag 
!
Hello
传递参数

在实际使用过程中,我们可能需要向修饰器传递参数,也有可能需要向被修饰的函数(或类)传递参数。按照语法约定,只要修饰器 @decorator 中的 decorator 是可调用即可,decorator(123) 如果返回一个新的可调用函数,那么也是合理的,上面的 @HTML("html") 即是一例,下面再以 flask 的路由修饰器为例说明如何传递参数给修饰器:

RULES = {}
def route(rule):
    def decorator(hand):
        RULES.update({rule: hand})
        return hand
    return decorator
@route("/")
def index():
    print("Hello world!")

def home():
    print("Welcome Home!")
home = route("/home")(home)

index()
home()
print(RULES)
Hello world!
Welcome Home!
{"/": , "/home": }

向被修饰的函数传递参数,要看我们的修饰器是如何作用的,如果像上面这个例子一样未执行被修饰函数只是将其原模原样地返回,则不需要任何处理(这就把函数当做普通的值一样看待即可):

@route("/login")
def login(user = "user", pwd = "pwd"):
    print("DB.findOne({{{}, {}}})".format(user, pwd))
login("hail", "python")
DB.findOne({hail, python})

如果需要在修饰器内执行,则需要稍微变动一下:

def log(f):
    def wraper(*args, **kargs):
        print("INFO: Start Logging")
        f(*args, **kargs)
        print("INFO: Finish Logging")
    return wraper

@log
def run(hello = "world"):
    print("Hello {}".format(hello))
run("Python")
INFO: Start Logging
Hello Python
INFO: Finish Logging
functools

由于修饰器将函数(或类)进行包装之后重新返回:func = decorator(func),那么有可能改变原本函数(或类)的一些信息,以上面的 HTML 修饰器为例:

@HTML("body")
def body():
    """
        return body content
    """
    return "Hello, body!"
print(body.__name__)
print(body.__doc__)
LOG: Baking Tag !

None

因为 body = HTML("body")(body) ,而 HTML("body").__call__() 返回的是一个 lambda 函数,因此 body 已经被替换成了 lambda,虽然都是可执行的函数,但原来定义的 body 中的一些属性,例如 __doc__/__name__/__module__ 都被替换了(在本例中__module__没变因为都在同一个文件中)。为了解决这一问题 Python 提供了 functools 标准库,其中包括了 update_wrapperwraps 两个方法(源码)。其中 update_wrapper 就是用来将原来函数的信息赋值给修饰器中返回的函数:

from functools import update_wrapper
"""
functools.update_wrapper(wrapper, wrapped[, assigned][, updated])
"""


class HTML(object):
    """
        Baking HTML Tags!
    """
    def __init__(self, tag="p"):
        print("LOG: Baking Tag <{}>!".format(tag))
        self.tag = tag
    def __call__(self, func):
        wraper = lambda: "<{0}>{1}".format(self.tag, func(), self.tag)
        update_wrapper(wraper, func)
        return wraper
@HTML("body")
def body():
    """
        return body content!
    """
    return "Hello, body!"
print(body.__name__)
print(body.__doc__)
LOG: Baking Tag !
body

        return body content!
    

有趣的是 update_wrapper 的用法本身就很像是修饰器,因此 functools.wraps 就利用 functools.partial(还记得函数式编程中的偏应用吧!)将其变成一个修饰器:

from functools import update_wrapper, partial

def my_wraps(wrapped):
    return partial(update_wrapper, wrapped=wrapped)

def log(func):
    @my_wraps(func)
    def wraper():
        print("INFO: Starting {}".format(func.__name__))
        func()
        print("INFO: Finishing {}".format(func.__name__))
    return wraper

@log
def run():
    """
    Docs" of run
    """
    print("Running run...")
print(run.__name__)
print(run.__doc__)
run

    Docs" of run
    

欢迎关注公众号 PyHub!

参考

Python修饰器的函数式编程

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/37841.html

相关文章

  • PyTips 0x02 - Python 中的函数式编程

    摘要:项目地址中的函数式编程函数式编程英语或称函数程序设计,又称泛函编程,是一种编程范型,它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及易变对象。 项目地址:https://git.io/pytips Python 中的函数式编程 函数式编程(英语:functional programming)或称函数程序设计,又称泛函编程,是一种编程范型,它将电脑运算视为数学上的函数计算,并且...

    FrozenMap 评论0 收藏0
  • PyTips 0x01 - 迭代器与生成器

    摘要:项目地址迭代器与生成器迭代器与生成器是中比较常用又很容易混淆的两个概念,今天就把它们梳理一遍,并举一些常用的例子。生成器前面说到创建迭代器有种方法,其中第三种就是生成器。 项目地址:https://git.io/pytips 迭代器与生成器 迭代器(iterator)与生成器(generator)是 Python 中比较常用又很容易混淆的两个概念,今天就把它们梳理一遍,并举一些常用的例...

    chemzqm 评论0 收藏0
  • 流畅的 Python - 5. 装饰器与闭包

    摘要:看了这一章,发现原来是装饰器,又一新知识。期间,装饰器会做一些额外的工作。书中介绍了模块中的三个装饰器。另一个是,这个装饰器把函数结果保存了起来,避免传入相同参数时重复计算。叠放不奇怪,装饰器返回的就是函数或可调用对象。 在 Web 框架 Flask 中,最常看到的或许是以@app.route开头的那行代码。由于还是刚接触 Flask,所以对这种语法还不熟悉。看了这一章,发现原来是装饰...

    Markxu 评论0 收藏0
  • PyTips 0x0d - Python 上下文管理器

    摘要:项目地址引入了语句与上下文管理器类型,其主要作用包括保存重置各种全局状态,锁住或解锁资源,关闭打开的文件等。了解了语句的执行过程,我们可以编写自己的上下文管理器。生成器的写法更简洁,适合快速生成一个简单的上下文管理器。 项目地址:https://git.io/pytips Python 2.5 引入了 with 语句(PEP 343)与上下文管理器类型(Context Manager ...

    yuxue 评论0 收藏0
  • PyTips 0x17-Python 中的枚举类型

    摘要:中的枚举类型枚举类型可以看作是一种标签或是一系列常量的集合,通常用于表示某些特定的有限集合,例如星期月份状态等。 Python 中的枚举类型 枚举类型可以看作是一种标签或是一系列常量的集合,通常用于表示某些特定的有限集合,例如星期、月份、状态等。Python 的原生类型(Built-in types)里并没有专门的枚举类型,但是我们可以通过很多方法来实现它,例如字典、类等: WEEKD...

    yedf 评论0 收藏0

发表评论

0条评论

dingding199389

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<