闭包以及装饰器

闭包

何为闭包

如果在一个 内部函数里,对 在外部作用域(但不是在全局作用域)变量 进行引用,那么 内部函数 就被认为是 闭包(closure)

def addx(x):
    def adder(y):
        return x + y
    return adder
  • 在一个内部函数里: adder(y)就是这个内部函数
  • 对在 外部作用域(但不是全局作用域)变量 进行引用: x就是这个被引用的变量, x外部作用域addx里面,但不在 全局作用域里
  • 那么这个内部函数adder就是闭包

闭包中是不能修改外部作用域的局部变量的

  • global 适用于函数内部修改全局变量的值
  • nonlocal 适用于嵌套函数中内部函数修改外部变量的值
  • 如果没有使用以上关键字,对全局变量或者外部变量进行修改,python会默认将 全局变量 或者外部变量隐藏起来

装饰器

假如现在在一个公司,有A B C三个业务部门,还有S一个基础服务部门,目前呢,S部门提供了两个函数,供其他部门调用,函数如下:

def f1():
    print('调用f1')

def f2():
    print("调用f2")

在初期,其他部门这样调用是没有问题的,随着公司业务的发展,现在 S部门 需要对 函数调用 例如权限进行 验证 ,如果 有权限 的话,才能进行 调用 ,否则调用失败。考虑一下,如果是我们,该怎么做呢?

方案集合

  • 方案一:让调用方,也就是 ABC 部门在调用时候, 主动 进行 权限验证
  • 方案二S部门在对外提供的函数中,首先进行 权限验证,然后才能进行真正的函数操作

问题

方案一

  1. 将本不该 暴露外层 的权限认证暴露在 使用方 面前
  2. 如果有多个部门,要每个部门的每个人都要周知,另外还要确保所有人要这么做

方案二

  1. 看似可行,但S部门对外提供的所有方法都需要修改,每个函数都要调用权限验证,同样也费尽
  2. 另外代码本着 开放封闭 原则,允许在原来代码上面扩展,不允许修改已实现代码。因为一旦修改,可能其他函数调用该函数的时候就会出现问题。

这就需要应用到装饰器原理

装饰器原理

def w1(func):
    def inner():
        print("...验证权限..")
        func()
    return inner

@w1
def f1():
    print("f1被调用")

@w1
def f2():
    print("f2被调用")

python解释器就会从上到下解释代码,步骤如下:

  1. def w1(func) ==> 将w1函数加载到内存
  2. @w1

执行到@w1内部会执行如下操作:

执行w1函数,并将 @w1下面的函数作为w1函数的参数,即:@w1等价于w1(f1),所以内部就会去执行:

def w1(f1):
    def inner():
        print("验证权限")
        f1()
    return inner  # 返回inner的地址

w1的返回值

将执行完的w1函数 返回值(inner内存地址)赋值给@w1下面的函数也就是函数名f1,由于Python变量名只是标签,返回的函数inner又有了一个新的名字,也就是f1.

因此以后的业务部门只需要调用新f1即可.

装饰器应用

  1. 引入日志
  2. 函数执行时间统计
  3. 执行函数前预备处理
  4. 执行函数后清理功能
  5. 权限校验等场景
  6. 缓存

装饰器示例

被装饰函数无参数

from time import ctime, sleep

def timefun(func):
    def wrappedfunc():
        print("%s called at %s"%(func.__name__, ctime()))
        func()
    return wrappedfunc

@timefun
def foo():
    print("I am foo")
foo()
sleep(2)
foo()
<--------->
foo called at Tue Jun 19 00:03:49 2018
I am foo
foo called at Tue Jun 19 00:03:51 2018
I am foo

被装饰函数有参数

from time import ctime, sleep

def timefun(func):
    def wrappedfunc(a, b):  # 直接传入参数即可
        print("%s called at %s"%(func.__name__, ctime()))
        func(a, b)  # 用形参来调用原来函数,传入原来参数
    return wrappedfunc

@timefun
def foo(a, b):
    print(a+b)
foo(3,5)
sleep(2)
foo(2,4)
<--------->
foo called at Tue Jun 19 00:06:34 2018
8
foo called at Tue Jun 19 00:06:36 2018
6

被装饰函数有不定长参数

from time import ctime, sleep

def timefun(func):
    def wrappedfunc(*args, **kwargs):  # 与被装饰函数有参数相类似
        print("%s called at %s"%(func.__name__, ctime()))
        func(*args, **kwargs)  # 利用形参来调用原来函数
    return wrappedfunc

@timefun
def foo(a, b, c, d):
    print(a+b+c+d)
foo(3,5,7,8)
sleep(2)
foo(2,4,9,10)
<--------->
foo called at Tue Jun 19 00:08:15 2018
23
foo called at Tue Jun 19 00:08:17 2018
25

装饰器中的return

  • 一般情况下,为了让装饰器更通用,可以有return
from time import ctime, sleep

def timefun(func):
    def wrappedfunc():
        print("%s called at %s"%(func.__name__, ctime()))
        func()
    return wrappedfunc

@timefun
def foo():
    print("I am foo")

@timefun
def getInfo():
    return '----hahah---'

现在调用的时候会出现问题:

foo()
sleep(2)
foo()
<--------->
foo called at Tue Jun 19 15:51:17 2018
I am foo
foo called at Tue Jun 19 15:51:19 2018
I am foo

然而调用getInfo()的时候就出现了问题,按道理,应该返回出---hahah---

# 用一个变量接受函数返回值
ret = getInfo()
# 打印出返回值
print(ret)
<--------->
getInfo called at Tue Jun 19 15:53:21 2018
None

返回值的结果为None,然而讲道理应该有返回结果的。原来,我们来看wrappendfunc()函数内部,没有返回值。如果理解刚刚装饰器的原理的话就知道,其实我们调用的新的f1其实是将原来的f1作为实参传入形参func后返回的wrappedfunc函数的地址,因此并没有返回值.

def wrappedfunc():
        print("%s called at %s"%(func.__name__, ctime()))
        func()

func()修改为return func(),则有返回值

from time import ctime, sleep

def timefun(func):
    def wrappedfunc():
        print("%s called at %s"%(func.__name__, ctime()))
        return func()
    return wrappedfunc

@timefun
def foo():
    print("I am foo")

@timefun
def getInfo():
    return '----hahah---'
ret = getInfo()
print(ret)
<--------->
ecuted in 6ms, finished 16:01:18 2018-06-19
getInfo called at Tue Jun 19 16:01:18 2018
----hahah---

一般情况下,为了让装饰器更通用,可以有return

提醒:一定要在明白装饰器原理的情况下去理解上面的案例,否则光靠硬背是无法熟练使用python里面难度比较大的装饰器的

装饰器带有参数,并且在原有装饰器的基础上,设置外部变量

def timefun_arg(pre="hello"):
    def timefun(func):
        def wrappedfunc():
            print("%s called at %s %s"%(func.__name__, ctime(), pre))
            return func()
        return wrappedfunc
    return timefun

@timefun_arg("itcast")
def foo():
    print("I am foo")
foo()
sleep(2)
foo()
<--------->
foo called at Tue Jun 19 16:06:31 2018 **itcast**
I am foo
foo called at Tue Jun 19 16:06:33 2018 **itcast**
I am foo

当装饰器需要传入参数时,只需要在外层嵌套一个函数,传入参数,并且返回里面的装饰器函数即可.

实际使用以及@wraps(func)

一般地我们会使用带返回值的不定长参数的装饰器:

import time
from functools import wraps

def timethis(func):
    '''
    Decorator that reports the execution time.
    '''
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end-start)
        return result
    return wrapper

使用装饰器:

>>> @timethis
... def countdown(n):
...     '''
...     Counts down
...     '''
...     while n > 0:
...         n -= 1
...
>>> countdown(100000)
countdown 0.008917808532714844
>>> countdown(10000000)
countdown 0.87188299392912
>>>

其原理countdown = timethis(countdown)

  • 使用*args**kwargs目的是确保任何参数都能适用

  • 返回结果值基本都是调用原始函数func(*args, **kwargs)的返回结果

  • @wraps(func)非常重要,否则会丢失注释等信息

  • 如果想要直接访问原始的未包装的那个函数,可以通过访问__wrapped___属性来访问原始函数

    >>> @somedecorator
    >>> def add(x, y):
    ...     return x + y
    ...
    >>> orig_add = add.__wrapped__
    >>> orig_add(3, 4)
    7

@property

Python内置的@property实现对参数的检查,实现的原理比较复杂,但用法非常简单

class Money(object):
    def __init__(self):
        self.__money = 0

    @property
    def money(self):  # 获取时候调这个,property.  两个函数名字相同
        return self.__money

    @money.setter
    def money(self, value):  # 设置值得时候调这个money.setter
        if isinstance(value, int):
            self.__money = value
        else:
            print("error:不是整形数字")
m = Money()
# 调用的时候直接用
t.money = 1
print(t.money)

类装饰器

将装饰器定义为类的一部分

如果你想在类中定义装饰器,并将其作用在其他的函数或方法上,首先确定它的使用方式,是一个实例方法还是类方法:

from functools import wraps
class A:
    # Decodrator as an 实例方法
    def decorator1(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print("实例方法装饰器")
            return func(*args, **kwargs)
        return wrapper
# 差别在于调用上

    # Decorator as a 类方法
    @classmethod
    def decorator2(cls, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print('类方法装饰器')
            return func(*args, **kwargs)
        return wrapper
# 实例化一个类
a = A()
# 实例方法装饰器
@a.decorator1
def spam():
    pass
spam()

----
# 类方法装饰器
@A.decorator2
def grok():
    pass
grok()

将装饰器定义为类(比较难用)

  • 你想使用一个装饰器去包装函数,但是希望返回一个可调用的实例
  • 需要让装饰器可以同时工作在类定义的内部和外部

解决方案

为了将装饰器定义成一个实例,需要实现__call__()__get__()方法

import types
from functools import wraps

class Profiled:
    def __init__(self, func):
        wraps(func)(self)
        self.ncalls = 0

    def __call__(self, *args, **kwargs):
        self.ncalls += 1
        return self.__wrapped__(*args, **kwargs)

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return types.MethodType(self, instance)

你可以你可以将它当做一个普通的装饰器来使用,在类里面或外面都可以:

@Profiled
def add(x, y):
    return x + y

class Spam:
    @Profiled
    def bar(self, x):
        print(self, x)
>>> add(2, 3)
5
>>> add(4, 5)
9
>>> add.ncalls
2
>>> s = Spam()
>>> s.bar(1)
<__main__.Spam object at 0x10069e9d0> 1
>>> s.bar(2)
<__main__.Spam object at 0x10069e9d0> 2
>>> s.bar(3)
<__main__.Spam object at 0x10069e9d0> 3
>>> Spam.bar.ncalls
3

为类和静态方法提供装饰器

import time
from functools import wraps

# A simple decorator
def timethis(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        r = func(*args, **kwargs)
        end = time.time()
        print(end-start)
        return r
    return wrapper

# Class illustrating application of the decorator to different kinds of methods
class Spam:
    @timethis
    def instance_method(self, n):
        print(self, n)
        while n > 0:
            n -= 1

    @classmethod
    @timethis
    def class_method(cls, n):
        print(cls, n)
        while n > 0:
            n -= 1

    @staticmethod
    @timethis
    def static_method(n):
        print(n)
        while n > 0:
            n -= 1

给类或静态方法提供装饰器是很简单的,不过要确保@方法@classmethod@staticmethod 之后

为类和静态方法提供装饰器-keras中的参数检查

学习代码的一个很好地途径是阅读开源项目的源代码,并且记录好笔记。我们看keras框架中参数检查的设置:

Dense层中装饰器的使用,即@interfaces.legacy_dense_support

class Dense(Layer):
   @interfaces.legacy_dense_support  # 装饰器
    def __init__(self, units,
                 activation=None,
                 use_bias=True,
                 kernel_initializer='glorot_uniform',
                 bias_initializer='zeros',
                 kernel_regularizer=None,
                 bias_regularizer=None,
                 activity_regularizer=None,
                 kernel_constraint=None,
                 bias_constraint=None,
                 **kwargs):
        if 'input_shape' not in kwargs and 'input_dim' in kwargs:
            kwargs['input_shape'] = (kwargs.pop('input_dim'),)
        super(Dense, self).__init__(**kwargs)
        self.units = units
        self.activation = activations.get(activation)
        self.use_bias = use_bias
        self.kernel_initializer = initializers.get(kernel_initializer)
        self.bias_initializer = initializers.get(bias_initializer)
        self.kernel_regularizer = regularizers.get(kernel_regularizer)
        self.bias_regularizer = regularizers.get(bias_regularizer)
        self.activity_regularizer = regularizers.get(activity_regularizer)
        self.kernel_constraint = constraints.get(kernel_constraint)
        self.bias_constraint = constraints.get(bias_constraint)
        self.input_spec = InputSpec(min_ndim=2)
        self.supports_masking = True

再回头看装饰器,其实际上只是generate_legacy_interface(...一串东西)函数的地址,实际上仍然调用的是generate_legacy_interface.

legacy_dense_support = generate_legacy_interface(
    allowed_positional_args=['units'],
    conversions=[('output_dim', 'units'),
                 ('init', 'kernel_initializer'),
                 ('W_regularizer', 'kernel_regularizer'),
                 ('b_regularizer', 'bias_regularizer'),
                 ('W_constraint', 'kernel_constraint'),
                 ('b_constraint', 'bias_constraint'),
                 ('bias', 'use_bias')])

generate_legacy_interface就是一个典型的装饰器,要传入外部参数,则外部嵌套一层,为实例方法提供装饰器:

def generate_legacy_interface(allowed_positional_args=None,
                              conversions=None,
                              preprocessor=None,
                              value_conversions=None,
                              object_type='class'):
    if allowed_positional_args is None:
        check_positional_args = False
    else:
        check_positional_args = True
    allowed_positional_args = allowed_positional_args or []
    conversions = conversions or []
    value_conversions = value_conversions or []

    def legacy_support(func):
        @six.wraps(func)
        def wrapper(*args, **kwargs):
            if object_type == 'class':
                object_name = args[0].__class__.__name__
            else:
                object_name = func.__name__
            if preprocessor:
                args, kwargs, converted = preprocessor(args, kwargs)
            else:
                converted = []
            if check_positional_args:
                if len(args) > len(allowed_positional_args) + 1:
                    raise TypeError('`' + object_name +
                                    '` can accept only ' +
                                    str(len(allowed_positional_args)) +
                                    ' positional arguments ' +
                                    str(tuple(allowed_positional_args)) +
                                    ', but you passed the following '
                                    'positional arguments: ' +
                                    str(list(args[1:])))
            for key in value_conversions:
                if key in kwargs:
                    old_value = kwargs[key]
                    if old_value in value_conversions[key]:
                        kwargs[key] = value_conversions[key][old_value]
            for old_name, new_name in conversions:
                if old_name in kwargs:
                    value = kwargs.pop(old_name)
                    if new_name in kwargs:
                        raise_duplicate_arg_error(old_name, new_name)
                    kwargs[new_name] = value
                    converted.append((new_name, old_name))
            if converted:
                signature = '`' + object_name + '('
                for i, value in enumerate(args[1:]):
                    if isinstance(value, six.string_types):
                        signature += '"' + value + '"'
                    else:
                        if isinstance(value, np.ndarray):
                            str_val = 'array'
                        else:
                            str_val = str(value)
                        if len(str_val) > 10:
                            str_val = str_val[:10] + '...'
                        signature += str_val
                    if i < len(args[1:]) - 1 or kwargs:
                        signature += ', '
                for i, (name, value) in enumerate(kwargs.items()):
                    signature += name + '='
                    if isinstance(value, six.string_types):
                        signature += '"' + value + '"'
                    else:
                        if isinstance(value, np.ndarray):
                            str_val = 'array'
                        else:
                            str_val = str(value)
                        if len(str_val) > 10:
                            str_val = str_val[:10] + '...'
                        signature += str_val
                    if i < len(kwargs) - 1:
                        signature += ', '
                signature += ')`'
                warnings.warn('Update your `' + object_name +
                              '` call to the Keras 2 API: ' + signature, stacklevel=2)
            return func(*args, **kwargs)
        wrapper._original_function = func
        return wrapper
    return legacy_support

这里就利用装饰器对Dense中初始化方法中的参数进行了检查。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值