返回目录

本篇索引

(1)闭包

(2)装饰器

(3)生成器

(4)协程

(1)闭包

闭包(closure)是很多现代编程语言都有的特点,像C++、Java、JavaScript等都实现或部分实现了闭包功能,很多高级应用都会依靠闭包实现。 一般专业文献上对闭包的定义都比较拗口,比如:“将组成函数的语句和这些语句的执行环境打包在一起时,得到的对象称为闭包。”

其实,简单来说,你可以将闭包看成是一个轻载的类,这个类只有一个函数方法,并且只有为数不多的几个成员变量。 闭包的优点是:实现起来比类稍微轻巧一点(意思就是可以少敲一些代码),并且运行速度比类要快得多(据说约快50%)。下面是一个定义闭包的简单例子:

def foo(x, y):
def hellofun():
print('hellofun x is %d, y is %d.' %(x,y))
return hellofun a = foo(1,2)
b = foo(30,40) a()
b() # 运行结果为:
hellofun x is 1, y is 2.
hellofun x is 30, y is 40.

上例中,foo就定义了一个闭包,它将内部定义的函数hellofun返回(但并不运行这个函数), 同时将入参x,y作为以后hellofun要运行时的环境,隐式地与hellofun打包一起返回。 因此,a=foo(1,2) 语句的作用就是:生成一个闭包对象a,这个对象是可作为函数运行的,且其内部含有隐式的成员变量x=1和y=2。 当后面执行 a() 时,会真正运行这个hellofun函数,并且其运行时的环境就是闭包中的:x=1和y=2。

● 查看闭包中变量的内容

续上例:

print(a.__closure__)
print(a.__closure__[0].cell_contents)
print(a.__closure__[1].cell_contents) # 运行结果为:
(<cell at 0x0000022B7436CFD8: int object at 0x00007FFDB0E37100>, <cell at 0x0000022B74386288: int object at 0x00007FFDB0E37120>)
1
2

● 用闭包实现计数器的例子

def countdown(n):
def next():
nonlocal n # Python3可使用nonlocal关键字,用于声明n为next()函数外部的变量
r = n
n -= 1
return r
return next next = countdown(10)
while True:
v = next()
if not v: break

(2)装饰器

装饰器(decorator)是一个函数,其主要用途是包装另一个函数。它可以在不改动原函数的情况下,增强原函数的功能。 相当于给原函数加装了一个增强包。我们来看一个例子:

def square(x):
return x*x

上面是一个计算平方的函数,但是功能非常简单。我们可以通过为其加装装饰器的方法,增强其功能,比如:为这个函数增加打印计算结果的功能。 代码如下:

# 定义装饰器函数
def print_result(func):
def callf(*args, **kwargs):
r = func(*args, **kwargs)
print('The result is %d.' %r)
return r
return callf # 原函数定义
def square(x):
return x*x # 用装饰器函数装饰原函数
square = print_result(square)

我们先不管装饰器函数的定义,先看最后一行:装饰器的原理就是,将原来的函数square作为参数传递给我们新定义的装饰器函数, 再偷偷将square这个名称替换成我们自己定义的装饰器函数print_result中返回的callf函数。这样,当用户执行比如 square(2) 语句时, 并不是在执行原来的square函数,而是在运行我们的 callf(2)。

接下来我们再来看装饰器函数中的内容,print_result(func)实际上是定义了一个闭包,和前面的例子中将数据x,y作为闭包环境数据传进来不同, 这里将整个square()函数定义作为闭包环境数据传了进来,好在Python中万物皆对象,函数定义本质上也是一个对象, 所以将它作为数据传进来也是可以的。

然后在运行callf(2)的时候,将入参"2"通过(*args, **kwargs)参数原封不动地传给了func(*argc, **kwargs)去运行, 而这个func就是闭包对象中的的数据(即原square函数的定义),在运行完func函数之后(其实就是运行了square(2)之后),增加了一句print打印功能, 最后再将func函数返回的结果r再原封不动地返回出去。整个过程就好像给原函数square套了一个壳,故称为装饰器。

运行装饰后的函数:

y = square(2)
print(y) # 运行结果为:
The result is 4.
4

可以用特殊语法符号@来简写装饰器,以上代码的简写形式为:

# 定义装饰器函数
def print_result(func):
def callf(*args, **kwargs):
r = func(*args, **kwargs)
print('The result is %d.' %r)
return r
return callf # 用@装饰原函数
@print_result
def square(x):
return x*x

● 使用多个装饰器

可以对一个原函数使用多个装饰器,其装饰先后顺序为从下到上、从内到外:

@dec1
@dec2
@dec3
def square(x):
pass # 相当于:
def square(x):
pass
square = dec1(dec2(dec3(square)))

● 接收参数的装饰器

装饰器也可以接收参数,用法如下:

@eventhandler('BUTTON')
def handle_button(msg):
pass @eventhandler('RESET')
def handle_reset(msg):
pass

接收参数的装饰器的语义如下:

temp = eventhandler('BUTTON')
handle_button = temp(handle_button)

接收参数的装饰器通常用于函数的回调注册等用途,下面是以上代码的完整例子:

# 事件处理程序装饰器
event_handlers = {}
def eventhandler(event):
def register_function(f):
event_handlers[event] = f
return f
return register_function @eventhandler('BUTTON')
def handle_button(msg):
pass

当运行带参数的装饰器语句@eventhandler('BUTTON'),首先会运行 temp = eventhandler('BUTTON'),运行完后temp指向的是 register_function(f)函数,并且其环境数据event为'BUTTON'。接下来运行 handle_button = temp(handle_button), 这相当于运行:register_function(handle_button),在这个函数中,会把handle_button函数(即入参f)放入全局字典event_handlers中, 然后再把这个handle_button函数原封不动地返回去,返回给全局名称handle_button。

这个装饰器的用法和我们前面见过的普通装饰器的功能稍有不同,它并没有通过偷换handle_button名称来增强handle_buttton()函数的功能, 仅仅是将handle_button函数放入了全局字典event_handlers,做了一个类似注册的工作。handle_button()函数还是原来那个函数。

(3)生成器

只要在函数中使用了 yield 语句,这个函数就称为生成器(generator)。生成器本身的概念很简单,理解起来也不难, 但是可以用 yield 语句玩出很多花样、写出一些执行效率很高又看上去比较优雅的代码,比如管道、协程等等。

● 基本概念

这里我们先讲生成器的基本概念:调用生成器函数时,函数将返回一个生成器对象,但本身并不运行。 当第一次调用__next__()方法时,函数从头开始运行,直到第一次遇见yield语句,当运行完这句 yield语句后,函数会暂停运行,并将yield语句指定的返回值返回。 之后,可以在这个生成器对象上反复调用__next__()方法,每次调用,都从刚才暂停的地方开始继续运行,并运行到下一个yield语句再次暂停。 最后,当函数中所有数据都迭代完毕,再无yield语句可用时,会引发一个StopIteration异常。用户如捕获到这个异常,可知生成器已迭代完毕。

下例为定义并使用一个生成器的基本方法:

# 定义生成器
def countdown(n):
print('Start counting')
while n > 0:
yield n
n -= 1

使用生成器

>>> c = countdown(3)

>>> print(c.__next__())
Start counting
3
>>> print(c.__next__())
2
>>> print(c.__next__())
1
>>> print(c.__next__())
引发StopIteration异常

上例中,函数countdown()定义了一个生成器。当运行 c = countdown(3) 后,这个生成对象被赋值给了c,在这个生成器对象中,保存了函数运行时内部的上下文局部变量数据。

(1)当第1次调用 c.__next__()语句时,函数从头开始运行,并运行到第一个 yield n 语句暂停,根据yield n 语句的指示, 将 n(此时值为3)作为返回值返回。

(2)当第2次调用 c.__next__()语句时,函数从刚才暂停的地方(即 yield n的后一句:n -= 1)开始运行,并运行到再次遇到 yield n 为止。 此时函数再次暂停,并将 n(此时值为2)作为返回值返回。

(3)当第3次调用 c.__next__()语句时,函数继续从刚才暂停的地方(即 n -= 1)开始运行,并运行到再次遇到 yield n 为止。 此时函数再次暂停,并将 n(此时值为1)作为返回值返回。

(4)当第4次调用 c.__next__()语句时,函数继续从刚才暂停的地方(即 n -= 1)开始运行。当这句 n -= 1 运行完毕后,n的值为0,再回到上面的while语句时,不再满足 while n > 0 的循环要求, 因此,函数结束while循环,运行到函数结束位置。此时,生成器引发StopIteration异常。

另外,可使用 c.send(None) 来代替 c.__next__(),效果是一样的:

>>> c = countdown(3)

>>> print(c.send(None))
Start counting
3
>>> print(c.send(None))
2
>>> print(c.send(None))
1
>>> print(c.send(None))
引发StopIteration异常

● 在for循环中使用生成器

上例仅用于说明生成器的基本概念。一般我们在使用生成器时,通常不会像上面那样手动去调用__next__()方法, 而是通过一个for语句让Python自动调用生成器的__next__()方法,并自动捕获StopIteration异常来结束for循环。

下例为上面的countdown生成器的通常使用方法:

for i in countdown(3):
print(i) # 运行结果为:
3
2
1

● 用生成器实现类似管道的功能

管道是Linux/Unix操作系统中的一种强大的数据流处理方式,它可以将输出程序和输入程序分开实现,非常符合模块化的思想。 而Python的生成器,可以在程序内部模仿这种风格,使得输出函数只要考虑如何输出,输入函数只要考虑如何处理输入的内容, 而将它们的交互糅合交给外部用户去安排。

考虑如下一个任务:假设有一个日志文件 log.txt,这个文件由操作系统负责写入,每当有新用户登录时, 操作系统会在这个文件最后追加一行,记录新登录用户的用户名和登录时间。现在需要编一个Python程序持续监视这个日志文件 (每秒查看一次这个日志文件),每当发现用户名Tom登录时,在屏幕上打印这条登录信息。

如果不使用生成器的话,一般会使用类似如下代码实现这个功能:

import os
import time def grep(line, searchtxt):
if searchtxt in line:
print(line) def tail(f):
f.seek(0, os.SEEK_END) # 移动到文件尾
while True:
line = f.readline() # 如果没有新的内容,readline()会返回空
if not line:
time.sleep(1)
continue grep(line, 'Tom') # 下面是用户使用代码
f = open('log.txt')
tail(f)

代码不难,上例中实现了2个函数,tail()函数负责每秒查看一次日志文件log.txt,若无新内容,则睡眠1秒钟;若发现日志文件中有新内容, 则调用grep()函数进行判断,grep()函数若发现新行中有特定的文本(本例中是'Tom'),则使用print在屏幕上打印这行内容。

上面代码的问题在于,输入输出函数没有做到严格分开,如果现在变更一下需求,需要监视用户名为Jerry的用户并打印这条登录信息, 那么势必要去更改 tail() 函数的内部实现(第16行改成 grep(line, 'Jerry')),这就不符合模块化编程的思想了。

如果使用生成器来编程,就可以很好地做到这一点,代码如下所示:

import os
import time # 输出函数只管输出
def tail(f):
f.seek(0, os.SEEK_END)
while True:
line = f.readline()
if not line:
time.sleep(1)
continue yield line # 输入函数只管处理输入内容
def grep(linegtr, searchtxt):
for line in linegtr:
if searchtxt in line:
yield line # 下面是用户使用代码
f = open('log.txt')
logtxt = tail(f) # logtxt是一个生成器对象,每次
result = grep(logtxt, 'Jerry') # result也是一个生成器对象
for line in result:
print(line)

上述代码中,logtxt = tail(f) 产生了一个生成器,这个生成器永远不会耗尽数据(当没有新数据时会睡眠,但生成器永不会耗尽而引发StopIteration异常), 所以当grep()函数中用 for line in linegtr 去迭代这个logtxt生成器时,当有数据时,会使用后面的 if 语句进行判断,当符合条件即用 yield line 语句返回这行文本; 当没有数据时进程就睡眠。

而grep()函数也是一个生成器,也是永不耗尽。所以当下面的用户代码用 for line in result 语句去迭代这个 result 生成器时, 若grep有数据返回,则用print()函数打印这行内容,若没有则睡眠。

以上代码做到了输出函数与输入函数的分开实现,将交互糅合的工作交给了用户来处理。使用生成器的关键好处是: 它可以使函数返回一些内容,又不退出函数。

● 关闭生成器

生成器在所有数据迭代完后会被自动关闭,一般不需要手动去关闭。但在一些特殊情况下,也可以通过调用close()方法去手动关闭生成器。

c = countdown(3)
c.__next__()
c.close() # 手动关闭生成器 c.__next__() # 报错!close()后再调用__next__()方法会引发StopIteration异常

在生成器内部,在yield语句上出现GeteratorExit异常时,就会调用close()方法,也可以在生成器中手动去捕获这个异常, 以执行一些清理操作,如下例所示:

def countdown(n):
try:
while n > 0:
yield n
n -= 1
except GeneratorExit:
print('The current n is %d' %n)

● 生成器表达式

我们以前学过“列表推导”(list comprehension),可以很方便地根据条件来生成一个列表。其缺点也很明显,如果数据量很大, 会在内存中生成一个庞大的列表,很吃内存资源。

对于大量数据,更好的方法是使用“生成器表达式”(generator expression),它的功能与列表推导相同,但不会立即生成一个大列表, 而是生成一个生成器表达式对象,在后面的迭代过程中,每次仅动态生成需要的部分。

“生成器表达式”的语法同“列表推导”非常相似,只是用圆括号替代方括号,其语法格式如下:

(expression for item1 in iterable1 if condition1
for item2 in iterable2 if condition2
...
for itemN in iterableN if conditionN)

下例打开一个文件,并打印其中所有以 # 开头的注释行

f = opeon('a.txt')
comments = (t for t in lines if t[0] == '#') # comments为生成器表达式对象
for c in comments:
print(c)

上例中,运行 comments = (t for t in lines if t[0] == '#') 语句时,仅仅生成了一个生成器表达式对象,并没有真正去读取整个文件。 而在后面的 for 循环中,才真正去按需读取文件的各行并进行过滤,每一行都是按需生成的。

由于不需要把整个文件都加载到内存中,这对于读取GB级大小的文件时是非常高效的。

最后需要注意的是,生成器表达式不是列表,你不能对它进行下标索引,也不能进行任何诸如append()之类的列表常规操作。 如果需要,你可以使用内置的list()函数,将生成器表达式对象转换成真正的列表。

● 声明式编程

利用生成器表达式,可以写出很多紧凑和高效的代码。假设我们有下面一个文本文件stationery.txt, 第1列为商品名称、第2列为单价、第3列为数量:

pen,20.5,3
ruler,3.0,10
eraser,2.5,8

现在我们要对每行的第2列和第三列求乘积,并将所有行的乘积求和算出总价,传统的实现代码是类似下面这个样子的:

total = 0
for line in open('stationery.txt'):
fields = line.split(',')
total += float(fields[1]) * float(fields[2])
print(total)

而如果使用生成器表达式,可以像这样写:

lines = open('stationery.txt')
fields = (line.split(',') for lien in lines) # fields 为第1个生成器表达式对象
print(sum( float(f[1]) * float(f[2]) for f in fields )) # sum()函数中的内容为第2个生成器表达式对象

这样写的代码比上面的传统写法更紧凑,而且执行速度往往更快。在sum()函数中使用时,可直接在当前的圆括号内使用, 而不必另加一对圆括号。由于在写生成器表达式的时候,仅仅是说明了生成迭代规则, 并不真正运行迭代(真正的迭代运行交给下面的for去完成),有点像写配置文件,故称为“声明式编程”(declarative programming)

生成器表达式还可以与数据库查询语句(SQL select)结合使用,写出非常紧凑的复杂功能代码:

sum(price*qty for price,qty in
cursor.execute('select price, qty from stationery')
if price*qty >= 100)

(4)协程

前面的生成器函数只能单向返回数据,而不能在挂起期间动态接收新的数据。其实,只要在生成器函数中将yield反过来用, 将yield放在等号的右边,并加上括号,就可以接收数据。以这种方式使用yield语句的函数称为协程(coroutine)

就如同前面我们说过“闭包”像一个轻载的类,“协程”就像一个轻载的线程。可以将协程当成一个任务,能发给它数据, 让协程根据收到的数据去完成任务,协程完成任务后会自己挂起;直到下次再收到数据,协程会再次激活运行, 运行完任务后继续挂起……很像一个线程的行为(普通线程是收到特定的信号(Signal)激活,运行完任务后自动挂起,但需要操作系统来调度)。

● 协程的基本用法

下例定义了一个简单的协程 receiver:

def receiver():
print("The receiver is running")
while True:
n = (yield) # 获取外部发给协程的数据
print("Got %s" %n)

使用协程:

>>> r = receiver()        # 生成一个协程
>>> r.__next__() # 调用__next__()是必须的,为的是让函数运行到 yield 前一句
The receiver is running
>>> r.send('Hello')
Got Hello
>>> r.send(1)
Got 1

上面的例子中,receiver()是一个协程,其功能是每次收到新数据后将其打印到屏幕。当第一次调用完__next__()方法后, 函数将运行到n = (yield) 语句的右半部分,暂停并返回(这里返回None)。之后每次通过send()方法给这个协程发数据后, 函数恢复运行(注意:这里会继续运行n = (yield)语句的左半部分,将收到的数据赋值给n),一直运行到下一个yield语句为止。

● 给协程上装饰器

在协程使用过程中,一个常见的错误是:经常会忘记写__next__()调用。我们可以写一个装饰器来自动完成这一个功能:

# 定义装饰器coroutine
def coroutine(func):
def start(*args, **kwargs):
g = func(*args, **kwargs)
g.__next__()
return g
return start @coroutine
def receiver():
print("The receiver is running")
while True:
n = (yield)
print("Got %s" %n)

使用协程:

>>> r = receiver()            # 生成一个协程
The receiver is running # 用户不必自己调用__next__(),装饰器已帮我们自动调用好了
>>> r.send('Hello')
Got Hello
>>> r.send(1)
Got 1

● 关闭协程

协程一般不会自己退出,会永远执行下去(回忆对比一下生成器:会在迭代数据耗尽后自己退出)。 可以使用 close() 方法显式关闭协程,当协程被关闭后,再给协程发数据会引发StopIteration异常。

>>> r = receiver()
>>> r.close()
>>> r.send('Hello') # 报错!close()后再调用send()方法会引发StopIteration异常

同生成器一样,close()操作将在协程内部引发GeneratorExit异常。

另外,还可以在协程对象上使用throw()方法在协程内部引发异常,以这种方式引发的异常将在协程中当前恢复执行的yield语句处出现, 协程可以选择捕捉这个异常并以正确的方式处理它们。顺带提一句,不要通过其他线程给当前线程的协程发送throw()异常。

● 使用协程同时收发数据

协程可以使用yield一句同时接收数据和发出返回值,用法如下:

def receiver():
print("The receiver is running")
result_list = []
while True:
n = (yield result_list) # 接收数据的同时,将result_list 放入返回值
result_list.append(n)
print("Got %s" %n)

使用结果:

>>> r = receiver()
>>> print(r.__next__())
The receiver is running
[]
>>> print(r.send('a'))
Got a
['a']
>>> print(r.send('b'))
Got b
['a', 'b']

(1)上例中,先手动调用__next__()执行到第1个yield语句,这时仅执行这个 yield 语句的右半部分 (yield result_list)。 这个右半部分会返回 result_list列表(此时为空列表)。

(2)当之后调用 r.send('a') 时,协程继续运行 n = (yield result_list) 语句的左半部分,将收到的 'a' 赋值给n, 然后运行到下一个 n = (yield result_list) 语句的右半部分,此时返回的 result_list 的值为['a']。

(3)之后再次调用 r.send('b') 时,分析方法同上类似。

● 使用协程实现并发

在理解了上面协程的基本用法后,我们来看如何用协程实现并发编程。使用协程,可以轻松实现几百几千个轻载任务的并发。 一个典型的应用是处理网络连接,如果有几百个用户连接进来,用协程可以轻松地进行异步处理,相比之下, 如果开几百个线程来处理的话,开销就太大了。

由于网络编程部分还在后面,这里先演示一个用协程处理打开若干个文件的例子,其中coroutine装饰器已在前面的例子中定义。 下面代码的功能是,用户指定一个目录和一个文件名,让程序自动去遍历查找这个目录及其子目录下有没有这个文件, 若有,则打开文件查找其中有没有含“Tom”这个字符串的行,若找到则在屏幕上打印这个行。

import os
import fnmatch @coroutine
def find_files(target):
while True:
topdir, filename = (yield)
for path, dirlist, filelist in os.walk(topdir):
for name in filelist:
if name == filename:
target.send(os.path.join(path, name)) @coroutine
def opener(target):
while True:
name = (yield)
f = open(name)
target.send(f) @coroutine
def cat(target):
while True:
f = (yield)
for line in f:
target.send(line) @coroutine
def grep(pattern, target):
while True:
line = (yield)
if pattern in line:
target.send(line) @coroutine
def printer():
while True:
line = (yield)
sys.stdout.write(line) # 下面是使用协程
finder = find_files(opener(cat(grep("Tom", printer())))) # 发送值给协程,激活协程去工作
finder.send('/var', 'log.txt')
finder.send('testdir1', 'filea.txt')

下面使用协程的第一句:finder = find_files(opener(cat(grep("Tom", printer())))) 的功能是启动所有协程。 下面我们对其运行过程一步步进行分析。

(1)按顺序,最里面的printer()协程最先运行,运行到printer()中的yield语句挂起返回,返回值就是这个printer协程对象。

(2)然后将“Tom”字符串和前面这个printer()返回的协程对象作为参数,传递给grep()协程。 同样的,在grep()中也是运行到yield语句挂起返回,返回这个grep协程对象。

(3)然后再将这个grep协程对象作为入参传递给cat()协程,之后的流程也是类似的。依次一步步往外传……

(4)最后一步就是将opener协程对象作为find_files()协程的入参,生成最外层的find_files协程对象。在find_files()中, 也是运行到yield语句挂起返回,不过find_files()中的这句 topdir, filename = (yield) 语句可同时接收2个数据, 若收到数据则将它们分别赋值给topdir和filename变量。

之后,就可以通过调用 finder 协程对象的send()方法,给协程发数据让协程工作了。发送的顺序与刚才完全倒过来,从最外层的find_files()协程开始。

(1)当执行 finder.send('/var', 'log.txt') 语句后,会激活最外层的find_files协程,它收到“目录”与“文件名”两个数据后,
执行os.walk()方法遍历整个目录树,若在目录树中找到与给定文件名相同的文件,则通过 target.send() 语句,将这个文件名(含完整路径)
发送给opener协程去打开。这里的target就是最先前find_files协程初始化的时候,传进来的opener协程对象。

(2)当opener协程收到数据后,同样也被激活,然后它执行 f = open(name) 完成打开文件的任务,再将这个文件对象 f 发送给cat协程。

(3)cat协程收到数据后,同样也被激活,然后它执行 for 语句来迭代这个文件对象,每次for迭代会返回文件中的一行,
并将这行字符串数据 line 发送给grep协程对象。

(4)grep协程收到一行字符串数据后,通过 if 语句比对这行字符串中是否含有初始化时输入的“Tom”,若有,则将这行字符串发送给printer协程。

(5)最后,当printer协程收到数据后,通过 sys.stdout.write(line) 完成在屏幕打印这行字符串的任务,之后再次运行到yield语句挂起返回。
其外层的各个协程也依次一个个挂起返回,最终完成了一次send()调用。

之后可以任意次调用finder.send()方法来搜索不同的目录和文件名。

返回目录

Python函数进阶:闭包、装饰器、生成器、协程的更多相关文章

  1. Python函数篇:装饰器

    装饰器本质上是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的前提下增加额外的功能,装饰器的返回值也是一个函数对象.它经常用于有切面需求的场景,比如:插入日志.性能测试.事务处理. ...

  2. python函数与方法装饰器

    之前用python简单写了一下斐波那契数列的递归实现(如下),发现运行速度很慢. def fib_direct(n): assert n > 0, 'invalid n' if n < 3 ...

  3. python笔记3 闭包 装饰器 迭代器 生成器 内置函数 初识递归 列表推导式 字典推导式

    闭包 1, 闭包是嵌套在函数中的 2, 闭包是内层函数对外层函数的变量(非全局变量)的引用(改变) 3,闭包需要将其作为一个对象返回,而且必须逐层返回,直至最外层函数的返回值 闭包例子: def a1 ...

  4. python开发函数进阶:装饰器

    一,装饰器本质 闭包函数 功能:就是在不改变原函数调用方式的情况下,在这个函数前后加上扩展功能 作用:解耦,尽量的让代码分离,小功能之前的分离. 解耦目的,提高代码的重用性 二,设计模式 开放封闭原则 ...

  5. python函数:叠加装饰器、迭代器、自定义迭代器、生成式

    一.叠加多个装饰器二.迭代器三.自定义迭代器四.xxx生成式 一.叠加多个装饰器 # 加载装饰器就是将原函数名偷梁换柱成了装饰器最内层那个wrapper函数 # 在加载完毕后,调用原函数其实就是在调用 ...

  6. python函数学习之装饰器

    装饰器 装饰器的本质是一个python函数,它的作用是在不对原函数做任何修改的同时,给函数添加一定的功能.装饰器的返回值也是一个函数对象. 分类: 1.不带参数的装饰器函数: def wrapper( ...

  7. python之嵌套 闭包 装饰器 global、nonlocal关键字

    嵌套: 在函数的内部定义函数闭包: 符合开放封闭原则:在不修改源代码与调用方式的情况下为函数添加新功能  # global 将局部变量变成全局变量 num = 100 def fn1(): globa ...

  8. python 函数基础及装饰器

    没有参数的函数及return操作 def test1(): print ("welcome") def test2(): print ("welcomt test2&qu ...

  9. Python 学习 —— 进阶篇(装饰器、类的特殊方法)

    Python基础部分学完之后,在进入其OOP部分前,先理解一下其装饰器这种结构,其功能可类比于Java中的面向切面编程,下面参见具体实例: def log(f): def fn(x): print ' ...

随机推荐

  1. Codeforces_828

    A.模拟,注意单人的时候判断顺序. #include<bits/stdc++.h> using namespace std; int n,a,b; int main() { ios::sy ...

  2. WeChall_Training: Crypto - Caesar I (Crypto, Training)

    As on most challenge sites, there are some beginner cryptos, and often you get started with the good ...

  3. EMC NW NMM to backup MS AG

    To use EMC NW NMM to backup MS SQL always on database, that is a simple and safe way to protector da ...

  4. Java 添加、读取、删除Excel文档属性

    在文档属性中,可以设置诸多关于文档的信息,如创建时间.作者.单位.类别.关键词.备注等摘要信息以及一些自定义的文档属性.下面将通过Java程序来演示如何设置,同时对文档内的已有信息,也可以实现读取和删 ...

  5. EF core (code first) 通过自定义 Migration History 实现多租户使用同一数据库时更新数据库结构

    前言 写这篇文章的原因,其实由于我写EF core 实现多租户的时候,遇到的问题. 具体文章的链接: Asp.net core下利用EF core实现从数据实现多租户(1) Asp.net core下 ...

  6. Vue项目使用vant框架

    近期在开发h5端项目,用到vant框架,vant是一款基于Vue的移动UI组件,看了vant的官方文档(https://youzan.github.io/vant/#/zh-CN/)感觉不错,功能比较 ...

  7. objectarx 多段线自交检查

    只支持直线段的多段线检查,因为主要用了初中的知识,一元一次方程求交点,详细的说就是,把多段线上相邻的两个点构成一条直线段,然后每条直线段与剩余的直线段求交点,一条直线段就代表一个一元一次方程,知道两点 ...

  8. qt creator源码全方面分析(2-8)

    目录 Editing MIME Types Editing MIME Types Qt Creator使用文件的MIME类型,来确定用于打开文件的模式和编辑器. 例如,Qt Creator在C++编辑 ...

  9. pytorch -- CNN 文本分类 -- 《 Convolutional Neural Networks for Sentence Classification》

    论文  < Convolutional Neural Networks for Sentence Classification>通过CNN实现了文本分类. 论文地址: 666666 模型图 ...

  10. pytorch之 Variable

    import torch from torch.autograd import Variable # Variable in torch is to build a computational gra ...