引言

函数装饰器(不是设计模式中的装饰器模式)用于在源码中“标记”函数,以某种方式增强函数的行为。这是一项强大的功能,但是若想掌握,必须理解闭包。

除了在装饰器中有用处之外,闭包还是回调式异步编程和函数式编程风
格的基础。
首先看一下装饰器基础知识

装饰器基础知识

装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。 装
饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个
函数或可调用对象。

@decorate
def target():print('running target()')

上述代码的效果与下面写法一样:

def target()print('running target()')target = decorate(target)

上述两个代码片段执行完毕后得到的target 不一定是原来那个 target 函数,而是 decorate(target)返回的函数。

装饰器通常把函数替换成另一个函数:

In [1]: def deco(func): ...:     def inner(): ...:         print('running inner()') ...:     return inner  #deco返回inner函数对象...:                                                                         In [2]: @deco  #使用deco装饰target...: def target(): ...:     print('running target()') ...:                                                                         In [3]: target()    #其实会运行inner
running inner()In [4]: target  #target是inner的引用
Out[4]: <function __main__.deco.<locals>.inner()>

综上,装饰器的一大特性是,能把被装饰的函数替换成其他函数。第二
个特性是,装饰器在加载模块时立即执行。

Python何时执行装饰器

装饰器的一个关键特性是,它们在被装饰的函数定义之后立即运行。这通常是在导入时(即 Python 加载模块时)

#registration.py
# -*- coding: utf-8 -*
registry = [] #保存被 @register 装饰的函数引用
def register(func): #参数func是一个函数print('running register(%s)' % func) #打印被装饰的函数registry.append(func)#存入registryreturn func #必须返回函数,这里返回的就是被装饰的函数#f1和f2被装饰
@register
def f1():print('running f1()')@register
def f2():print('running f2()')#f3没有被装饰
def f3():print('running f3()')def main():print('running main()')print('registry ->',registry)f1()f2()f3()if __name__ == '__main__':main()

运行该脚本,输出为:

running register(<function f1 at 0x0000015C8077E730>) #register在main运行之前运行
running register(<function f2 at 0x0000015C8077E7B8>)
running main()
registry -> [<function f1 at 0x0000015C8077E730>, <function f2 at 0x0000015C8077E7B8>]
running f1()
running f2()
running f3()

如果导入registration.py模块,输出如下:

>>> import registration
running register(<function f1 at 0x0000022BB52DE840>)
running register(<function f2 at 0x0000022BB52DE8C8>)

证明了函数装饰器在导入模块时立即执行,而被装饰的函数只在明确调用时运行。

使用装饰器改进策略模式

from collections import namedtupleCustomer = namedtuple('Customer','name fidelity')class LineItem:def __init__(self,product,quantity,price):self._product = productself._quantity = quantityself._price = pricedef total(self):return self._price * self._quantityclass Order: #上下文,也就是客户端代码def __init__(self,customer,cart,promotion = None):self._customer = customerself._cart = list(cart)self._promotion = promotiondef total(self):if not hasattr(self, '__total'):self.__total = sum(item.total() for item in self._cart)return self.__totaldef due(self):"""折扣"""if self._promotion is None:discount = 0else:discount = self._promotion(self) #只需调用self.promotion()函数return self.total() - discountdef __repr__(self):fmt = '<Order total: {:.2f} due: {:.2f}>'return fmt.format(self.total(), self.due())promos = [] #列表初始为空def promotion(promo_func):promos.append(promo_func) #添加到promos列表return promo_func#原封不动将其返回@promotion#通过@promotion装饰的函数都会添加到promos列表中
def fidelity_promo(order):"""为积分为1000或以上的顾客提供5%折扣"""return order.total() * 0.05 if order._customer.fidelity >= 1000 else 0@promotion
def bulk_item_promo(order):"""单个商品为20个或以上时提供10%折扣"""discount = 0for item in order._cart:if item._quantity >= 20:discount += item.total() * .1return discount@promotion
def large_order_promo(order):"""订单中的不同商品达到10个或以上时提供7%折扣"""distinct_items = {item._product for item in order._cart}if len(distinct_items) >= 10:return order.total() * 0.07return 0def best_promo(order):"""选择可用的最佳折扣"""return max(promo(order) for promo in promos)

与Python使用一等函数实现设计模式给出的方案相比,该方案有几个优点:

  • 促销函数无需使用特殊的名称
  • @promotion装饰器突出了被装饰的函数的作用,便于临时禁用某个促销策略:只需把装饰器注释掉
  • 促销折扣策略可以在其他模块中定义,只要使用@promotion装饰即可

不过,多数装饰器会修改被装饰的函数。通常,它们会定义一个内部函
数,然后将其返回,替换被装饰的函数。使用内部函数的代码几乎都要
靠闭包才能正确运作。为了理解闭包,我们要退后一步,先了解 Python
中的变量作用域。

变量作用域规则

一个函数,读取一个局部变量和一个全局变量:

In [1]: def f1(a): ...:     print(a) ...:     print(b) ...:                                                                         In [2]: f1(3)
3
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-2-db0f80b394ed> in <module>
----> 1 f1(3)<ipython-input-1-c1318c6d0711> in f1(a)1 def f1(a):2     print(a)
----> 3     print(b)4 NameError: name 'b' is not defined

如果先给全局变量 b 赋值,然后再调用 f,那就不会出错:

In [3]: b = 6
In [5]: f1(3)

看一下下面示例中的 f2 函数。前两行代码与前面的 f1 一样,然
后为 b 赋值,再打印它的值。可是,在赋值之前,第二个 print 失败了。


In [8]: b = 6
In [9]: def f2(a): ...:     print(a) ...:     print(b) ...:     b = 9 ...:
In [10]: f2(3)
3
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-10-ddde86392cb4> in <module>
----> 1 f2(3)<ipython-input-9-2304a03d7bfd> in f2(a)1 def f2(a):2     print(a)
----> 3     print(b)4     b = 95 UnboundLocalError: local variable 'b' referenced before assignment

从上面的报错信息可知,是在print(b)处报的错,是不是很吃惊,我也以为会打印6,因为有个全局变量b。

可事实是,Python 编译函数的定义体时,它判断 b 是局部变量,因为在
函数中给它赋值了。生成的字节码证实了这种判断,Python 会尝试从本
地环境获取 b。后面调用 f2(3) 时, f2 的定义体会获取并打印局部变
量 a 的值,但是尝试获取局部变量 b 的值时,发现 b 没有绑定值。

这不是缺陷,而是设计选择:Python 不要求声明变量,但是假定在函数
定义体中赋值的变量是局部变量。

如果在函数中赋值时想让解释器把 b 当成全局变量,要使用 global 声明:

In [11]: b = 6
In [12]: def f3(a): ...:     global b  #使用global 声明...:     print(a) ...:     print(b) ...:     b = 9 ...:                                                                        In [13]: f3(3)
3
6
In [14]: b
Out[14]: 9
In [16]: f3(3)
3
9
In [17]: b = 30
In [18]: b
Out[18]: 30

闭包

闭包指延伸了作用域的函数,其中包含函数定义体中引用、但是
不在定义体中定义的非全局变量。

假如有个名为 avg 的函数,它的作用是计算不断增加的系列值的均
值;例如,整个历史中某个商品的平均收盘价。每天都会增加新价格,
因此平均值要考虑至目前为止所有的价格。

起初,avg 是这样使用的:

>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0

avg 从何而来,它又在哪里保存历史值呢?
我们通过函数式来实现:

# -*- coding: utf-8 -*
def make_averager():series = []#历史值def averager(new_value):series.append(new_value)total = sum(series)return total/len(series)return averagerif __name__ == '__main__':avg = make_averager()print(avg(10))print(avg(11))print(avg(12))

输出为:

10.0
10.5
11.0

注意,seriesmake_averager函数的局部变量,在函数的定义体重初始化了series: series = []。但是,调用avg(10)时,make_averager函数已经返回了,而它的本地作用域也一去不复返了。

averager函数中,series 是自由变量(free variable)。这是一
个技术术语,指未在本地作用域中绑定的变量。


averager 的闭包延伸到那个函数的作用域之外,包含自由变量series 的绑定。

print(avg.__code__.co_varnames) #('new_value', 'total')
print(avg.__code__.co_freevars) #('series',)
print(avg.__closure__) #(<cell at 0x00000261E22D8498: list object at 0x00000261E2371308>,)
print(avg.__closure__[0].cell_contents) #[10, 11, 12],就是series保存的值

avg.__closure__中的各个元素对应于avg.__code__.co_freevars 中的一个名称。这些元素是 cell 对象,有个 cell_contents 属性,保存着真正的值。

闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,
这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。

只有嵌套在其他函数中的函数才可能需要处理不在全局作用域中的外部变量。

nonlocal声明

前面实现make_averager 函数的方法效率不高。我们把所有值存储在历史数列中,然后在每次调用 averager 时使用 sum求和。更好的实现方式是,只存储目前的总值和元素个数,然后使用这两个数计算均值。

def make_averager():count = 0total = 0def averager(new_value):count += 1total += new_valuereturn total/countreturn averager

当我敲完这些代码后,编译器有提示报错,我尝试运行一下,看是什么错误:

UnboundLocalError: local variable 'count' referenced before assignment

count是数字或任何不可变类型时,count += 1语句的
作用其实与 count = count + 1一样。因此,我们在 averager
定义体中count赋值了,这会把 count 变成局部变量total
量也是一样。

在之前的示例没遇到这个问题,因为我们没有给series赋值,只是调用了append方法。也就是说,利用了列表时可变的对象这一事实。

也就是说,重新绑定(赋值)会隐式创建局部变量,就不是自由变量了,因此不会保存在闭包中。

Python 3 引入了 nonlocal 声明。它的作用是把变量标记为自由变量,即使在函数中为变量赋予新值了,也会变成自由变量。

def make_averager():count = 0total = 0def averager(new_value):nonlocal  count,total #和之前global的用法类似count += 1total += new_valuereturn total/countreturn averager

实现一个简单的装饰器

一个简单的装饰器,输出函数的运行时间:

# -*- coding: utf-8 -*
import timedef clock(func):def clocked(*args): #接受任意个定位参数t0 = time.perf_counter()result = func(*args)#clocked的闭包中包含自由变量funcelapsed = time.perf_counter() - t0name = func.__name__arg_str = ', '.join(repr(arg) for arg in args) #参数print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))return resultreturn clocked#返回内部函数,取代被装饰的函数@clock
def snooze(seconds):time.sleep(seconds)@clock
def factorial(n):return 1 if n < 2 else n * factorial(n - 1)if __name__ == '__main__':print('*' * 40, 'Calling snooze(.123)')snooze(.123)print('*' * 40, 'Calling factorial(6)')print('6! =', factorial(6))

运行输出为:

**************************************** Calling snooze(.123)
[0.12056875s] snooze(0.123) -> None
**************************************** Calling factorial(6)
[0.00000079s] factorial(1) -> 1
[0.00002686s] factorial(2) -> 2
[0.00003832s] factorial(3) -> 6
[0.00004899s] factorial(4) -> 24
[0.00005926s] factorial(5) -> 120
[0.00007111s] factorial(6) -> 720
6! = 720

如下代码:

@clock
def factorial(n):return 1 if n < 2 else n * factorial(n - 1)

等价于:

def factorial(n):return 1 if n < 2 else n*factorial(n-1)factorial = clock(factorial)

factorial会作为func参数传给clock,然后,clock函数会返回clocked函数,python在背后会把clocked赋值给factorial。可以查看factorial__name__属性:

print(factorial.__name__) #clocked

现在factorial保存的是clocked函数的引用。

每次调用factorial(n),执行的都是clocked(n)clocked 大致
做了下面几件事。

  1. 记录初始时间 t0。
  2. 调用原来的 factorial 函数,保存结果。
  3. 计算经过的时间。
  4. 格式化收集的数据,然后打印出来。
  5. 返回第 2 步保存的结果。

这是装饰器的典型行为:把被装饰的函数替换成新函数,二者接受相同
的参数,而且(通常)返回被装饰的函数本该返回的值,同时还会做些
额外操作。

上面实现的clock 装饰器有几个缺点:不支持关键字参数,而
且覆盖了被装饰函数的 __name____doc__属性。
在下面的示例中使用functools.wraps 装饰器把相关的属性从 func 复制到 clocked中。此外,这个新版还能正确处理关键字参数。

import functoolsdef clock(func):@functools.wraps(func)def clocked(*args,**kwargs): #接受任意个定位参数和关键字参数t0 = time.time()result = func(*args,**kwargs)#clocked的闭包中包含自由变量funcelapsed = time.time() - t0name = func.__name__arg_lst = []if args:arg_lst.append(', '.join(repr(arg) for arg in args))if kwargs:pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())]arg_lst.append(', '.join(pairs))arg_str = ', '.join(arg_lst)print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))return resultreturn clocked#返回内部函数,取代被装饰的函数

functools.wraps只是标准库中拿来即用的装饰器之一。

标准库中的装饰器

标准库中最值得关注的两个装饰器是 lru_cache 和全新的singledispatch(Python 3.4 新增)。这两个装饰器都在 functools模块中定义。

使用functools.lru_cache做备忘

functools.lru_cache 是非常实用的装饰器,它实现了备忘
(memoization)功能。把耗时的函数的结果保存
起来,避免传入相同的参数时重复计算。LRU 三个字母是“Least
Recently Used”的缩写,表明缓存不会无限制增长,一段时间不用的缓存
条目会被扔掉。

生成第 n 个斐波纳契数这种慢速递归函数适合使用 lru_cache:

from clock_deco import clock@clock
def fibonacci(n):if n < 2:return nreturn fibonacci(n-2) + fibonacci(n-1)if __name__ == '__main__':print(fibonacci(6))

输出:

[0.00000119s] fibonacci(0) -> 0
[0.00000079s] fibonacci(1) -> 1
[0.00008020s] fibonacci(2) -> 1
[0.00000040s] fibonacci(1) -> 1
[0.00000040s] fibonacci(0) -> 0
[0.00000040s] fibonacci(1) -> 1
[0.00002252s] fibonacci(2) -> 1
[0.00004425s] fibonacci(3) -> 2
[0.00014736s] fibonacci(4) -> 3
[0.00000040s] fibonacci(1) -> 1
[0.00000040s] fibonacci(0) -> 0
[0.00000079s] fibonacci(1) -> 1
[0.00002094s] fibonacci(2) -> 1
[0.00004227s] fibonacci(3) -> 2
[0.00000040s] fibonacci(0) -> 0
[0.00000040s] fibonacci(1) -> 1
[0.00002094s] fibonacci(2) -> 1
[0.00000040s] fibonacci(1) -> 1
[0.00000040s] fibonacci(0) -> 0
[0.00000079s] fibonacci(1) -> 1
[0.00002291s] fibonacci(2) -> 1
[0.00004267s] fibonacci(3) -> 2
[0.00008889s] fibonacci(4) -> 3
[0.00015289s] fibonacci(5) -> 5
[0.00032118s] fibonacci(6) -> 8
8

浪费时间的地方很明显:fibonacci(1)调用了 8次,fibonacci(2)调用了 5 次……但是,如果增加两行代码,使用lru_cache,性能会显著改善:

from clock_deco import clock
import functools@functools.lru_cache() #像常规函数那样调用,因为可以接收参数
@clock
def fibonacci(n):if n < 2:return nreturn fibonacci(n-2) + fibonacci(n-1)if __name__ == '__main__':print(fibonacci(6))

输出:

[0.00000040s] fibonacci(0) -> 0
[0.00000040s] fibonacci(1) -> 1
[0.00008573s] fibonacci(2) -> 1
[0.00000079s] fibonacci(3) -> 2
[0.00010509s] fibonacci(4) -> 3
[0.00000079s] fibonacci(5) -> 5
[0.00012168s] fibonacci(6) -> 8
8

每个值只调用了一次,执行时间优化了一半多。

lru_cache 可以使用两个可选的参数来配置。它的签名是:
functools.lru_cache(maxsize=128, typed=False)

  • maxsize 知道缓存多少个调用结果
  • typed 参数如果设为 True,把不同参数类型得到的结果分开保存,即把通常认为相等的浮点数和整数参数(如 1 和 1.0)区分开。

单分派泛函数

Python 3.4 新增的functools.singledispatch 装饰器可以把整体
方案拆分成多个模块,甚至可以为你无法修改的类提供专门的函数。使
@singledispatch 装饰的普通函数会变成泛函数(generic function):根据第一个参数的类型,以不同方式执行相同操作的一组函数。

singledispatch 创建一个自定义的htmlize.register 装饰器,把多个函数绑在一起组成一个泛函数:

from functools import singledispatch
from collections import abc
import numbers
import html@singledispatch #@singledispatch 标记处理 object 类型的基函数。
def htmlize(obj):content = html.escape(repr(obj))return '<pre>{}</pre>'.format(content)@htmlize.register(str) #各个专门函数使用@«base_function».register(«type») 装饰
def _(text): #专门函数的名称无关紧要content = html.escape(text).replace('\n', '<br>\n')return '<p>{0}</p>'.format(content)@htmlize.register(numbers.Integral) #为每个需要特殊处理的类型注册一个函数。numbers.Integral 是int 的虚拟超类
def _(n):return '<pre>{0} (0x{0:x})</pre>'.format(n)
@htmlize.register(tuple) #可以叠放多个 register 装饰器,让同一个函数支持不同类型
@htmlize.register(abc.MutableSequence)
def _(seq):
inner = '</li>\n<li>'.join(htmlize(item) for item in seq)
return '<ul>\n<li>' + inner + '</li>\n</ul>'

只要可能,注册的专门函数应该处理抽象基类(如
numbers.Integralabc.MutableSequence),不要处理具体
实现(如 intlist)。这样,代码支持的兼容类型更广泛。

装饰器是函数,因此可以组合起来使用(即,可以在已经被装饰的函数上应用装饰器)

叠放装饰器

@d1@d2 两个装饰器按顺序应用到 f 函数上,作用相当于 f =d1(d2(f))

@d1
@d2
def f():print('f')

等同于:

def f():print('f')
f = d1(d2(f)) #注意顺序

参数化装饰器

如何让装饰器接受其他参数呢?答案是:创建一个装饰器
工厂函数,把参数传给它,返回一个装饰器,然后再把它应用到要装饰
的函数上。

我们用registration.py模块的删减版为例:

registry = []
def register(func):print('running register(%s)' % func)registry.append(func)return func
@register
def f1():print('running f1()')print('running main()')
print('registry ->', registry)
f1()

一个参数化的注册装饰器

为了便于启用或禁用 register 执行的函数注册功能,我们为它提供
一个可选的 active 参数,设为 False 时,不注册被装饰的函数:

# -*- coding: utf-8 -*
registry = set() #set对象,使得添加和删除函数的速度更快
def register(active=True): #接收一个可选的关键字参数def decorate(func): #decorate这个内部函数才是真正的装饰器,参数是一个函数print('running register(active=%s)->decorate(%s)' % (active, func))if active: #只有active参数的值(从闭包中获得)是True时才注册funcregistry.add(func)#存入registryelse:registry.discard(func) #实现反向注册(注销)功能return func #decorate 是装饰器,必须返回一个函数return decorate#register 是装饰器工厂函数,返回 decorate#@register工厂函数必须作为函数调用,并传入所需的参数
@register(active=False)
def f1():print('running f1()')#哪怕不传参数,也要作为函数调用
@register()
def f2():print('running f2()')def f3():print('running f3()')

从概念上看,这个新的 register 函数不是装
饰器,而是装饰器工厂函数。调用它会返回真正的装饰器,这才是应用
到目标函数上的装饰器。

如果导入上面的模块,得到的结果如下:

>>> import registration
running register(active=False)->decorate(<function f1 at 0x000001CEA7FBE8C8>)
running register(active=True)->decorate(<function f2 at 0x000001CEA7FBE950>)
>>>registration.registry
{<function f2 at 0x0000026C7B43E950>}

如果不使用 @句法,那就要像常规函数那样使用 register;若想把 f
添加到 registry中,则装饰 f 函数的句法是register()(f);不
想添加(或把它删除)的话,句法是 register(active=False)(f)
下面演示了如何把函数添加到 registry 中,以及如何从中删除函数:

>>> from registration import *
running register(active=False)->decorate(<function f1 at 0x0000028C925CE8C8>)
running register(active=True)->decorate(<function f2 at 0x0000028C925CE950>)
>>> registry #导入模块时,只有f2在registry中
{<function f2 at 0x0000028C925CE950>}
>>> register()(f3) #register() 表达式返回 decorate,然后把它应用到 f3 上
running register(active=True)->decorate(<function f3 at 0x0000028C925CE840>)
<function f3 at 0x0000028C925CE840>
>>> registry#上面把f3注册到了registry中
{<function f3 at 0x0000028C925CE840>, <function f2 at 0x0000028C925CE950>}
>>> register(active=False)(f2)#将f2删除
running register(active=False)->decorate(<function f2 at 0x0000028C925CE950>)
<function f2 at 0x0000028C925CE950>
>>> registry#确认f2已经删除
{<function f3 at 0x0000028C925CE840>}

《流畅的Python》读书笔记——Python函数装饰器和闭包相关推荐

  1. python function at 0x00000_Python 中的函数装饰器和闭包

    本文是<流畅的Python>第7章的学习笔记. 函数装饰器可以被用于增强方法的某些行为,如果想自己实现装饰器,则必须了解闭包的概念. 装饰器的基本概念 装饰器是一个可调用对象,它的参数是另 ...

  2. Python 学习笔记9(装饰器,decorator)

    Python 学习笔记9(装饰器,decorator) 31 装饰器 装饰器可以对一个函数.方法或者类进行加工,是一种高级的python语法. 装饰函数 接收一个可调用对象作为输入参数,并返回一个新的 ...

  3. CHAR.VI 函数装饰器和闭包

    CHAR.VI 函数装饰器和闭包 函数装饰器用于在源码中"标记"函数,以某种方式增强函数的行为.这是一项强大的功能,但是若想掌握,必须理解闭包. nonlocal 是新近出现的保留 ...

  4. python装饰器与闭包_Python:函数装饰器和闭包

    摘自<流畅的python> 7.1 装饰器基础知识 装饰器是可调用的对象,其参数是另一个函数(被装饰的函数). 装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用 ...

  5. Python的神奇功能——函数装饰器MetaClass

    Python中的装饰器,会让很多人望而却步.不要被它吓跑,啃下它,其实超有用,也没有想象中难. 所谓的装饰器,其实就是通过装饰器函数,来修改原函数的一些功能,使得原函数不需要修改. Python的装饰 ...

  6. python基础教程:函数装饰器详解

    谁可以作为装饰器(可以将谁编写成装饰器): 函数 方法 实现了__call__的可调用类 装饰器可以去装饰谁(谁可以被装饰): 函数 方法 类 基础:函数装饰器的表现方式 假如你已经定义了一个函数fu ...

  7. [python 进阶] 第7章 函数装饰器和闭包

    文章目录 7.1 装饰器基础知识 7.2 Python何时执行装饰器 7.3 使用装饰器改进"策略" 7.4 变量作用域(global) 备注 -比较字节码(暂略) 7.5 闭包 ...

  8. python function at 0x00000_Python函数装饰器原理与用法详解

    本文实例讲述了Python函数装饰器原理与用法.分享给大家供大家参考,具体如下: 装饰器本质上是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的前提下增加额外的功能,装饰器的返回值 ...

  9. python function at 0x00000_《fluent python》第七章 函数装饰器和闭包

    前言 函数装饰器用于在代码中"标记"函数,以某种方式增强函数的行为. 想理解与掌握这一功能必须先理解闭包:除此之外,闭包还是回调式异步编程和函数式编程风格的基础 装饰器 装饰器是可 ...

  10. python 闭包_一起看流畅的python:函数装饰器和闭包

    装饰器 装饰器是可调用的对象,其参数是另一个函数(被装饰的函数).装 饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个 函数或可调用对象. 装饰器的一个关键特性是,它们在被装饰的函数定义 ...

最新文章

  1. 一文看懂AI数据采集标注未来三年的发展和趋势
  2. 云计算-从基础到应用架构系列-云计算的概念
  3. No module named keras.engine.base_layer
  4. tensorflow_yolo-v3笔记 IOU:Intersection over union 交并比
  5. 第2章-神经网络的数学基础(笔记)
  6. Retrofit+RxJava联网工具类
  7. js中 给json对象添加属性和json数组添加元素
  8. MyEclipse搭建SSH(Struts2+Spring2+Hibernate3)框架项目教程
  9. MySQL导入sql文件的三种方法
  10. 白话计算机入门书籍--《穿越计算机的迷雾》有感
  11. 【YOLO3D】:端到端3D点云输入的实时检测
  12. c语言去尾法和进一法的例子,用“进一法”和“去尾法”解决问题教学案例
  13. 合作伙伴最怕的是什么_创业须知:什么样的人是好的合作伙伴?
  14. python蓝桥杯省赛冲刺篇——3真题:答疑、鲁卡斯队列、金币、最大化股票交易的利润、谈判、排座椅
  15. dede php 输出html,DedeCMS系统,后台模板生成HTML页面时,不能解析dede标签,直接输出dede标签乱码的问题。...
  16. 【Apollo 6.0项目实战】Perception模块
  17. java7G怎么表示_Java研发方向如何准备BAT技术面试答案(上)
  18. CAS机制以及简单实现
  19. visual添加mysql.dll_MySQL.VisualStudio.dll,下载,简介,描述,修复,等相关问题一站搞定_DLL之家...
  20. 电脑蓝牙耳机连接不稳定_一个困扰我半年的 macOS 蓝牙有时断连的问题终于解决了!...

热门文章

  1. Ext 介绍入门之 Templates(模板)
  2. android - startActivity浅谈
  3. 三种DDL的简述:create、alter、drop
  4. linux内核启动地址的确定
  5. Vert.x 异步访问数据库 MySQL
  6. django Form 效验
  7. JAVA学习总结-面向对象
  8. Java TCP协议传输
  9. 关于18183-王者荣耀专区网站的TDK简要分析(更多内容请访问http://www.eduaskx6.com/)...
  10. HTML和CSS代码片段快速编写方式(Emmet Documentation)