7.python装饰器

一、装饰器

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

  • 全局作用域是定义在文件级别的变量,函数名。而局部作用域,则是定义函数内部。
  • 关于作用域,我们要理解三点:
    1.在全局不能访问到局部定义的变量
    2.在局部能够访问到全局定义的变量,但是不能修改全局定义的变量(当然有方法可以修改)
    1. 全局变量能够被文件任何地方引用,但修改只能在全局进行操作;如果局部没有找到所需的变量,就会往外进行查找,没有找到就会报错
      下面我们来看看下面实例:
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

二、高级函数

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

    1. 函数名可以作为一个值
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_func[cmd](cmd_1)
    else:
        print("Error")

#将函数作为字典值,实现文本数据的增删查改操作
    1. 函数名可以作为返回值
def outer():
    def inner():
        pass
    return inner

s = outer()
print(s)

######输出结果为#######
<function outer.<locals>.inner at 0x000000D22D8AB8C8>
    1. 函数名可以作为一个参数
def index():
    print("index func")

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

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

index func

三、闭包函数

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

    1. 实例一:以下仅仅在函数内部定义一个函数,但并非闭包函数
def outer():
    def inner():
        print("inner func excuted")
    inner()  # 调用执行inner()函数
    print("outer func excuted")
outer()  # 调用执行outer函数

####输出结果为##########
inner func excuted
outer func excuted
    1. 以下函数内部定义了一个函数,而且还引用了一个外部变量x,但也还不是闭包函数
x = 1
def outer():

    def inner():
        print("x=%s" %x)  # 引用了一个非inner函数内部的变量
        print("inner func excuted")
    inner()  # 执行inner函数
    print("outer func excuted")

outer()
#####输出结果########
x=1
inner func excuted
outer func excuted
    1. 实例3:上面实例满足闭包函数的条件。现在,你应该清楚,作为一个闭包函数,必须得满足上述的两个条件,缺一不可。
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
    1. 实例4:一般情况下,我们都会给闭包函数返回一个值.这里先不说为什么.在接下来的内容中,你会看到这个返回值的用途.
def outer():
    x = 1
    def inner():
        print("x=%s" %x)
        print("inner func excuted")
    print("outer func excuted")
    return inner  # 返回内部函数名
    
outer()
    1. 现在我们来抽象的定义一下闭包函数。它是函数和与其相关的引用环境组合而成的实体。在实现深约束时,需要创建一个能显式表示引用环境的东西,并将它与相关的子程序捆绑在一起,这样捆绑起成为闭包。在上面实例中,我们可以发现,闭包函数,它必须包含自己的函数以及一个外部变量才能真正称得上是一个闭包函数。如果没有一个外部变量与其绑定,那么這个函数不能算得上是闭包函数。
      那么怎么知道一个闭包函数有多少个外部引用变量呢?看看下面代码.
def outer():
    x = 1
    y = 2

    def inner():
        print("x= %s" %x)
        print("y= %s" %y)

    print(inner.__closure__)
    return inner

outer()

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

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

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

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

    1. 根据传入的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.不修改被装饰函数的源代码

    1. 无参装饰器
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()
    1. 有参装饰器
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()

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

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(3)
    print('welecome to index page')

index()

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

    1. 类装饰器
      装饰器不仅可以是函数,还可以是类,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器主要依靠类的call方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。
class Foo(object):
    def __init__(self, func):
        self._func = func

    def __call__(self):
        print('class decorator runing')
        self._func()
        print('class decorator ending')


@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

    def __call__(self, func):
        def _call(*args, **kw):
            print('class decorator runing')
            return func(*args, **kw)
        return _call

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

Bar().bar('aa', 'ids')
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,919评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,567评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,316评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,294评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,318评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,245评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,120评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,964评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,376评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,592评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,764评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,460评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,070评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,697评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,846评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,819评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,665评论 2 354

推荐阅读更多精彩内容