Python手册(7) 函数&装饰器

0. 前言

  • 参考资料:
    • 《流畅的Python》第五、六、七章
    • 《Python Cookbook》第七、九章
    • 《深入理解Python特性》第三章
  • TODO(目前不太关心的):
    • 标准库里有很多内置的装饰器。
    • 闭包没有具体展开。

1. 函数

1.1. 基本概念

  • (编程语言中的)一等对象:运行时创建、能赋值给变量或数据结构中的元素、能作为参数传给函数、能作为函数返回结果。
  • 函数是一等对象。
    • 高阶函数:接受函数作为参数,或把函数作为结果返回的函数。
    • 函数对象及其名称是相互独立的实体,换句话说,指向函数的变量和函数本身实际上是彼此独立的。
  • 闭包
    • 即词法闭包,在程序流不在闭包范围内的情况下,也能记住封闭作用域中的值。
    • 内部函数不仅可以从父函数返回,还可以捕获并携带父函数的某些状态。
  • 普通对象也可以作为函数使用,即有些对象不是函数,也可以调用。
    • 实现方式:实现__call__ 方法。
    • 其他:Python内置函数 callable 用于检查一个对象是否可以调用。
    • 可调用函数包括:用于定义的函数、内置函数、内置方法、方法、类、类的实例、生成器函数。

1.2. lambda 表达式

  • 作用:快速声明小型匿名函数,根据功能也有人称之为单表达式函数
    • 匿名函数:猜测就是没有函数名的函数。
    • 与匿名函数相对的是具名函数
    • lambda只是语法糖。
  • 实例:(lambda x, y: x+y)(5, 3)
  • 使用场景:
    • 可迭代对象进行排序时,使用lambda表达式定义简短的key函数,如 sorted(ruples, key=lambda x: x[1])
    • 其他好像推荐用得比较少。
  • 注意事项:
    • 不应过度使用 lambda。
    • 使用前总是先问问自己:使用普通具名函数或列表解析式是否更加清晰。
  • 在匿名函数中绑定变量的值:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 如果直接使用,则不会记住变量的值,而是跟变量一起变化
    x = 10
    a = labmda y: x + y
    x = 20
    b = lambda y: x + y
    a(10) # 30
    b(10) # 30

    # 可以设置默认参数来实现
    x = 10
    a = labmda y, x=x: x + y
    x = 20
    b = lambda y, x=x: x + y
    a(10) # 20
    b(10) # 30

1.3. * 和 **

  • 作为函数参数时,经常使用*args**kwargs这两种形式,功能是让函数接受可选参数,使得代码更灵活。
    • args和kwargs只是约定俗成的名称,可以修改。
    • *args获取的对象是元组,**kwargs获取的对象是字典。
  • 可用于解包
    • *可用于解包序列。
    • **可用于解包字典。
    • 高效参数解包有助于模块和函数编写更灵活。
  • *打头的参数只能作为最后一个位置参数,但可以存在其他参数;以**打头的参数只能作为最后一个参数出现。
    • def f(x, *args, y) 是允许的。
  • 单独的星号可以作为分隔符,用于分隔位置参数与命名关键字参数。
    • 星号本身不是参数。

1.4. 变量作用域

  • 从作用域角度看,变量分为局部变量和全局变量。
  • 函数内部出现的变量默认为局部变量。
    • 如果同名局部变量不存在,且存在同名全局变量,也不会直接调用全局变量。
    • 如果想在局部作用域中使用全局变量,必须在局部作用域中先使用global关键字声明全局变量
      1
      2
      3
      4
      5
      6
      7
      8
      9
      a = 1
      b = 2

      def f():
      c = 3
      global b
      print(a) # 报错,无法直接获取全局变量
      print(b) # 正常,因为已经在局部作用域中声明了全局变量
      print(c) # 正常,直接调用局部变量

2. 装饰器

2.1. 基本概念

  • 作用:
    • 用来临时扩展和修改可调用对象(函数、方法、类)的行为,同时又不会永久修改可调用对象本身。
    • 将通用功能应用到现有的类或函数的行为上。
  • 应用举例:日志、访问控制与授权、计算执行时间、限制请求速率、缓存。
  • 本质:
    • 定义内部函数,扩充原函数的功能,并替代原函数。
    • 装饰器就是语法糖,简化上述过程。
  • 多装饰器修饰应用顺序从下到上,可称为装饰器栈

2.2. 基本形式举例

2.2.1 形式一:最简单的装饰器,不实现任何功能。

  • 该装饰器不能实现任何功能,只是用于帮助理解装饰器本质。
  • 从该实例可以看出,装饰器其实就是语法糖。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    def null_decorator(func):
    return func

    @null_decorator
    def greet():
    return 'Hello!'

    # 上面这种装饰器语法等价于下面的方法
    def greet():
    return 'Hello!'
    greet = null_decorator(greet)

2.2.2. 形式二:装饰器的基本形式。

  • 实际起作用的装饰器形式如下。
  • 从该实例中可以参数,基本装饰器就是定义内部函数,通过内部函数来扩充源函数的功能。
  • 通常,装饰器会把函数替换成另一个函数。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def uppercase(func):
    def wrapper():
    original_result = func()
    modified_result = original_result.upper()
    return modified_result
    return wrapper

    @uppercase
    def greet():
    return 'Hello!'

2.2.3. 形式三:参数化装饰器。

  • 直观的看,所谓参数化装饰器,其实就是定义了内部函数的内部函数。
  • 另外一种理解参数化装饰器,其实就是构建一个装饰器工厂函数,把参数传给它,返回一个装饰器。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    registry = set()

    def register(active=True):
    def decorate(func):
    print('running register(active=%s)->decorate(%s)' % (active, func))
    if active:
    registry.add(func)
    else:
    registry.discard(func)
    return func
    return decorate

    @register(active=False)
    def f1():
    print('running f1()')

    @register() # 注意,这里不能少了括号,否则报错
    def f2():
    print('running f2()')

    def f3():
    print('running f3()')

2.2.4. 形式四:多种形式装饰器。

  • 编写一个单独的装饰器,既可以使用@decorator,也可以使用@decorator(x, y, z)
    1
    2
    3
    4
    def logged(func=None, *, level=logging.DEBUG, name=None, message=None):
    if func is None:
    return partial(logged, level=level, name=name, message=message)
    ...

2.2.5. 形式五:类中定义装饰器。

  • 其实 @property 也是使用类实现的。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    class A:
    def decorator1(self, func):
    @wraps(func)
    def wrapper(*args, **kwargs):
    print('Decorator 1')
    return func(*args, **kwargs)
    return wrapper

    @classmethod
    def decorator2(cls, func):
    @wraps(func)
    def wrapper(*args, **kwargs):
    print('Decorator 2')
    return func(*args, **kwargs)
    return wrapper

    a = A()

    @a.decorator1
    def greet():
    return 'Hello!'

    @A.decorator2
    def spam():
    pass

2.3. 装饰器执行时间

  • 装饰器的一个关键特性,是在被装饰的函数定义之后立即执行,通常是在导入时。
  • 换个说法:函数装饰器在导入模块时立即执行,而被装饰的函数只在明确调用时运行。
  • 这突出了Python程序员所说的导入时运行时之间的区别。
  • 具体查看以下实例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    def register(func):
    print('running register(%s)' % func)
    return func

    @register
    def f1():
    print('running f1()')

    @register
    def f2():
    print('running f2()')

    def main():
    print('running main()')
    f1()
    f2()

    # 输出结果
    # running register(<function f1 at xxx>)
    # running register(<function f2 at xxx>)
    # running main()
    # running f1()
    # running f2()

2.5. 无法调试(无法获取元数据)

  • 如果使用最朴素的方法定义装饰器,可能会导致函数无法调试,即函数的元数据(如文档、原函数名称、参数列表)被隐藏。
    • 解决方案:使用functools.wraps装饰器,如下例子。
      1
      2
      3
      4
      5
      def uppercase(func):
      @functools.wraps(func):
      def wrapper():
      return func().upper()
      return wrapper