average在python中的格式_average在python中的格式


本文分为如下几个部分

  • 什么是闭包
  • 闭包与装饰器
  • 闭包等价——偏函数
  • 闭包等价——类
  • 闭包等价——其他
  • 闭包用于捕获状态值
  • 闭包等价——协程
  • 三种方法实现动态均值

什么是闭包

闭包是携带着一些自由变量的函数。

我们直接来看一个例子


def fun_out(a):
    def fun_in(b):
        return a + b
    return fun_in
fun1 = fun_out(1)


其中fun1函数是一个闭包,它携带着fun_out中定义的a变量,值为1。运行程序的效果是这样的


>>> fun1 = fun_out(1)
>>> fun1(3)
4
>>> fun1(6)
7

>>> fun5 = fun_out(5)
>>> fun5(3)
8
>>> fun5(6)
11


fun1fun5两个函数的定义相同,只是携带的自由变量不同,便成为了两个函数。由此闭包可以作为函数工厂,生产出功能类似,但是会有细微差别的函数。

闭包用起来非常直观,它反映了函数内调用一个变量的搜索路径,fun_in中调用某变量会

  • 先在fun_in函数定义的局部空间中开始搜索,可以找到b
  • 如果在局部空间中找不到,则在更上层的局部空间中找,可找到a
  • 之后在全局空间中搜索,即函数外定义的变量
  • 如果还是找不到会去找python内置变量
  • 如果还是找不到则抛出异常

闭包的特点就是,如果单独看fun_in函数的定义,不看周围环境,则a是未被定义的;而用这种方式返回则可以将a的值内置到函数中,这就是fun_infun1的区别。

我们可以查看闭包中绑定的自由变量


def fun_out(a):
    def fun_in(b):
        return a + b
    return fun_in

>>> fun1 = fun_out(1)
>>> fun1.__closure__
(<cell at 0x000001BC7FD3D768: int object at 0x0000000072C96C40>,)


另外,有两点需要注意。第一,下面这个fun_in不是闭包,因为其中没有调用外面的a值,调用__closure__会为None


def fun_out(a):
    def fun_in(b):
        return 1 + b
    return fun_in


第二,下面这种情况ac都会内置进fun_in中,其中c内置的值是3


def fun_out(a):
    c = 1
    def fun_in(b):
        return a + b + c
    c = 3
    return fun_in


第三,注意区分下面这种情况


def fun_out(a):
    return fun_in()

def fun_in():
    return a

fun_out(1)


如果fun_in不是定义在fun_out里面,则fun_in在寻找变量a时,不会找fun_out的局部空间,而是直接到全局去找。如果代码只是像上面这样定义,则调用fun_out会报错,因为fun_in找不到a这个变量;除非在全局定义一个a才能找到。

闭包与装饰器

装饰器是闭包的一个应用,只是携带的自由变量是一个函数


def print1(func):
    def wrapper(*args, **kw):
        print(1)
        return func(*args, **kw)
    return wrapper

@print1
def print2():
    print(2)

>>> print2()
1
2


@print1命令等价于print2 = print1(print2),所以装饰器是闭包的一种应用,自由变量是一个函数,传入和输出使用了相同的变量名。使用@print1的好处是使编程思路更加直观,定义好装饰器后,如果想对这个函数添加这个功能,就装饰上去,而不用重新思考print2传入print1是如何调用的、返回的是什么。

闭包等价——偏函数

还是以这个闭包为例


def fun_out(a):
    def fun_in(b):
        return a + b
    return fun_in
fun1 = fun_out(1)


下面使用偏函数实现


from functools import partial
def fun(a, b):
    return a + b

>>> fun1 = partial(fun, b=1)
>>> fun1(3)
4
>>> fun5 = partial(fun, b=5)
>>> fun5(3)
8


偏函数即固定某些参数的取值,达到函数工厂的作用。

但是这种方法不够灵活,比如下面一种情况


def fun_out(a):
    c = a ** 2
    def fun_in(b):
        return c + b
    return fun_in
fun2 = fun_out(2)


之后我们要反复使用fun2这个函数, c只有在定义fun2时计算过,不会在之后的步骤中重复计算,而使用偏函数就无法达到这种效果。这种效果下面的类也是可以达到的。

闭包等价——类

还是上面的例子,这里定义一个类来实现


class Add:
    def __init__(self, b):
        self.b = b

    def __call__(self, a):
        return a + self.b

>>> add1 = Add(1)
>>> add1(3)
4
>>> add5 = Add(5)
>>> add5(3)
8


类相比于普通函数的一个好处是,类可以将一些变量内置进去,让类中定义的函数随意调用和修改。类相比于闭包的好处在于,函数运行结束后,可以随意调用修改属性(自由变量);比如一个递归函数,想查看这个函数被调用了几次。

闭包等价——其他

1.如果只是使用普通的函数,传入两个参数也可以达到类似的效果


def fun(a, b):
    return a + b
fun(3, b=1)


但是调用时太麻烦了,应该不算实现了闭包的功能。

2.使用lambda表达式


def fun(a, b):
    return a + b

>>> fun1 = lambda a: fun(a, 1)
>>> fun1(3)
4
>>> fun5 = lambda a: fun(a, 5)
>>> fun5(3)
8


相比于partial来说,lambda表达式会略显臃肿。

闭包用于捕获状态值

除了函数工厂,闭包还可以用于将函数与它的状态绑定,方便输出函数的状态和函数运行结果,比如输出函数被调用的次数


def fun_out():
    count = 0
    def fun_in(something):
        nonlocal count
        count += 1
        print(count, something)
    return fun_in

>>> fun1 = fun_out()
>>> fun1('first')
1 first
>>> fun1('second')
2 second


这种应用可以改写成类的形式但是不能用partial等来改写。不过还有另一种改写方式——协程。

闭包等价——协程


def fun():
    count = 0
    while True:
        something = yield
        count += 1
        print(count, something)

>>> fun1 = fun()
>>> next(fun1)
>>> fun1.send('first')
1 first
>>> fun1.send('second')
2 second


yield不了解的读者可以参考这篇文章

三种方法实现动态均值

我们要实现一个函数达到下面的效果


def running_avg(number):
    pass

running_avg(10) # 10 --> 10 / 1
running_avg(20) # 15 --> (10 + 20) / 2
running_avg(30) # 20 --> (10 + 20 + 30) / 3


1.类实现


class Running_avg:
    def __init__(self):
        self.total = 0
        self.count = 0

    def __call__(self, number):
        self.total += number
        self.count += 1
        return self.total / self.count

>>> running_avg = Running_avg()
>>> running_avg(10)
10.0
>>> running_avg(20)
15.0
>>> running_avg(30)
20.0


2.闭包实现


def initial():
    total = 0
    count = 0
    def func(number):
        nonlocal total
        nonlocal count
        total += number
        count += 1
        return total / count
    return func

>>> running_avg = initial()
>>> running_avg(10)
10.0
>>> running_avg(20)
15.0
>>> running_avg(30)
20.0


3.协程实现


def Running_avg():
    total = 0
    count = 0
    average = None
    while True:
        number = yield average
        count += 1
        total += number
        average = total / count

>>> running_avg = Running_avg()
>>> next(running_avg)
>>> running_avg.send(10)
10.0
>>> running_avg.send(20)
15.0
>>> running_avg.send(30)
20.0


参考资料

下面是本文的参考资料

  • 这篇文章对什么是闭包解释的非常清楚
  • 闭包的应用和替代方案
  • 闭包捕获状态值
  • 动态均值参考fluent python一书第16章

专栏信息

专栏主页:python编程

专栏目录:目录

版本说明:软件及包版本说明