python总结之闭包和装饰器

目录
  • 一、装饰器
    • 1. 装饰器的简单介绍
    • 2. 装饰器的解析过程
  • 二、闭包
  • 三、闭包中nonlocal语句的使用
    • 1. 外部变量的引用和改写
    • 2. nolocal的使用及特点
  • 四、闭包与装饰器
  • 五、闭包的作用
  • 六、几个小栗子
    • 栗子1:
    • 栗子2:
    • 栗子3
  • 七、特殊的装饰器
    • property 装饰器
      • 1. 我们为什么需要用到property
      • 2. 使用Getters和Setters
      • 3. property的作用
      • 4. 小栗子
    • staticmethod装饰器和classmethod装饰器
      • step1:定义实例方法count()。
      • step2:定义静态方法count()。
      • step3:定义类方法count()。
  • 总结

一、装饰器

1. 装饰器的简单介绍

“装饰器的功能是将被装饰的函数当作参数传递给与装饰器对应的函数(名称相同的函数),并返回包装后的被装饰的函数”,听起来有点绕,没关系,直接看示意图,其中 a 为与装饰器 @a 对应的函数, b 为装饰器修饰的函数,装饰器@a的作用是:

举个栗子:

def test(func):
    return func
@test
def afunc():
    print("hello")
afunc()

# hello

上面使用@test来表示装饰器,其等同于:afunc = test(afunc),因此装饰器本质上就是个语法糖,其作用为简化代码,以提高代码可读性。

2. 装饰器的解析过程

step1. python 解释器发现@test,就去调用与其对应的test函数

step2. test函数调用前要指定一个参数,传入的就是@test下面修饰的函数,也就是afunc()

step3. test() 函数执行,调用 afunc(),afunc() 打印“hello”

二、闭包

在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

闭包并不是Python中特有的概念,所有把函数做为一等公民的语言均有闭包的概念。不过像Java这样以class为一等公民的语言中也可以使用闭包,只是它得用类或接口来实现。

通过Python的语言介绍,一个闭包就是你调用了一个函数A,这个函数A返回了一个函数B给你。这个返回的函数B就叫做闭包。你在调用函数A的时候传递的参数就是自由变量。

举个栗子:

def func(name):
    def inner_func(age):
        print 'name:', name, 'age:', age
    return inner_func

bb = func('the5fire')
bb(26)  # >>> name: the5fire age: 26

这里面调用func的时候就产生了一个闭包——inner_func,并且该闭包持有自由变量——name,因此这也意味着,当函数func的生命周期结束之后,name这个变量依然存在,因为它被闭包引用了,所以不会被回收。

三、闭包中nonlocal语句的使用

1. 外部变量的引用和改写

在 python 的函数内,可以直接引用外部变量,但不能改写外部变量。

例如在下面的栗子,

counter中可以正常打印常量count,但无法改变count;对list可以执行append操作,正常修改。

def cnt(param):
    count = 0
    alist = []
    def counter():
        alist.append(1)
        # count += 1 # UnboundLocalError: local variable 'count' referenced before assignment
        print(param, str(count), alist)
    return counter

test = cnt("test")
test()
# test 0 [1]

2. nolocal的使用及特点

为了解决上述不可变变量的修改问题:

python 2 中可以在函数内使用 global 语句,但全局变量在任何语言中都不被提倡,因为它很难控制。python 3 中引入了 nonlocal 语句解决了这个问题。

Nonlocal 与 global 的区别在于:nonlocal 语句会去搜寻本地变量与全局变量之间的变量,其会优先寻找层级关系与闭包作用域最近的外部变量。

def cnt(param):
    count = 0
    def counter():
        nonlocal count
        count += 1
        print(param, str(count))
    return counter

test = cnt("test")
test()

# test 1

四、闭包与装饰器

上面已经简单演示了装饰器的功能,事实上,装饰器就是一种的闭包的应用,只不过其传递的(自由变量)是函数:

使用装饰器的写法:

def make1(fn):
    def wrapped():
        return "<a>" + fn() + "</a>"
    return wrapped

def make2(fn):
    def wrapped():
        return "<b>" + fn() + "</b>"
    return wrapped

@make1
@make2
def hello():
    return "hello"

print(hello())
# <a><b>hello</b></a>

显式使用闭包的写法:

def make1(fn):
    def wrapped():
        return "<a>" + fn() + "</a>"
    return wrapped

def make2(fn):
    def wrapped():
        return "<b>" + fn() + "</b>"
    return wrapped

def hello():
    return "hello"

hello = make2(hello)
hello = make1(hello)
print(hello())
# <a><b>hello</b></a>

多个装饰器装饰一个函数时,执行时的顺序是:最先装饰的装饰器,最后一个执行。它遵循了先进后出规则 类似于stack。

五、闭包的作用

闭包的最大特点是可以将父函数的变量与内部函数绑定,并返回绑定变量后的函数(也即闭包),此时即便生成闭包的环境(父函数)已经释放,闭包仍然存在。

这个过程很像类(父函数)生成实例(闭包),不同的是父函数只在调用时执行,执行完毕后其环境就会释放,而类则在文件执行时创建,一般程序执行完毕后作用域才释放,因此对一些需要重用的功能且不足以定义为类的行为,使用闭包会比使用类占用更少的资源,且更轻巧灵活。

假设我们仅仅想打印出各类动物的叫声,分别以类和闭包来实现:

样的,但显然类的实现相对繁琐,且这里只是想输出一下动物的叫声,定义一个 Animal 类未免小题大做,而且 voice 函数在执行完毕后,其作用域就已经释放,但 Animal 类及其实例 dog 的相应属性却一直贮存在内存中。

除此之外,闭包还有很多其他功能,比如用于封装等,另外,闭包有效的减少了函数参数的数目,这对并行计算非常有价值,比如可以让每台电脑负责一个函数,然后串起来,实现流水化的作业等。

六、几个小栗子

栗子1:

def outer(f):
    def inner(*arg, **kargs):
        inner.co += 1
        return f(*arg, **kargs)
    inner.co = 0
    return inner

@outer
def cu():
    pass

if __name__ == '__main__':
    cu()
    cu()
    cu()
    print(cu.co)
# 3

栗子2:

下述样例中,注意点:

  • 首先解析装饰器A,装饰器装饰了几个类就执行几次,输出两次“i赋值”
  • B和C为两个对象,属性值单独累加。
def A(func):
    def inner():
        inner.i += 1
        print("i加1,i={0}".format(inner.i))
    inner.i = 0
    print("i赋值")
    return inner

@A
def B():
    pass

@A
def C():
    pass
B()
B()
B()
C()
C()
print(id(B), id(B.i))
print(id(C), id(C.i))

i赋值
i赋值
i加1,i=1
i加1,i=2
i加1,i=3
i加1,i=1
i加1,i=2
281473235252496 187650677653032
281473235252768 187650677653000

栗子3

  • 装饰器是在python解释器加载test函数的时候就完成的,即使不调用test函数,也会输出"bbb"和"aaa",输出顺序为,dec_b装饰了test,执行输出bbb,dec_a装饰了dec_b,执行输出aaa;
  • 执行test等同于执行dec_a(dec_b(test))
def dec_a(function):    print("aaa")    def inner_func():        print("before function")        function()    return inner_funcdef dec_b(function):    print("bbb")    def inner_func():        function()        print("after function")    return inner_func@dec_a@dec_bdef test():    print("test")test()
bbbaaabefore functiontestafter function

七、特殊的装饰器

property 装饰器

参考这篇文章:https://www.tianqiweiqi.com/python-property.html

property 是Python中很赞的概念,它使得面向对象的编程更加简单。

在Python中,property()是一个内置函数,用于创建和返回一个property对象。Property对象有三个方法,getter(), setter()和delete(),用来在对象创建后设置fget,fset和fdel。

装饰器(decorator)可以给函数动态加上功能,对于类的方法,装饰器一样起作用。Python内置的@property装饰器就是负责把一个方法变成属性调用的。属性是对事物某种特性的抽象,面向对象编程中一个重要概念;区别于字段,它通常表示为字段的扩展,加以访问与设置保护机制。

1. 我们为什么需要用到property

博文中假设了一种场景,假设我们有一个存储并转化温度的需求,可以通过类实现:

class Celsius:
    def __init__(self, temperature = 0):
        self.temperature = temperature
    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

然后通过类实例进行温度的设定和获取,且可以看到这个属性已经被添加man.__dict__中了。

>>> man = Celsius()
>>> man.temperature = 37
>>> man.temperature
37
>>> man.to_fahrenheit()
98.60000000000001

>>> man.__dict__
{'temperature': 37}

但是此时如果我们需要对温度的设定进行一定的约束,此前的方案是没办法做到的。

2. 使用Getters和Setters

对于上边的约束,一个很容易想到的解决方案是隐藏其温度属性(使其私有化),并且定义新的用于操作温度属性的getter和setter接口。可以这么实现:

class Celsius:
    def __init__(self, temperature = 0):
        self.set_temperature(temperature)

    def to_fahrenheit(self):
        return (self.get_temperature() * 1.8) + 32

    def get_temperature(self):
        return self._temperature

    def set_temperature(self, value):
        if value < -273:
            raise ValueError("Temperature below -273 is not possible")
        self._temperature = value

上述方案虽然满足了基本需求,但是有个问题是,在赋值和调用时,需要修改调用方式,例如obj.temperature需改为obj.get_temperature()obj.temperature = val改为obj.set_temperature(val)。

我们希望我们的更新是不向后兼容地。这就是需要property闪亮登场的地方。

3. property的作用

对于上边的问题,Python式的解决方式是使用property,在setter中进行参数校验:

class Celsius:
    def __init__(self, temperature = 0):
        self._temperature = temperature

    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

    @property
    def temperature(self):
        print("Getting value")
        return self._temperature

    @temperature.setter
    def temperature(self, value):
        if value < -273:
            raise ValueError("Temperature below -273 is not possible")
        print("Setting value")
        self._temperature = value

在Python中,property()是一个内置函数,用于创建和返回一个property对象。该函数的签名为:

property(fget=None, fset=None, fdel=None, doc=None)

只定义getter方法,不定义setter方法就是一个只读属性;
否则为可读可写属性,且在setter中进行参数校验。

4. 小栗子

class Student():
    def __init__(self):
        self._score = 10000

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if value < 0:
            print("wrong value")
            return
        self._score = value

    @score.deleter
    def score(self):
        del self._score

a = Student()
a.score = 99
print(a.score)

del a.score
a.score = -1
print(a.score)

# 99
# wrong value
# AttributeError: 'Student' object has no attribute '_score'

staticmethod装饰器和classmethod装饰器

python面向对象编程中,类中定义的方法:

  • @classmethod 装饰的类方法:第一个参数必须是cls
  • @staticmethod 装饰的静态方法:和普通的函数没有区别
  • 不带装饰器的实例方法:第一个参数必须是 self

以一个简单的代码为例,执行方式如下:

class A(object):   # 创建一个类对象,初始化类属性和方法
    def m1(self, n):
        print("self:", self)

    @classmethod
    def m2(cls, n):
        print("cls:", cls)

    @staticmethod
    def m3(n):
        pass

a = A() # 调用类构造器,构造实例对象a
a.m1(1) # 内部把[实例对象a]传递给[self]进行绑定,self和a指向同一个实例对象。
A.m2(1) # 内部把[类对象A]传递给[cls],cls和A都指向类对象。
A.m3(1)

下面分别使用不同的类方法进行代码的测试:

step1:定义实例方法count()。

Spam.numInstances为类调用,直接返回初始化的99;x.numInstances为实例化调用,在实例化时调用了init构造方法,调用了实例方法count,在99的基础上加1。

Sub.numInstances, Other.numInstances为类调用,直接返回初始化的1;y1.numInstances, z1.numInstances为实例化调用,由于sub和other子类继承了父类spam,且在内部没有定义init方法,因此返回父类的init,调用count,在初始化的基础上加1。

class Spam:
    numInstances = 99
    def count(self):
        self.numInstances += 1
    def __init__(self):
        self.count()

class Sub(Spam):
    numInstances = 0

class Other(Spam):
    numInstances = 0

x = Spam()
y1, y2 = Sub(), Sub()
z1, z2, z3 = Other(), Other(), Other()
print(x.numInstances, y1.numInstances, z1.numInstances)
print(Spam.numInstances, Sub.numInstances, Other.numInstances)

100 1 1
99 0 0

step2:定义静态方法count()。

每次实例化都会调用init方法,调用count对类属性Spam.numInstances的值进行累加,因此实例化几次,就会累加多少次。

class Spam:
    numInstances = 99
    @staticmethod
    def count():
        Spam.numInstances += 1
    def __init__(self):
        self.count()

class Sub(Spam):
    numInstances = 0

class Other(Spam):
    numInstances = 0

x = Spam()
y1, y2 = Sub(), Sub()
z1, z2, z3 = Other(), Other(), Other()
print(x.numInstances, y1.numInstances, z1.numInstances)
print(Spam.numInstances, Sub.numInstances, Other.numInstances)

105 0 0
105 0 0

step3:定义类方法count()。

在实例化Sub和Other子类时,子类内部定义了numInstances,因此会在cls.numInstances += 1时,分别在Sub和Other各自的numInstances 分别进行累加,实例化多少次,进行多少次累加。

class Spam:
    numInstances = 99
    @classmethod
    def count(cls):
        cls.numInstances += 1
    def __init__(self):
        self.count()

class Sub(Spam):
    numInstances = 0

class Other(Spam):
    numInstances = 0

x = Spam()
y1, y2 = Sub(), Sub()
z1, z2, z3 = Other(), Other(), Other()
print(x.numInstances, y1.numInstances, z1.numInstances)
print(Spam.numInstances, Sub.numInstances, Other.numInstances)

100 2 3
100 2 3

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • Python 中闭包与装饰器案例详解

    项目github地址:bitcarmanlee easy-algorithm-interview-and-practice 1.Python中一切皆对象 这恐怕是学习Python最有用的一句话.想必你已经知道Python中的list, tuple, dict等内置数据结构,当你执行: alist = [1, 2, 3] 时,你就创建了一个列表对象,并且用alist这个变量引用它: 当然你也可以自己定义一个类: class House(object): def __init__(self, are

  • Python闭包装饰器使用方法汇总

    闭包内容: 匿名函数:能够完成简单的功能,传递这个函数的引用,只有功能 普通函数:能够完成复杂的功能,传递这个函数的引用,只有功能 闭包:能够完成较为复杂的功能,传递这个闭包中的函数以及数据,因此传递是功能+数据 对象:能够完成最复杂的功能,传递很多数据+很多功能,因此传递的是数据+功能 ------------------- 对全局函数进行修改:在函数当中加global,在闭包中外边中的变量加nonlocal 闭包定义:有两个函数嵌套使用,里面的函数可以使用外面函数所传输的参数,最后可传递的是

  • Python闭包与装饰器原理及实例解析

    一.闭包 闭包相当于函数中,嵌套另一个函数,并返回.代码如下: def func(name): # 定义外层函数 def inner_func(age): # 内层函数 print('name: ', name, ', age: ', age) return inner_func # 注意此处要返回,才能体现闭包 bb = func('jayson') # 将字符串传给func函数,返回inner_func并赋值给变量 bb(28) # 通过变量调用func函数,传入参数,从而完成闭包 >>

  • python闭包和装饰器你了解吗

    目录 一.闭包 1. 什么是闭包? 2. 形成闭包的三个条件(缺一不可) 3. 闭包的原理 4. 闭包的好处 二.装饰器 1. 什么是装饰器 2. 装饰器有什么用 3. 小 练 习 三. 编写和使用装饰器 1. 小案例 2.应用注意事项 3.保留元数据-什么是元数据 4.保留元数据-装饰器后为什么元数据会丢失 5.保留元数据 6.小案例 7.内置的装饰器 四. 装饰器的应用 1.为什么使用装饰器 2.在何处使用装饰器 五. 课后作业 1.实现函数注册表 2.完善三创购物系统 总结 一.闭包 1.

  • Python 中的函数装饰器和闭包详解

    函数装饰器可以被用于增强方法的某些行为,如果想自己实现装饰器,则必须了解闭包的概念. 装饰器的基本概念 装饰器是一个可调用对象,它的参数是另一个函数,称为被装饰函数.装饰器可以修改这个函数再将其返回,也可以将其替换为另一个函数或者可调用对象. 例如:有个名为 decorate 的装饰器: @decorate def target(): print('running target()') 上述代码的写法和以下写法的效果是一样的: def target(): print('running targe

  • Python闭包及装饰器运行原理解析

    一.闭包 闭包从形式上来说是在外部函数中定义内部函数,并且内部函数引用了外部函数的变量,此变量叫做自由变量. 或者说是将组成函数的语句和这些语句的执行环境打包在一起. 闭包满足的条件: 必须有一个内嵌函数 内嵌函数必须使用外部函数的变量 外部函数的返回值必须是内嵌函数 def closure(): value = [] def fun(tmp): value.append(tmp) return value return fun cc = closure() cc(0) #[0] 等同于clos

  • 深入理解python中的闭包和装饰器

    python中的闭包从表现形式上定义(解释)为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure). 以下说明主要针对 python2.7,其他版本可能存在差异. 也许直接看定义并不太能明白,下面我们先来看一下什么叫做内部函数: def wai_hanshu(canshu_1): def nei_hanshu(canshu_2): # 我在函数内部有定义了一个函数 return canshu_1*canshu_2 return

  • python总结之闭包和装饰器

    目录 一.装饰器 1. 装饰器的简单介绍 2. 装饰器的解析过程 二.闭包 三.闭包中nonlocal语句的使用 1. 外部变量的引用和改写 2. nolocal的使用及特点 四.闭包与装饰器 五.闭包的作用 六.几个小栗子 栗子1: 栗子2: 栗子3 七.特殊的装饰器 property 装饰器 1. 我们为什么需要用到property 2. 使用Getters和Setters 3. property的作用 4. 小栗子 staticmethod装饰器和classmethod装饰器 step1:

  • python中函数总结之装饰器闭包详解

    1.前言 函数也是一个对象,从而可以增加属性,使用句点来表示属性. 如果内部函数的定义包含了在外部函数中定义的对象的引用(外部对象可以是在外部函数之外),那么内部函数被称之为闭包. 2.装饰器 装饰器就是包装原来的函数,从而在不需要修改原来代码的基础之上,可以做更多的事情. 装饰器语法如下: @deco2 @deco1 def func(arg1,arg2...): pass 这个表示了有两个装饰器的函数,那么表示的含义为:func = deco2(deco1(func)) 无参装饰器语法如下:

  • 详解 Python中LEGB和闭包及装饰器

    详解 Python中LEGB和闭包及装饰器 LEGB L>E>G?B L:local函数内部作用域 E:enclosing函数内部与内嵌函数之间 G:global全局作用域 B:build-in内置作用域 python 闭包 1.Closure:内部函数中对enclosing作用域变量的引用 2.函数实质与属性 函数是一个对象 函数执行完成后内部变量回收 函数属性 函数返回值 passline = 60 def func(val): if val >= passline: print (

  • 简析Python的闭包和装饰器

    什么是装饰器? 装饰器(Decorator)相对简单,咱们先介绍它:"装饰器的功能是将被装饰的函数当作参数传递给与装饰器对应的函数(名称相同的函数),并返回包装后的被装饰的函数",听起来有点绕,没关系,直接看示意图,其中 a 为与装饰器 @a 对应的函数, b 为装饰器修饰的函数,装饰器@a的作用是: 简而言之:@a 就是将 b 传递给 a(),并返回新的 b = a(b) 栗子: 上面使用@dobi来表示装饰器,其等同于:qinfeng = dobi(qinfeng) 因此装饰器本质

  • Python闭包和装饰器用法实例详解

    本文实例讲述了Python闭包和装饰器用法.分享给大家供大家参考,具体如下: Python的装饰器的英文名叫Decorator,作用是完成对一些模块的修饰.所谓修饰工作就是想给现有的模块加上一些小装饰(一些小功能,这些小功能可能好多模块都会用到),但又不让这个小装饰(小功能)侵入到原有的模块中的代码里去. 闭包 1.函数引用 #coding=utf-8 def test1(): print('This is test1!') #调用函数 test1() #引用函数 ret = test1 #打印

  • 详解python中的生成器、迭代器、闭包、装饰器

    迭代是访问集合元素的一种方式.迭代器是一个可以记住遍历的位置的对象.迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前不会后退. 1|1可迭代对象 以直接作用于 for 循环的数据类型有以下几种: 一类是集合数据类型,如 list . tuple . dict . set . str 等: 一类是 generator ,包括生成器和带 yield 的generator function. 这些可以直接作用于 for 循环的对象统称为可迭代对象: Iterable .

  • Python高级特性之闭包与装饰器实例详解

    本文实例讲述了Python高级特性之闭包与装饰器.分享给大家供大家参考,具体如下: 闭包 1.函数参数: (1)函数名存放的是函数的地址 (2)函数名()存放的是函数内的代码 (3)函数名只是函数代码空间的引用,当函数名赋值给一个对象的时候,就是引用传递 def func01(): print("func01 is show") test = func01 print(func01) print(test) test() 结果: 2.闭包: (1)内层函数可以访问外层函数变量 (2)闭

  • python高级语法之闭包和装饰器详解

    一.闭包 闭包的形成条件: 1.函数嵌套. 2.内部函数使用了外部函数的变量或者参数. 3.外部函数返回了使用外 部变量的内部函数. 二.一个简单的例子 def func_out(num1): def inner(num2): res = num1 + num2 print(res) return inner # a = func_out(10)(10) a = func_out(10) a(10) 闭包修改外部函数的变量: 在闭包内修改外部函数的变量需要使用nonlocal关键字 def fu

  • Python必备基础之闭包和装饰器知识总结

    一.闭包 1.1 三要素 必须有一个内嵌函数 内嵌函数必须引用外部函数中变量 外部函数返回值必须是内嵌函数 1.2 语法 # 语法 def 外部函数名(参数): 外部变量 def 内部函数名(参数): 使用外部变量 return 内部函数名 # 调用 变量 = 外部函数名(参数) 变量(参数) 举个例子 def func01(): # 外部函数 a = 1 # 外部变量 print('外部变量:',a) def func02(num): #内部函数 print("调用内部函数后:",n

随机推荐