返回目录

本篇索引

(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. Comb结合android开发

    https://blog.csdn.net/qq_29665509/article/details/79272441 参考comb官方文档 https://blog.csdn.net/qq_29665 ...

  2. ARTS Week 1

    Oct 28,2019 ~ Nov 3,2019 Algorithm 本周的学习的算法是二分法.二分法可以用作查找即二分查找,也可以用作求解一个非负数的平方根等.下面主要以二分查找为例. 为了后续描述 ...

  3. DOCKER 学习笔记9 Kubernetes (K8s) 生产级容器编排 上

    前言 在上一节的学习中.我们已经可以通过最基本的 Docker Swarm 创建集群,然后在集群里面加入我们需要运行的任务 以及任务的数量 这样我们就创建了一个服务. 当然,这样的方式在我们本地虚拟机 ...

  4. Luinx安装RocketMQ

    一.RocketMQ环境 准备两台虚拟机,分别为master01 和master02 二.安装JDK(两台虚拟机相同步骤) 1. 检查当前虚拟机环境有没有JDK rpm -qa|grep java ( ...

  5. 基于LNMP架构部署NextCloud私有云盘

    一.NextCloud 概述 云盘这个词无论是做技术出身的朋友还是普通的网民.想必已经听的非常多了.在日常生活当中我们用的最多的云盘莫过于百度网盘了 在前几年百花齐放的网盘市场.到现如今只剩下了百度网 ...

  6. 带你简单了解域名系统DNS

    带你简单了解域名系统DNS 一.域名简介 1.1.DNS服务的作用 负责解析域名,将域名解析成IP地址. 1.2.域名系统概述 由于32位的IP地址并不容易记忆,人们往往喜欢记忆网站的域名.所以当我们 ...

  7. 开发时从宿主机连接容器中的MySQL

    从宿主机连接Docker容器中的MySQL 刚接触Docker,电脑安装Docker后,使用docker命令pull了一个MySQL5.6的Docker镜像,之后docker run启动创建容器. 可 ...

  8. pytorch --Rnn语言模型(LSTM,BiLSTM) -- 《Recurrent neural network based language model》

    论文通过实现RNN来完成了文本分类. 论文地址:88888888 模型结构图: 原理自行参考论文,code and comment: # -*- coding: utf-8 -*- # @time : ...

  9. prometheus operator(Kubernetes 集群监控)

    一.Prometheus Operator 介绍 Prometheus Operator 是 CoreOS 开发的基于 Prometheus 的 Kubernetes 监控方案,也可能是目前功能最全面 ...

  10. 一键安装php5.6.40脚本(LAMP环境)

    #!/bin/bash #安装依赖软件 yum -y install libxml2-devel curl-devel libjpeg libjpeg-devel libpng libpng-deve ...