闭包
何为闭包
如果在一个 内部函数里,对 在外部作用域(但不是在全局作用域)的 变量 进行引用,那么 内部函数 就被认为是 闭包(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部门在对外提供的函数中,首先进行 权限验证,然后才能进行真正的函数操作
问题
方案一
- 将本不该 暴露 给 外层 的权限认证暴露在 使用方 面前
- 如果有多个部门,要每个部门的每个人都要周知,另外还要确保所有人要这么做
方案二
- 看似可行,但S部门对外提供的所有方法都需要修改,每个函数都要调用权限验证,同样也费尽
- 另外代码本着 开放封闭 原则,允许在原来代码上面扩展,不允许修改已实现代码。因为一旦修改,可能其他函数调用该函数的时候就会出现问题。
这就需要应用到装饰器原理
装饰器原理
def w1(func):
def inner():
print("...验证权限..")
func()
return inner
@w1
def f1():
print("f1被调用")
@w1
def f2():
print("f2被调用")
python
解释器就会从上到下解释代码,步骤如下:
def w1(func)
==> 将w1
函数加载到内存@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
即可.
装饰器应用
- 引入日志
- 函数执行时间统计
- 执行函数前预备处理
- 执行函数后清理功能
- 权限校验等场景
- 缓存
装饰器示例
被装饰函数无参数
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中初始化方法中的参数进行了检查。