0
点赞
收藏
分享

微信扫一扫

Python 函数式编程(五)

酷子腿长一米八 2021-09-28 阅读 27

通过上一篇文章的学习,我们已经知道:

本片将继续深入探索 Python 中函数的特点,并详细介绍装饰器的更多精彩内容!

一. 深入探索函数

一等公民

我们知道,在 Python 中函数是一等公民,因此可以像基本数据类型一样,赋值给另一个变量:

def my_func(content="Strive for a hundred years and set sail on a new journey"):
    return content.upper()

运行:

>> my_func()
'STRIVE FOR A HUNDRED YEARS AND SET SAIL ON A NEW JOURNEY'
>> func_upper = my_func
>> func_upper()
'STRIVE FOR A HUNDRED YEARS AND SET SAIL ON A NEW JOURNEY'

甚至在删除原来的引用 my_func 之后,仍然可以使用新的引用 func_upper 访问原来的函数对象:

>> my_func('helllo python')
...
NameError: name 'my_func' is not defined
>> func_upper('helllo python')
'HELLLO PYTHON'

嵌套函数

Python 中,一个函数中可以定义另一个函数:

def talk():
    def whisper(word="yes"):
        return word.upper()+"!"
    print(whisper())

运行结果:

>> talk()
YES!
>> whisper()
...
NameError: name 'whisper' is not defined

函数引用还可以作为另一个函数的返回值:

def talk():
    def inner(word="yes"):
        return word.upper()+"!"
    return inner

运行结果:

>> whisper = talk()
>> whisper()
'YES!'
>> whisper('no')
'NO!'

此外,函数引用也可以作为参数传递:

def do_something_before(func):
    print('Do Something and then call the func.')
    ret = func()
    return ret

运行结果:

>> do_something_before(func_upper)
Do Something and then call the func.
'STRIVE FOR A HUNDRED YEARS AND SET SAIL ON A NEW JOURNEY'

综合上述知识,我们可以写出一个装饰器,再被封装函数之前和之后分别执行一段代码,而不去修改函数本身:

def my_decorator(func):
    def wrapper():
        print('Do something and then call the func.')
        print(func())
        print('Call the fun and then do otherthing.')
    return wrapper

运行结果:

>> func_wrapper = my_decorator(func_upper)
>> func_wrapper()
Do something and then call the func.
STRIVE FOR A HUNDRED YEARS AND SET SAIL ON A NEW JOURNEY
Call the fun and then do otherthing.

上述代码实现了一个最简单的装饰器:将 func_upper 函数传递给装饰器,装饰器将动态地将其包装在任何你想执行的代码中,然后返回一个新的函数 wrapper 。调用新函数 func_wrapper ,可以看到装饰器的效果。

上述代码的问题是:每次调用 func_upper 函数都需要修改为调用 func_wrapper 函数。为此,我们重新赋值 func_upper 引用即可:

>> func_upper = my_decorator(func_upper)
>> func_upper()
Do something and then call the func.
STRIVE FOR A HUNDRED YEARS AND SET SAIL ON A NEW JOURNEY
Call the fun and then do otherthing.

针对以上过程,Python 为我们提供了一个语法糖简以化操作:

@my_decorator
def my_func(content="Strive for a hundred years and set sail on a new journey"):
    return content.upper()

运行结果:

>> my_func()
Do something and then call the func.
STRIVE FOR A HUNDRED YEARS AND SET SAIL ON A NEW JOURNEY
Call the fun and then do otherthing.

最后,装饰器可以叠加使用,但是先后顺序非常重要:

def bold(func):
    """bold 装饰器"""
    def wrapper():
        return "<b>{}</b>".format(func())
    return wrapper

def italic(func):
    """italic 装饰器"""
    def wrapper():
        return "<i>{}</i>".format(func())
    return wrapper

先添加 @italic 装饰器,然后添加 @bold 装饰器:

@bold
@italic
def my_func():
    return "Strive for a hundred years and set sail on a new journey".upper()

运行结果:

>> my_func()
'<b><i>STRIVE FOR A HUNDRED YEARS AND SET SAIL ON A NEW JOURNEY</i></b>'

相应地字符串两边先添加了 <i></i> 标签,随后添加了 <b></b> 标签。
下面是交换顺序之后的结果:

@italic
@bold
def my_func2():
    return 'hello python'.upper()

运行结果:

>> my_func2()
'<i><b>HELLO PYTHON</b></i>'

二. 向装饰器函数传参

当我们调用装饰器返回的函数时,实际上是调用包装函数 wrapper,因此给包装函数传递参数即可将参数传递给装饰器:

def decorator_pass_args(func):
    def wrapper_with_args(arg1, arg2):
        print("args in wrapper: {}, {}".format(arg1, arg2))
        func(arg1, arg2)
    return wrapper_with_args

@decorator_pass_args
def print_full_name(first, last):
    print("My name is {}.{}".format(last, first))

运行结果:

>> print_full_name('Li', 'Mia')
args in wrapper: Li, Mia
My name is Mia.Li

在装饰方法时,必须考虑方法的首个参数,即指向当前对象的引用 self)

def decorator_method(func):
    def wrapper(self, lie):
        lie -= 3
        return func(self, lie)
    return wrapper

class Person:
    def __init__(self, age):
        self.age = age
        
    @decorator_method
    def print_age(self, lie):
        print("age: {}".format(self.age+lie))

运行结果:

>> Alex = Person(18)
>> Alex.print_age(3)
age: 18
>> Alex.print_age(6)
age: 21

也可以使用 *args**kwargs ,定义一个更加通用的装饰器,可以用在任何函数或对象方法上,而不必关心其参数:

def decorator(func):
    def wrapper(*args, **kwargs):
        print("args ", end=':')
        print(args, kwargs)
        func(*args, **kwargs)
    return wrapper

@decorator
def func_no_args():
    print('PYTHON')

运行结果 1:装饰无参数函数

>> func_no_args()
args :() {}
PYTHON

修饰函数,传参方式为位置传参:

@decorator
def func_args(a, b):
    print('a={}, b={}'.format(a, b))

运行结果 2:装饰位置参数函数:

>> func_args(100, 1000)
args :(100, 1000) {}
a=100, b=1000

修饰函数,传参方式为位置传参和关键字传参:

@decorator
def func_kwargs(a, b, c=0, d=0):
    print('a={}, b={}, c={}, d={}'.format(a, b, c, d))

运行结果 3:装饰位置参数和关键字参数函数:

>> func_kwargs(100, 1000, c=1000000)
args :(100, 1000) {'c': 1000000}
a=100, b=1000, c=1000000, d=0

修饰方法:

class Person:
    def __init__(self, age):
        self.age = age
        
    @decorator
    def print_age(self, lie):
        print("age: {}".format(self.age+lie))

运行结果 4:装饰类中的方法:

>> Alex = Person(18)
>> Alex.print_age(3)
args :(<__main__.Person object at 0x000001B915B69160>, 3) {}
age: 21

三. 向装饰器本身传递参数

由于装饰器必须使用函数作为参数,因此不能直接传递参数给装饰器本身,向装饰器本身传递参数时,我们必须另辟蹊径:声明一个用于创建装饰器的函数。

定义装饰器创建函数:

def decorator_maker():
    """此函数用于创建装饰器"""
    print("make decorator!")
    def decorator(func):
        print("decorator the func: {}".format(func.__name__))
        def wrapper(*args, **kwargs):
            print("do something before")
            func(*args, **kwargs)
            print("do something end")
        return wrapper
    print("return the decorator")
    return decorator

使用装饰器创建函数 decorator_maker 返回一个装饰器 my_decorator

使用创建的装饰器,装饰函数 func_no_args

调用被装饰的函数 func_no_args

下面,我们可以跳过中间变量 my_decorate ,直接使用 @decorator_maker 来装饰 func_no_args

下面,定义一个包含参数的装饰器创建函数,并使用此装饰器传递参数:

def decorator_maker(decorator_arg1, decorator_arg2):
    """此函数用于创建装饰器"""
    print("make decorator!")
    def decorator(func):
        print("decorator receive args: {}, {}".format(decorator_arg1, decorator_arg2))
        print("decorator the func: {}".format(func.__name__))
        def wrapper(*args, **kwargs):
            print("wrapper also receive args: {}, {}".format(decorator_arg1, decorator_arg2))
            func(*args, **kwargs)
            print("do something end")
        return wrapper
    print("return the decorator")
    return decorator

运行结果:

修改列表 l1 ,再次调用 func_no_args

>> l1[0] = 100000
>> func_no_args()
wrapper also receive args: [100000, 2, 3], [4, 5, 6]
PYTHON
do something end

装饰器仅在 Python 代码被导入时执行一次,因此之后不能再动态地改变参数。

四. @wraps 装饰器

定义一个函数,打印其 __name____doc__ 属性:

def func_no_args():
    """print python"""
    print('PYTHON')

运行结果:

>> func_no_args.__name__
'func_no_args'
>> func_no_args.__doc__
'print python'

给函数 func_no_args 添加上文中定义的装饰器 decorator_maker

此时,我们发现无法获取原函数 func_no_args__name____doc__ 属性,取而代之的是 wrapper 函数的相关属性。

定义装饰器时使用 @wraps 装饰 wrapper 函数:

from functools import wraps

def decorator_maker(decorator_arg1, decorator_arg2):
    """此函数用于创建装饰器"""
    print("make decorator!")
    def decorator(func):
        print("decorator receive args: {}, {}".format(decorator_arg1, decorator_arg2))
        print("decorator the func: {}".format(func.__name__))
        @wraps(func)
        def wrapper(*args, **kwargs):
            print("wrapper also receive args: {}, {}".format(decorator_arg1, decorator_arg2))
            func(*args, **kwargs)
            print("do something end")
        return wrapper
    print("return the decorator")
    return decorator

运行结果:

五. 装饰为什么那么有用

因为使用装饰器可以在函数的外部扩展一个函数的行为,而不用修改内部的调用。

为了加强巩固,下面分别使用装饰器来打印一个函数的执行时间、记录函数日志、记录一个函数的运行次数:

import time
from functools import wraps

def benchmark(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        t = time.time()
        res = func(*args, **kwargs)
        print("执行时间: %s %.2f" % (func.__name__, time.time() - t))
        return res
    return wrapper

def logging(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        print("运行日志:", func.__name__, args, kwargs)
        return res
    return wrapper

def counter(func):
    count = 0
    @wraps(func)
    def wrapper(*args, **kwargs):
        nonlocal count
        res = func(*args, **kwargs)
        count += 1
        print("执行次数:", func.__name__, count)
        return res
    return wrapper

@counter
@logging
@benchmark
def reversed_str(string):
    time.sleep(3)
    return reversed(string)

运行结果:

举报

相关推荐

0 条评论