11、Python高级程序设计

目录

回忆闭包

理解装饰器最重要的是闭包,之前说过闭包是函数中的函数,比如下面的就是闭包

#!/usr/bin/env python
# encoding: utf-8

name1 = 'zeng1'
name2 = 'zeng2'

def my_func(var_father_name):
    print("my_fucntion")
    def my_func_child(var_child_name):
        print("var_father_name:%s, var_child_name:%s" %(var_father_name, var_child_name))
    return my_func_child

f = my_func(name1)
f(name2)

Python装饰器就是用于拓展原来函数功能的一种函数,这个函数的特殊之处在于它的返回值也是一个函数,使用Python装饰器的好处就是在不用更改原函数的代码前提下给函数增加新的功能

假设要实现这样一个功能,要在不能改动的函数say_hello()的输出前加一些功能


没有装饰器的情况下可能需要这么来做

即把say_hello()当做参数,传入到debug函数中,在执行say_hello()之前加入需要的功能

# !/usr/bin/env python
# encoding: utf-8

def debug(func):
    def wrapper():
        print("在不能改动的函数{0}的输出前加一些功能".format(func.__name__))
        return func()
    return wrapper

def say_hello():
    print("hello")

say_hello = debug(say_hello)

say_hello()

有了装饰器之后,只需要这么来做

在装饰器debug中需要知道装饰的函数对象,所有在需要使用debug装饰器的函数前加上@debug,这样在debug函数内部就能知道say_hello()的存在了

# !/usr/bin/env python
# encoding: utf-8

def debug(func):
    def wrapper():
        print("在不能改动的函数{0}的输出前加一些功能".format(func.__name__))
        return func()
    return wrapper

@debug
def say_hello():
    print("hello")

say_hello()

对应函数单个参数的装饰器

那么,有些情况,我们的say_hello函数是需要固定参数,这时,在内部函数wrapper中就要匹配好say_hello函数的原型了

# !/usr/bin/env python
# encoding: utf-8

def debug(func):
    def wrapper(name, age):
        print("在不能改动的函数{0}的输出前加一些功能,name:{1}, age:{2}".format(func.__name__, name, age))
        return func(name, age)
    return wrapper

@debug
def say_hello(name, age):
    print("hello")

say_hello(name="zengraoli", age=24)

对应函数多个参数的装饰器

多个参数也好办啊,不是有可变参数么,在提一下可变参数,带一个的是获取元祖参数,带*的获取字典参数

# !/usr/bin/env python
# encoding: utf-8

def debug(name, *args, **kwargs):
    print(name)
    print(args)
    print(kwargs)

debug("zengraoli", 1, 2, "args", kwargs_name="zengraoli", kwargs_age=24)

传入参数的时候,按照第一个参数name对应第一个参数;剩余的不用字典对象的参数,则被args捕获;以字典方式传入的参数,则被kwargs捕获

这样再来修改装饰器,让他可以捕获多个不定参数

# !/usr/bin/env python
# encoding: utf-8

def debug(func):
    def wrapper(name, *args, **kwargs):
        print("在不能改动的函数{0}的输出前加一些功能".format(func.__name__))
        return func(name, *args, **kwargs)
    return wrapper

@debug
def say_hello(name, *args, **kwargs):
    print(name)
    print(args)
    print(kwargs)

say_hello("zengraoli", 1, 2, "args", kwargs_name="zengraoli", kwargs_age=24)

带参数的装饰器

在装饰器中带上参数,有时需要给内部的装饰器传入一个参数,此时需要在原来的装饰器上再加上一层装饰器,其他层不变,就能实现这种小小的功能

# !/usr/bin/env python
# encoding: utf-8

def debug(outer_para):
    def inter_debug(func):
        def wrapper(name, *args, **kwargs):
            print("在不能改动的函数{0}的输出前加一些功能,outer_para的值:{1}".format(func.__name__, outer_para))
            #在不能改动的函数say_hello的输出前加一些功能,outer_para的值:outer_para---zengraoli
            return func(name, *args, **kwargs)
        return wrapper
    return inter_debug

@debug("outer_para---zengraoli")
def say_hello(name, *args, **kwargs):
    print(name)#zengraoli
    print(args)#(1, 2, 'args')
    print(kwargs)#{'kwargs_name': 'zengraoli', 'kwargs_age': 24}


say_hello("zengraoli", 1, 2, "args", kwargs_name="zengraoli", kwargs_age=24)
# print(help(say_hello))

基于类实现的装饰器

首先需要了解call()
如果在想要直接把实例化对象当成函数对象来调用那么需要重载call函数,比如

# !/usr/bin/env python
# encoding: utf-8

class Person1(object):
    def __init__(self):
        self._name = "zeng"
    def __call__(self):
        print("my name is {0}".format(self._name))

class Person2(object):
    def __init__(self):
        self._name = "zeng"
    def __call__(self, friend_name):
        print("my name is {0}, my friend is {1}".format(self._name, friend_name))

p1 = Person1()
p1()#my name is zeng---无参数的函数对象

p2 = Person2()
p2("zengraoli")#my name is zeng, my friend is zengraoli---一个参数的函数对象

如果类已经重载了call(callable)对象,那么把这个类变成装饰器就相对简单了

# !/usr/bin/env python
# encoding: utf-8


class Person(object):
    def __init__(self, func):
        self.func = func
    def __call__(self, name, *arg):
        print(self.func.__name__)
        return self.func(name, *arg)#需要对应函数原型

@Person
def say_hello(name, *arg):
    print(name)
    for item in arg:
        print(item)

say_hello("zeng", "arg1", "arg2")

再来看看带参数的类装饰器,既然函数装饰器也可以带参数,那基于类的装饰器也可以带参数

# !/usr/bin/env python
# encoding: utf-8


class Person(object):
    def __init__(self, class_arg):#class_arg为给装饰器传入的参数
        self.class_arg = class_arg

    def __call__(self, func):
        def wrapper(name, *arg): #需要对应韩元的原型
            print("func name:{0}, class_arg:{1}".format(func.__name__, self.class_arg))
            func(name, *arg)
        return wrapper

@Person("class_arg")
def say_hello(name, *arg):
    print(name)
    for item in arg:
        print(item)

say_hello("zeng", "arg1", "arg2")

几个比较重要的内置装饰器

在使用property经常是这么使用

class Person(object):
    def __init__(self, name=""):#class_arg为给装饰器传入的参数
        self._name = name

    def get_name(self):
        return self._name

    def set_name(self, name):
        self._name = name

    def del_name(self):
        del self._name

    property_name = property(fget=get_name, fset=set_name, fdel=del_name)

person = Person()
person.property_name = "zengraoli"
print(person.property_name)

常规做法会有点太长,使用了语法糖之后,可以做的更简单,属性器@property对三个方法setter、getter、deleter做了封装,可以按照下面的示例代码来进行使用

# !/usr/bin/env python
# encoding: utf-8

class Person(object):

    def __init__(self, name="", age=0):#class_arg为给装饰器传入的参数
        self._name = name
        self._age = age

    @property
    def property_name(self):
        return self._name

    @property_name.setter
    def property_name(self, value):
        self._name = value

    @property
    def property_age(self):
        return self._age

person = Person()
person.property_name = "zengraoli" #property_name有get和set,是一个可读写的属性
print(person.property_name)#zengraoli

# person.property_age = 24 #AttributeError: can't set attribute
print(person.property_age)#0

@staticmethod声明一个方法为静态方法

# !/usr/bin/env python
# encoding: utf-8

class Person(object):
    @staticmethod
    def static_say_hello():
        print("say hello.")

Person.static_say_hello()

@classmethod

此时的函数不需要带一个self参数,但是需要用示例去调用,此方法会返回一个classmethod对象,默认会调用类的init方法

# !/usr/bin/env python
# encoding: utf-8

class Person(object):

    def __init__(self, name="zengraoli"):
        self._name = name

    def say_hello(self):
        print("{0} say hello.".format(self._name))

    @classmethod
    def classmethod_say_hello(cls):
        cls().say_hello()

Person.classmethod_say_hello()#zengraoli say hello.

更高级使用

使用functiontools模块,解决函数的帮助文档问题
当时候了装饰器后,会导致原来函数的doc来打印函数的文档时候,会发现打开的其实是装饰器的内部函数,比如下面的

# !/usr/bin/env python
# encoding: utf-8

def debug(func):
    def wrapper(name):
        """wrapper doc"""
        print("fun name {0}".format(func.__name__))
        func(name)
    return wrapper

@debug
def say_hello(name):
    """say_hello doc"""
    print("{0} say hello".format(name))

# say_hello("zengraoli")
print(say_hello.__name__)#wrapper
print(say_hello.__doc__)#wrapper doc

我们想要恢复say_hello的原型,而不是输出wrapper,则需要使用functiontools模块的wrapper装饰器了

# !/usr/bin/env python
# encoding: utf-8

from functools import wraps

def debug(func):
    @wraps(func)
    def wrapper(name):
        """wrapper doc"""
        print("fun name {0}".format(func.__name__))
        func(name)
    return wrapper

@debug
def say_hello(name):
    """say hello doc"""
    print("{0} say hello".format(name))

# say_hello("zengraoli")
print(say_hello.__name__)#say_hello
print(say_hello.__doc__)#say hello doc

使用functiontools模块decorator来优化装饰器

如果你觉得嵌套函数比较晕的话,可以试试decorator
首先需要先定义好wrapper和一个封装器debug,接着就是来用来

# !/usr/bin/env python
# encoding: utf-8

from decorator import decorate

def wrapper(func):
    """print log before a function."""
    print("[DEBUG]: enter {0}".format(func.__name__))
    return func()

def debug(func):
    return decorate(func, wrapper)  # 用wrapper装饰fun

@debug
def say_hello():
    print("say hello.")

say_hello()#[DEBUG]: enter say_hello say hello.

还可以更简单的

使用decorator,让你脸上面的debug函数都不用写出来

# !/usr/bin/env python
# encoding: utf-8

from decorator import decorator

@decorator
def wrapper(func):
    """print log before a function."""
    print("[DEBUG]: enter {0}".format(func.__name__))
    return func()

@wrapper
def say_hello():
    print("say hello.")

say_hello()#[DEBUG]: enter say_hello say hello.

使用wrapt来灵活的调整你的装饰器

参考文档
http://wrapt.readthedocs.io/en/latest/quick-start.html


装饰器的另类用法

参考,比较少人用得着吧
http://www.cnblogs.com/cicaday/p/python-decorator-more-usages.html


猜你喜欢

转载自blog.csdn.net/zengraoli/article/details/81320128