python装饰器及functools模块

python装饰器及functools模块

本文是笔者学习python装饰器以及functools模块的笔记。
在开始学习之前,先在此列举出python函数的一些特性:

  1. 一切皆对象:python函数也是对象,也就是说可以将函数赋值给变量
    def hi(name='Michael'):
    	return "Hi %s!" % name
    greet = hi # 将函数赋值给变量,函数名后不能加小括号
    print(greet())
    # output:Hi Michael!
    
  2. 在函数中定义函数:python可以让我们在一个函数中定义另一个函数
    def hi(name="Michael"):
    	print("Now, you are inside the hi() funciton!")
    
        # 在函数中定义两个函数
        def greet():
            return "Now, you are in the greet() function!"
    
        def welcome():
            return "Now, you are in the welcome() function!"
    
        print(greet())
        print(welcome())
        print("Now, you are back in the hi() function!")
    
    hi()
    # output: Now, you are inside the hi() funciton!
    #         Now, you are in the greet() function!
    #         Now, you are in the welcome() function!
    #         Now, you are back in the hi() function!
    
  3. 从函数中返回函数:python在一个函数中定义另一个函数后,可以将嵌套的函数作为返回值返回出来
    def hi(name="Michael"):
    
        # 在函数中定义两个函数
        def greet():
            return "Now, you are in the greet() function!"
    
        def welcome():
            return "Now, you are in the welcome() function!"
    
        # 返回函数,函数名后不能加小括号
        if name == "Michael":
            return(greet)
        else:
            return(welcome)
    
    a = hi()
    print(a)
    print(a())
    # output: <function hi.<locals>.greet at 0x7f393513bea0>
    #        Now, you are in the greet() function!
    
  4. 将函数作为另一个函数的参数:python可以将一个函数作为参数传给另一个函数
    def hi():
        return "Hi Michael!"
    
    # 将函数hi()传递给函数do_sth_before_hi()
    def do_sth_before_hi(func):
    	print("I am doing some boring work before executing hi()")
        print(func())
    
    do_sth_before_hi(hi)
    # output: I am doing some boring work before executing hi()
    #         Hi Michael!
    

python装饰器

装饰器(decorator)是一个返回函数的高阶函数,用来给增强普通函数的功能。

下面是一个使用普通的装饰器的蓝本:

import functools

def decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('call %s():' % func.__name__)
        print('args = {}'.format(*args))
        return func(*args, **kwargs)

    return wrapper

@decorator
def test(name):
	print('%s param: %s' % (test.__name__, name))

test("Michael")

输出为:

call test():
args = Michael
test param: Michael

下面取关键行解释:
第3~10行
定义一个装饰器,输入的参数为一个函数,返回值是增强了功能后的函数,本质上其实就是一个返回值为函数的高阶函数。
第4行
functools.wraps()是python提供的装饰器,它能把被装饰函数test()的元信息(name, doc, module, dict等内部参数说明信息)拷贝到装饰器里面的封装函数wrapper()中去。函数的元信息包括__docstring____name__,参数列表等等。如果没有第4行,在程序最后加一行print(test.__name__),输出是wrapper;如果有第4行,在程序最后加一行print(test.__name__),输出是test
第12行
使用@语法将原函数传入装饰器函数,相当于:

test = decorator(test)

带参数的装饰器

装饰器允许传入参数,一个携带了参数的装饰器将有三层函数,如下所示:

import functools

def decorator_with_param(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print('call %s():' % func.__name__)
            print('args = {}'.format(*args))
            print('decorator_param = {}'.format(text))
            return func(*args, **kwargs)

        return wrapper
    return decorator

@decorator_with_param("Jane")
def test(name):
    print('%s param: %s' % (test.__name__, name))

test("Michael")

输出为:

call test():
args = Michael
decorator_param = Jane
test param: Michael

下面取关键行进行解释:
第3~13行
定义一个带参数的装饰器
第15行
相当于执行:

test = decorator_with_param("Jane")(test)

首先执行decorator_with_param("Jane"),返回的是decorator函数,然后再调用返回的函数,即执行decorator(test),返回值是wrapper函数。

装饰器类

上面我们都是使用函数来定义一个装饰器,我们也可以用类来构建装饰器,这样我们可以使用类的继承得到很多我们需要的特化的装饰器。下面我们实现一个和上面带参数的装饰器相同功能的装饰器类,代码如下所示:

import functools

class decorator_with_param():
    def __init__(self, text):
        self.text = text

    def __call__(self, func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print('call %s():' % func.__name__)
            print('args = {}'.format(*args))
            print('decorator_param = {}'.format(self.text))
            self.notify()
            return func(*args, **kwargs)

        return wrapper

    def notify(self):
        pass

@decorator_with_param("Jane")
def test(name):
    print('%s param: %s' % (test.__name__, name))

test("Michael")

输出为:

call test():
args = Michael
decorator_param = Jane
test param: Michael

第18~19行
从新增加的notify()函数可以看出装饰器类使用起来比嵌套函数更加整洁,代码更易懂。

functools模块

functools模块主要包含了一些方便的功能函数和函数装饰器。

functools.cmp_to_key(func)

要了解这个函数,首先需要了解python2的cmp()函数用法、python2和python3的list.sort()的区别、python2和python3的sorted()的区别。

  1. python2的cmp()函数:比较函数(comparison function)
    函数语法:

    cmp(x, y)
    

    作用:cmp(x, y)函数用于比较两个对象,如果 x < y x < y x<y返回 − 1 -1 1,如果 x = = y x == y x==y返回 0 0 0,如果 x > y x > y x>y返回 1 1 1
    注意:python3已经没有这个内置函数了。

  2. python2和python3的list.sort()sorted()

    sort()sorted()区别:
    sort()是应用在list上的方法,sorted()可以对所有可迭代的对象进行排序操作。
    listsort()方法返回的是对已经存在的列表进行操作,而内建函数sorted()方法返回的是一个新的list,而不是在原来的基础上进行的操作。

    python2的list.sort()sorted()函数语法:

    list.sort(cmp=None, key=None, reverse=False)
    sorted(iterable, cmp=None, key=None, reverse=False)
    

    python3的list.sort()函数语法:

    list.sort(key=None, reverse=False)
    sorted(iterable, key=None, reverse=False)
    

    可以发现python3的list.sort()sorted()函数比python2的少了一个cmp参数(又叫比较参数)。

    • cmp参数的用法
      cmp参数表示使用指定的方法进行比较排序。详细用法见下例(只能在python2运行):

      #  -*-coding:utf8 -*-
      def numric_cmp(x, y):
          '''
          自己定义的比较函数,和python2的cmp()函数功能相似
          x > y: 返回正数
          x < y: 返回负数
          x == y: 返回0
          '''
          return x - y
      
      def cmp_reverse(x, y):
          '''
          自己定义的比较函数
          x > y: 返回负数
          x < y: 返回正数
          x == y: 返回0
          '''
          return y - x
      
      a = [3, 6, 2, 8, 1, -9]
      # sorted用法
      print sorted(a, cmp=numric_cmp)
      print sorted(a, cmp=cmp_reverse)
      print sorted(a)
      print a
      print "######我是分隔符######"
      a.sort(cmp=numric_cmp)
      print(a)
      a.sort(cmp=cmp_reverse)
      print(a)
      a.sort()
      print(a)
      

      输出为:

      [-9, 1, 2, 3, 6, 8]
      [8, 6, 3, 2, 1, -9]
      [-9, 1, 2, 3, 6, 8]
      [3, 6, 2, 8, 1, -9]
      ######我是分隔符######
      [-9, 1, 2, 3, 6, 8]
      [8, 6, 3, 2, 1, -9]
      [-9, 1, 2, 3, 6, 8]
      
    • key参数的用法
      key参数(又称关键字参数)对应的值必须是一个这样的关键字函数(key function):接收一个参数,返回一个用来排序的键。最简单的:

      >>> sorted("This is a test string from Andrew".split(), key=str.lower)
      ['a', 'Andrew', 'from', 'is', 'string', 'test', 'This']
      

      再例如按年龄给学生排序:

      >>> student_tuples = [
      ...     ("john", 'A', 15),
      ...     ("jane", 'B', 12),
      ...     ("dave", 'B', 10),
      ... ]
      >>> sorted(student_tuples, key=lambda student: student[2])
      [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
      
    • reverse参数的用法
      reverse参数表示排序规则:reverse = True 降序;reverse = False 升序(默认)。例如上一小节给字符串排序的例子如果置reverse = True

      >>> sorted("This is a test string from Andrew".split(), key=str.lower, reverse=True)
      ['This', 'test', 'string', 'is', 'from', 'Andrew', 'a']
      

现在我们对python2和python3的比较函数和排序函数有了一定了解。
那么有一个问题,python3的排序函数没有cmp参数,如果我想自己定义一个比较函数用于python3的排序,又没有cmp参数,该如何实现?
或者在将python2的代码迁移到python3时,python3排序函数没有了cmp参数,如何处理python2中指定了cmp参数的排序函数?
这就需要使用带装饰器的关键字函数(可以赋值给key参数)来代替比较函数(赋值给cmp参数)了,如下例:

def cmp_to_key(mycmp):
    'Convert a cmp= function into a key= function'
    class K(object):
        def __init__(self, obj, *args):
            self.obj = obj
        def __lt__(self, other):
            return mycmp(self.obj, other.obj) < 0
        def __gt__(self, other):
            return mycmp(self.obj, other.obj) > 0
        def __eq__(self, other):
            return mycmp(self.obj, other.obj) == 0
        def __le__(self, other):
            return mycmp(self.obj, other.obj) <= 0
        def __ge__(self, other):
            return mycmp(self.obj, other.obj) >= 0
        def __ne__(self, other):
            return mycmp(self.obj, other.obj) != 0
    return K

def reverse_cmp(x, y):
    return y - x

a = [3, 6, 2, 8, 1, -9]
print(sorted(a, key=cmp_to_key(reverse_cmp)))
print(sorted(a, key=cmp_to_key(reverse_cmp), reverse=True))

输出为:

[8, 6, 3, 2, 1, -9]
[-9, 1, 2, 3, 6, 8]

当然我们没必要自己去定义这个装饰器,因为functools模块已经有了cmp_to_key()函数,下面我们将 讲解cmp参数时用到的只能在python2上运行的例子 改写为能在python3上运行的代码:

import functools

def numric_cmp(x, y):
    '''
    和python2的cmp()函数功能相似
    x > y: 返回正数
    x < y: 返回负数
    x == y: 返回0
    '''
    return x - y

def cmp_reverse(x, y):
    '''
    x > y: 返回负数
    x < y: 返回正数
    x == y: 返回0
    '''
    return y - x

a = [3, 6, 2, 8, 1, -9]
# sorted用法
print(sorted(a, key=functools.cmp_to_key(numric_cmp)))
print(sorted(a, key=functools.cmp_to_key(cmp_reverse)))
print(sorted(a))
print(a)
print("######我是分隔符######")
a.sort(key=functools.cmp_to_key(numric_cmp))
print(a)
a.sort(key=functools.cmp_to_key(cmp_reverse))
print(a)
a.sort()
print(a)

主要的改动是之前使用cmp参数的地方都改为了key参数,并使用functools.cmp_to_key()将之前赋值给cmp参数的比较函数转换为了可以赋值给key参数的关键字函数。

functools.partial(func[,*args][, **keywords])

partial()是一个函数装饰器,用于为func()函数的部分参数指定参数值,从而得到一个转换后的函数,程序以后调用转换后的函数时,就可以少传入那些己指定值的参数,只传入剩下未指定值的参数即可。
例子如下:

import functools

def func(a, b):
    print('a:', a)
    print('b:', b)

# 固定住a,只输入b
a_func = functools.partial(func, 3)
a_func(1)

# 固定住b, 只输入a
b_func = functools.partial(func, b=2)
b_func(5)

print(type(a_func))
print(type(b_func))

a_func.func(22, 22) # func属性,返回的是被修饰的函数

输出为:

a: 3
b: 1
a: 5
b: 2
<class 'functools.partial'>
<class 'functools.partial'>
a: 22
b: 22

可以看出返回的其实是一个partial类型的变量,实际上相当于在partial中保存了需要调用的参数以及需要固定的参数,在需要调用时将预先设置的参数传入参数列表。

注意:
functools.partial有一个属性func,返回的是被修饰的函数,如上例中的最后一行。

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

和上文讲的@functools.wraps(func)功能相同但用法不同。下面是普通装饰器、使用了update_wrapper()的装饰器和使用了@functools.wraps()的装饰器的对比:

# 普通的装饰器
def decorator_1(func_1):
    def wrapper_1(*args, **kwargs):
        """wrap func_1"""
        print("before call func_1 ... ...")
        return func_1(*args, **kwargs)
    return wrapper_1

@decorator_1
def hello_1():
    """say hello_1"""
    print("Hello, first world!")

# 带functools.update_wrapper的装饰器
from functools import update_wrapper

def decorator_2(func_2):
    def wrapper_2(*args, **kwargs):
        """wrap func_2"""
        print("before call func_2 ... ...")
        return func_2(*args, **kwargs)
    return update_wrapper(wrapper_2, func_2) #  这里用到了update_wrapper()

@decorator_2
def hello_2():
    """say hello_2"""
    print("Hello, Second world!")

# 带functools.wraps()的装饰器
from functools import wraps

def decorator_3(func_3):
    @wraps(func_3) #  这里用到了wraps()
    def wrapper_3(*args, **kwargs):
        """wrap func_3"""
        print("before call func_3 ... ...")
        return func_3(*args, **kwargs)
    return wrapper_3

@decorator_3
def hello_3():
    """say hello_3"""
    print("Hello, third world!")

if __name__ == '__main__':

    hello_1()
    print("hello_1.__name__: %s" % hello_1.__name__)
    print("hello_1.__doc__: %s" % hello_1.__doc__)
    print("######我是分隔符######")
    hello_2()
    print("hello_2.__name__: %s" % hello_2.__name__)
    print("hello_2.__doc__: %s" % hello_2.__doc__)
    print("######我是分隔符######")
    hello_3()
    print("hello_3.__name__: %s" % hello_3.__name__)
    print("hello_3.__doc__: %s" % hello_3.__doc__)

输出为:

before call func_1 ... ...
Hello, first world!
hello_1.__name__: wrapper_1
hello_1.__doc__: wrap func_1
######我是分隔符######
before call func_2 ... ...
Hello, Second world!
hello_2.__name__: hello_2
hello_2.__doc__: say hello_2
######我是分隔符######
before call func_3 ... ...
Hello, third world!
hello_3.__name__: hello_3
hello_3.__doc__: say hello_3

functools其他部分

functools模块还有很多其他的功能,暂时用不到,待以后补充:

@functools.lru_cache(maxsize=128, typed=False)
@functools.total_ordering
functools.partialmethod(func, *args, **keywords)
functools.reduce(function, iterable[, initializer])
@functools.singledispatch

猜你喜欢

转载自blog.csdn.net/qyhaill/article/details/103006496