python装饰器大详解

一. 作用域

在 python 中,作用域分为两种: 全局作用域和局部作用域。

 全局作用域是定义在文件级别的变量, 函数名。而局部作用域,则是定义函数内部。

 关于作用域,我们要理解两点:

    a. 在全局不能访问到局部定义的变量

    b. 在局部能够访问到全局定义的变量,但是不能修改全局定义的变量 (当然有方法可以修改)

 下面我们来看看下面实例:

x = 1
def funx():
    x = 10
    print(x)  # 打印出 10

funx()
print(x) # 打印出 1

  如果局部没有定义变量 x, 那么函数内部会从内往外开始查找 x, 如果没有找到, 就会报错

x = 1
def funx():
    print(x)  

funx()
print(x) # 打印出 1
x = 1
def funx():
    def func1():
        print(x)  
    func1()

funx()
print(x) # 打印出 1

 

  因此, 关于作用域的问题,只需要记住两点就行:

    全局变量能够被文件任何地方引用,但修改只能在全局进行操作; 如果局部没有找到所需的变量,就会往外进行查找,没有找到就会报错。

二. 高级函数

我们知道,函数名其实就是指向一段内存空间的地址,既然是地址,那么我们可以利用这种特性来。

a 函数名可以作为一个值

def delete(ps):
    import os
    filename = ps[-1]
    delelemetns = ps[1]
    with open(filename, encoding='utf-8') as f_read,\
        open('tmp.txt', 'w', encoding='utf-8') as f_write:
        for line in iter(f_read.readline, ''):
            if line != '\n':  # 处理非空行
                if delelemetns in line:
                    line = line.replace(delelemetns,'')f_write.write(line)
    os.remove(filename)
    os.rename('tmp.txt',filename)

def add(ps):
filename
= ps[-1]
addelemetns
= ps[1]
with open(filename,
'a', encoding='utf-8') as fp:
fp.write(
"\n", addelemetns)

def modify(ps):
import os
filename
= ps[-1]
modify_elemetns
= ps[1]
with open(filename, encoding
='utf-8') as f_read,
open(
'tmp.txt', 'w', encoding='utf-8') as f_write:
for line in iter(f_read.readline, ''):
if line != '\n': # 处理非空行
if modify_elemetns in line:
line
= line.replace(modify_elemetns, '')
f_write.write(line)
os.remove(filename)
os.rename(
'tmp.txt', filename)

def search(cmd):
filename
= cmd[-1]
pattern
= cmd[1]
with open(filename,
'r', encoding="utf-8") as f:
for line in f:
if pattern in line:
print(line, end="")
else:
print("没有找到")

dic_func ={'delete': delete, 'add': add, 'modify': modify, 'search': search}

while True:
inp
= input("请输入您要进行的操作:").strip()
if not inp:
continue
cmd_1
= inp.split()
cmd
= cmd_1[0]
if cmd in dic_func:
dic_funccmd
else:
print("Error")

将函数作为字典值,实现文本数据的增删查改操作

b. 函数名可以作为返回值

def outer():
    def inner():
        pass
    return inner

s = outer()
print(s)

###### 输出结果为 #######
<function outer.<locals>.inner at 0x000000D22D8AB8C8>

c. 函数名可以作为一个参数

def index():
    print("index func")

def outer(index):
s
= index
s()

outer(index)

###### 输出结果 #########

index func

所以满足上面两个条件中的一个, 都可以称为高级函数.

 

三. 闭包函数

  闭包函数必须满足两个条件:1. 函数内部定义的函数 2. 包含对外部作用域而非全局作用域的引用

  下面通过一些实例来说明闭包函数:

实例一: 以下仅仅在函数内部定义了一个函数, 但并非闭包函数.

def outer():
    def inner():
        print("inner func excuted")inner()  # 调用执行 inner() 函数
    print("outer func excuted")outer()  # 调用执行 outer 函数

#### 输出结果为 ##########
inner func excuted
outer func excuted

实例二: 以下在函数内部定义了一个函数,而且还引用了一个外部变量 x, 那么这个是闭包函数么? 答案:不是

x = 1
def outer():
</span><span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> inner():
    </span><span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">x=%s</span><span style="color: rgba(128, 0, 0, 1)">"</span> %x)  <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 引用了一个非inner函数内部的变量</span>
    <span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">inner func excuted</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
inner()  </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 执行inner函数</span>
<span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">outer func excuted</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)

outer()
##### 输出结果 ########
x=1
inner func excuted
outer func excuted

在回头来看看对闭包函数的定义,是不是两条都满足? 聪明的你,一定发现不满足第二条. 对,这里的变量 x,是属于全局变量, 而非外部作用于域的变量。再来看看下面例子:

def outer():
    x = 1
    def inner():
        print("x=%s" %x)
        print("inner func excuted")inner()
    print("outer func excuted")

outer()

##### 输出结果 #########
x=1
inner func excuted
outer func excuted

显然, 上面实例满足闭包函数的条件。现在,你应该清楚, 作为一个闭包函数, 必须得满足上述的两个条件, 缺一不可。但是, 一般情况下,我们都会给闭包函数返回一个值. 这里先不说为什么. 在接下来的内容中,你会看到这个返回值的用途.

def outer():
    x = 1
    def inner():
        print("x=%s" %x)
        print("inner func excuted")
    print("outer func excuted")
    return inner  # 返回内部函数名
    
outer()

现在我们来抽象的定义一下闭包函数。它是函数和与其相关的引用环境组合而成的实体。在实现深约束时,需要创建一个能显式表示引用环境的东西,并将它与相关的子程序捆绑在一起,这样捆绑起成为闭包。在上面实例中,我们可以发现,闭包函数, 它必须包含自己的函数以及一个外部变量才能真正称得上是一个闭包函数。如果没有一个外部变量与其绑定, 那么這个函数不能算得上是闭包函数。

  那么怎么知道一个闭包函数有多少个外部引用变量呢? 看看下面代码.

def outer():
    x = 1
    y = 2
<span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> inner():
    </span><span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">x= %s</span><span style="color: rgba(128, 0, 0, 1)">"</span> %<span style="color: rgba(0, 0, 0, 1)">x)
    </span><span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">y= %s</span><span style="color: rgba(128, 0, 0, 1)">"</span> %<span style="color: rgba(0, 0, 0, 1)">y)

</span><span style="color: rgba(0, 0, 255, 1)">print</span>(inner.<span style="color: rgba(128, 0, 128, 1)">__closure__</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> inner

outer()

###### 输出结果 #######
(<cell at 0x000000DF9EA965B8: int object at 0x000000006FC2B440>, <cell at 0x000000DF9EA965E8: int object at 0x000000006FC2B460>)

结果表明, 在 inner 内部,引用了两个外部局部变量。如果引用的是非局部变量,那么这里输出的为 None.

  闭包函数的特点:1. 自带作用域 2. 延迟计算

  那么闭包函数有什么作用呢? 我们清楚的知道,闭包函数在定义时,一定会绑定一个外部环境。這个整体才能算的上是一个闭包函数,那么我们可以利用这个绑定特性,来完成某些特殊的功能。

实例三:根据传入的 URL, 来下载页面源码

from urllib.request import urlopen

def index(url)
def get()
return urlopen(url).read()
return get

python = index("http://www.python.org") # 返回的是 get 函数的地址
print(python()) # 执行 get 函数《并且将返回的结果打印出来
baidu = index("http://www.baidu.com")
print(baidu())

有人可以会说, 这个不满足闭包函数的条件啊! 我没有引用非全局的外部变量啊。其实并非如此, 给,我们之前说过,只要在函数内部的变量都属于函数。那么我在 index(url),这个 url 也属于函数内部,只不过我们省略一步而已,所以上面那个函数也是闭包函数。

四. 装饰器

 有了以上基础, 对于装饰器就好理解了.

  装饰器:外部函数传入被装饰函数名,内部函数返回装饰函数名。

  特点:1. 不修改被装饰函数的调用方式 2. 不修改被装饰函数的源代码

a. 无参装饰器

  有如下实例,我们需要计算一下代码执行的时间。

import time, random

def index():
time.sleep(random.randrange(
1, 5))
print("welcome to index page")

根据装饰器的特点,我们不能对 index() 进行任何修改, 而且调用方式也不能变。这时候,我们就可以使用装饰器来完成如上功能.

import time, random

def outer(func): # 将 index 的地址传递给 func
def inner():
start_time
= time.time()
func()
# fun = index 即 func 保存了外部 index 函数的地址
end_time = time.time()
print("运行时间为 %s"%(end_time - start_time))
return inner # 返回 inner 的地址

def index():
time.sleep(random.randrange(
1, 5))
print("welcome to index page")

index = outer(index) # 这里返回的是 inner 的地址,并重新赋值给 index

index()

装饰器实现计时

但是,有些情况,被装饰的函数需要传递参数进去,有些函数又不需要参数,那么如何来处理这种变参数函数呢?下面来看看有参数装饰器的使用情况.

b. 有参装饰器

def outer(func):  # 将 index 的地址传递给 func
    def inner(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)   # fun = index  即 func 保存了外部 index 函数的地址
        end_time = time.time()
        print("运行时间为 %s"%(end_time - start_time))
    return inner  # 返回 inner 的地址

下面来说说一些其他情况的实例。

   如果被装饰的函数有返回值

def timmer(func):
    def wrapper(*args,**kwargs):
        start_time = time.time()
        res=func(*args,**kwargs) #res 来接收 home 函数的返回值
        stop_time=time.time()
        print('run time is %s' %(stop_time-start_time))
        return res  
    return wrapper

def home(name):
time.sleep(random.randrange(
1,3))
print('welecome to %s HOME page' %name)
return 123123123123123123123123123123123123123123

这里补充一点, 加入我们要执行被装饰后的函数, 那么应该是如下调用方式:

  home = timmer(home)  # 等式右边返回的是 wrapper 的内存地址, 再将其赋值给 home,这里的 home 不在是原来的的那个函数, 而是被装饰以后的函数了。

  像 home = timmer(home) 这样的写法,python 给我们提供了一个便捷的方式 ------ 语法糖 @.

  以后我们再要在被装饰的函数之前写上 @timmer, 它的效果就和 home = timmer(home) 是一样的。

  如果一个函数被多个装饰器装饰, 那么执行顺序是怎样的。

import time
import random

def timmer(func):
def wrapper():
start_time
= time.time()
func()
stop_time
=time.time()
print('run time is %s' %(stop_time-start_time))
return wrapper
def auth(func):
def deco():
name
=input('name: ')
password
=input('password: ')
if name == 'egon' and password == '123':
print('login successful')
func()
#wrapper()
else:
print('login err')
return deco

@auth # index = auth(timmer(index))
@timmer # index = timmer(index)
def index():

time.sleep(</span>3<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">welecome to index page</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)

index()

View Code

  实验结果表明, 多个装饰器装饰一个函数, 其执行顺序是从下往上。

  关于装饰器,还有一些高级用法, 有兴趣的可以自己研究研究。

 

c. 类装饰器

  装饰器不仅可以是函数,还可以是类,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器主要依靠类的 __call__ 方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。

class Foo(object):
    def __init__(self, func):
        self._func = func
</span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__call__</span><span style="color: rgba(0, 0, 0, 1)">(self):
    </span><span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">class decorator runing</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)
    self._func()
    </span><span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">class decorator ending</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)

@Foo  # bar = Foo(bar)
def bar():
print('bar')

bar()  # Foo(bar)()

# 结果
#
class decorator runing
#
bar
#
class decorator ending

 

class Foo(object):
    def __init__(self):
        pass
<span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__call__</span><span style="color: rgba(0, 0, 0, 1)">(self, func):
    </span><span style="color: rgba(0, 0, 255, 1)">def</span> _call(*args, **<span style="color: rgba(0, 0, 0, 1)">kw):
        </span><span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">class decorator runing</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)
        </span><span style="color: rgba(0, 0, 255, 1)">return</span> func(*args, **<span style="color: rgba(0, 0, 0, 1)">kw)

    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> _call

class Bar(object):
@Foo()
def bar(self, test, ids): # bar = Foo()(bar)
print('bar')

Bar().bar('aa', 'ids')