python函数式编程之functools、itertools、operator详解

写在篇前

  这篇博客主要介绍什么是函数式编程、在Python中怎么进行函数式编程。函数式编程(Functional Programming)是一种编程范式,它将计算机运算视为函数运算,并且避免使用程序状态以及mutable对象。因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

  函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!Python对函数式编程提供部分支持, 包括标准库itertoolsoperatorfunctools,下面将会一一介绍。由于Python允许使用变量,因此,Python不是纯函数式编程语言。本文代码均放在github 仓库

itertools

​ 受APL,Haskell和SML的启发,本模块实现一系列 iterator ,用于快速创建高效率的迭代器。本模块中包含的迭代器创建函数可分为以下三个部分:

无穷迭代器

无穷迭代意味着可以迭代无穷次而不会抛出StopIteration异常,在程序中需要设置一定条件主动停止迭代以避免无限循环。

迭代器实参结果示例
count()start, [step]start, start+step, start+2*step, …count(10) --> 10 11 12 13 14 ...
cycle()pp0, p1, … plast, p0, p1, …cycle('ABCD') --> A B C D A B C D ...
repeat()elem [,n]elem, elem, elem, … 重复无限次或n次repeat(10, 3) --> 10 10 10
  • 这些函数中最需要注意的是cycle(),它会创建一个迭代器,返回iterable中所有元素并保存一个副本,因此该函数可能需要相当大的辅助空间。其实现大致相当于:

    def cycle(iterable):
        # cycle('ABCD') --> A B C D A B C D A B C D ...
        saved = []
        for element in iterable:
            yield element
            saved.append(element)
        while True:
            for element in saved:
                  yield element
    
  • repeat()函数比较常用方式是给mapzip提供常数:

    >>> list(map(pow, range(10), repeat(2)))
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    

最短停止迭代器

最短停止迭代器,即Iterators terminating on the shortest input sequence,迭代器迭代次数取决于传入的最短序列(下面的zip_longest()除外)。这一类迭代器函数一共有12个,下面我们挑选几个具体举例:

  • accumulate(iterable, func=None)函数接受一个可迭代对象以及一个callable对象为参数,返回序列累积(如数学累加、累乘)结果或则最大值最小值,总之只要func接受两个参数进行允许的操作即可,其实现大致等同如下:

    import operator
    def accumulate(iterable, func=operator.add):
        'Return running totals'
        # accumulate([1,2,3,4,5]) --> 1 3 6 10 15
        # accumulate([1,2,3,4,5], operator.mul) --> 1 2 6 24 120
        it = iter(iterable)
        try:
            total = next(it)
        except StopIteration:
            return
        yield total
        for element in it:
            total = func(total, element)
            yield total
    

    默认实现一个累加的效果,我们也可以实现一个累乘的效果:

    >>> import operator
    >>> list(accumulate([1, 2, 3, 4, 5], func=operator.mul))
    [1, 2, 6, 24, 120]
    >>> list(accumulate([1, 2, 3, 4, 5], func=lambda x, y: x*y))
    [1, 2, 6, 24, 120]
    
  • dropwhile()takewhile()filterfalse()这三个函数比较相似,均是按一定条件迭代iterable中的元素,下面给出一个例子说明他们的区别:

    # -----------------------dropwhile---------------------------------
    
    print(list(dropwhile(lambda x: x < 3, [1, 2, 3, 4, 5])))  # starting when pred fails
    
    # -----------------------filterfalse------------------------------
    
    print(list(filterfalse(lambda x: x < 3, [1, 2, 3, 4, 5])))  # get elements of seq where pred(elem) is false
    
    # -----------------------takewhile--------------------------------
    
    print(list(takewhile(lambda x: x < 3, [1, 2, 3, 4, 5])))  # seq[0], seq[1], until pred fails
    
    [1, 2, 4]
    [3, 4, 5]
    [3, 4, 5]
    
  • groupby()是对iterable进行自定义分组的一个迭代器创建函数,其一般与sorted()函数配合使用,达到最佳的分组效果,如下面例子对水果进行分类,原谅我才用了比较奇怪的分组方式,我将英文单词长度一样的水果分为一组:

    def key_func(x):
        return len(x)
    
    
    fruits = ['apple', 'banana', 'grape', 'pear', 'chestnut', 'orange']
    fruits = sorted(fruits, key=key_func)  # 可以注释这句话查看不一样的效果
    group = groupby(fruits, key=key_func)
    
    for k, v in group:
        print('{0}:{1}'.format(k, list(v)))
    
    4:['pear']
    5:['apple', 'grape']
    6:['banana', 'orange']
    8:['chestnut']
    
  • zip_longest()这是一个比较特殊的函数,与zip()函数相比,前者的迭代次数是取决于更长的输入序列,并未短序列填充一个固定值:

    for x, y in zip_longest([1, 1, 1, 1], [2, 2, 2], fillvalue=None):  # 于zip对比
        print('(%s, %s)' % (x, y), end='\t')
        
    # output
    (1, 2)	(1, 2)	(1, 2)	(1, None)
    

所有迭代器列表如下,其使用方法其实都是同一套路:

teratorArgumentsResultsExample
accumulate()p [,func]p0, p0+p1, p0+p1+p2, …accumulate([1,2,3,4,5]) --> 1 3 6 10 15
chain()p, q, …p0, p1, … plast, q0, q1, …chain('ABC', 'DEF') --> A B C D E F
chain.from_iterable()iterablep0, p1, … plast, q0, q1, …chain.from_iterable(['ABC', 'DEF']) --> A B C D E F
compress()data, selectors(d[0] if s[0]), (d[1] if s[1]), …compress('ABCDEF', [1,0,1,0,1,1]) --> A C E F
dropwhile()pred, seqseq[n], seq[n+1], starting when pred failsdropwhile(lambda x: x<5, [1,4,6,4,1]) --> 6 4 1
filterfalse()pred, seqelements of seq where pred(elem) is falsefilterfalse(lambda x: x%2, range(10)) --> 0 2 4 6 8
groupby()iterable[, key]sub-iterators grouped by value of key(v)
islice()seq, [start,] stop [, step]elements from seq[start:stop:step]islice('ABCDEFG', 2, None) --> C D E F G
starmap()func, seqfunc(*seq[0]), func(*seq[1]), …starmap(pow, [(2,5), (3,2), (10,3)]) --> 32 9 1000
takewhile()pred, seqseq[0], seq[1], until pred failstakewhile(lambda x: x<5, [1,4,6,4,1]) --> 1 4
tee()it, nit1, it2, … itn splits one iterator into n
zip_longest()p, q, …(p[0], q[0]), (p[1], q[1]), …zip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D-

排列组合迭代器

该类函数创建的迭代器和排列组合有关,如product()是进行笛卡尔积运算;permutations()即数学中的排列;combinations()即数学中的组合;combinations_with_replacement()即又放回的组合。

IteratorArgumentsResults
product()p, q, … [repeat=1]cartesian product, equivalent to a nested for-loop
permutations()p[, r]r-length tuples, all possible orderings, no repeated elements
combinations()p, rr-length tuples, in sorted order, no repeated elements
combinations_with_replacement()p, rr-length tuples, in sorted order, with repeated elements

下面给出几个简单的例子,其中需要说明的是permcomb是scipy中实现排列组合的函数,返回排列组合的组合个数。

from itertools import product, permutations, combinations, combinations_with_replacement
from scipy.special import comb, perm

# -----------------------product-----------------------------
print(list(product([1, 2, 3, 4], [1, 2], repeat=1)))  # *iterables, 他们之间进行笛卡尔积

# -----------------------permutations-------------------------
per = list(permutations([1, 2, 3, 4], r=2))  # 有序
assert len(per) == perm(4, 2)  # A^2_4, 排列组合问题
print(per)

# -----------------------combinations-------------------------

per = list(combinations([1, 2, 3, 4], r=2))  # 无放回抽样
assert len(per) == comb(4, 2)  # C^2_4
print(per)

# -----------------------combinations-------------------------

per = list(combinations_with_replacement([1, 2, 3, 4], r=2))  # 有放回抽样
assert len(per) == comb(4, 2, repetition=True)  # 10
print(per)

​ 虽然itertools模块一下子提供了这么多cool的迭代器生成函数,但是更多灵活的应用还需要多思考、多查阅文档。另外,在python中谈到迭代器,不得不谈到列表生成式、生成器、可迭代对象等,在这篇文章不准备细谈。如有需要,请参考我之前的博客Python面向对象、魔法方法最后一个部分。另外,限于篇幅原因,更多例子请参考我放在github仓库中的代码,我对每一个迭代器都给出了详细例子。

operator

基本运算符函数

​ 上面也用到了operator模块,该模块提供了一套与Python的内置运算符对应的高效率函数,包括:对象的比较运算、逻辑运算、数学运算以及序列运算。该模块一共定义了54个(python 3.7.2)运算符函数,可以通过operator.__all__查看,比较常用的一些如下列表所示:

运算语法函数
加法a + badd(a, b)
字符串拼接seq1 + seq2concat(seq1, seq2)
包含测试obj in seqcontains(seq, obj)
除法a / btruediv(a, b)
除法a // bfloordiv(a, b)
按位与a & band_(a, b)
按位异或a ^ bxor(a, b)
按位取反~ ainvert(a)
按位或a | bor_(a, b)
取幂a ** bpow(a, b)
一致a is bis_(a, b)
一致a is not bis_not(a, b)
索引赋值obj[k] = vsetitem(obj, k, v)
索引删除del obj[k]delitem(obj, k)
索引取值obj[k]getitem(obj, k)
左移a << blshift(a, b)
取模a % bmod(a, b)
乘法a * bmul(a, b)
矩阵乘法a @ bmatmul(a, b)
否定(算术)- aneg(a)
否定(逻辑)not anot_(a)
正数+ apos(a)
右移a >> brshift(a, b)
切片赋值seq[i:j] = valuessetitem(seq, slice(i, j), values)
切片删除del seq[i:j]delitem(seq, slice(i, j))
切片取值seq[i:j]getitem(seq, slice(i, j))
字符串格式化s % objmod(s, obj)
减法a - bsub(a, b)
真值测试objtruth(obj)
比较a < blt(a, b)
比较a <= ble(a, b)
相等a == beq(a, b)
不等a != bne(a, b)
比较a >= bge(a, b)
比较a > bgt(a, b)

这里重点提一下setitem()delitem()getitem()三个函数,能方便的对序列进行操作,并且其效果是in-place的,下面会继续讲到in-place,请注意关注!

>>> from operator import setitem, delitem, getitem
>>> list_a = [1,2,3,4,5,6]
>>> getitem(list_a, 0)
Out[4]: 1
>>> getitem(list_a, slice(1, 5))
Out[7]: [2, 3, 4, 5]

>>> delitem(list_a, slice(1, 2))
>>> list_a
Out[9]: [1, 3, 4, 5, 6]

>>> setitem(list_a, 1, 2)
>>> list_a
Out[11]: [1, 2, 4, 5, 6]

看到这里可能你会想,So, 有什么用呢? 我为什么不直接用运算符呢?operator库提供的函数一来是方便将运算符作为函数快捷的传入,二是效率一般也更高:

import operator
from itertools import accumulate
from time import time

start = time()
list(accumulate(range(100000000), func=operator.mul))
print('operator:', time()-start)
start = time()
list(accumulate(range(100000000), func=lambda x, y: x*y))
print('lambda', time()-start)

# Output
operator: 6.269657135009766
lambda 9.311992883682251

operator模块中运算符函数部分还提供了In-place版本,当调用inplace版本时,计算和赋值会分开进行。对于不可变对象附录1,如字符串、数字、元祖,值更新操作会进行,但是赋值操作会略过:

>>> a = 'hello'
>>> iadd(a, ' world')
'hello world'
>>> a
'hello'

而对于可变对象附录1,如列表、字典,inplace method会依次进行计算、赋值操作:

>>> a = [1,2,3]

>>> add(a, [4,5,6])
[1, 2, 3, 4, 5, 6]
>>> a
[1, 2, 3]

>>> iadd(a, [4,5,6])
[1, 2, 3, 4, 5, 6]
>>> a
[1, 2, 3, 4, 5, 6]

属性查询

operator块还定义了用于通用属性查找的接口。

  • attrgetter(*attrs)

    attrgetter函数常用于mapsorted等接受一个函数作为参数的函数,用于获取对象的属性,该函数返回一个函数g(obj)。其实现其实很简单,就是利用getattr函数以及闭包附录2的特性获取对象的属性,其实现等同如下代码:

    def attrgetter(*items):
        if any(not isinstance(item, str) for item in items):
            raise TypeError('attribute name must be a string')
        if len(items) == 1:
            attr = items[0]
            def g(obj):
                return resolve_attr(obj, attr)
        else:
            def g(obj):
                return tuple(resolve_attr(obj, attr) for attr in items)
        return g
    
    def resolve_attr(obj, attr):
        for name in attr.split("."): 
            obj = getattr(obj, name)
        return obj
    

    其效果是,如果指定f = attrgetter('name'),则f(obj)返回obj.name;如果f = attrgetter('name', 'age'),则f(obj)返回(obj.name, obj.age);如果f = attrgetter('name.first', 'name.last'),则f(b)返回(b.name.first, b.name.last)。举个例子,对学生按年龄进行排序,将attrgetter作为sorted函数的key函数。

    from operator import attrgetter
    
    
    class Student:
        def __init__(self, name, grade, age):
            self.name = name
            self.grade = grade
            self.age = age
    
        def __str__(self):
            return self.name
    
        __repr__ = __str__
    
    
    students = [
        Student('jane', 96, 12),
        Student('john', 95, 12),
        Student('dave', 98, 10),
    ]
    
    print(sorted(students, key=attrgetter('age')))
    print(sorted(students, key=lambda o: o.age))
    print(sorted(students, key=attrgetter('age', 'grade'), reverse=True))
    
  • itemgetter(*items)

    itemgetter()函数的实现类似于attrgetter(),调用该函数返回一个函数,用于获取序列数据。当然,这里说获取序列数据也许并不严谨,应该说可以获取任何实现了__getitem__魔法方法的类对象元素信息。其实现等同于:

    def itemgetter(*items):
        if len(items) == 1:
            item = items[0]
            def g(obj):
                return obj[item]
        else:
            def g(obj):
                return tuple(obj[item] for item in items)
        return g
    

    比如可用于字符串截取:

    >>> itemgetter(1)('ABCDEFG')
    'B'
    >>> itemgetter(1,3,5)('ABCDEFG')
    ('B', 'D', 'F')
    >>> itemgetter(slice(2,None))('ABCDEFG')
    'CDEFG'
    
  • methodcaller(name[, args...])

    ​ 毫无疑问,methodcaller()的实现方式也是类似的,用于配合接受一个函数作为参数的函数调用对象的方法,其内部实现等同于如下代码:

    def methodcaller(name, *args, **kwargs):
        def caller(obj):
            return getattr(obj, name)(*args, **kwargs)
        return caller
    

    ​ 如果指定f = methodcaller('name'), 则 f(b) 返回 b.name()函数的调用结果;如果指定f = methodcaller('name', 'foo', bar=1), 则 f(b) returns b.name('foo', bar=1)的调用结果。

functools

functools模块设计了一系列应用于高阶函数的接口,可以通过functools.__all__查看所有可用函数,包括以下:

>>> import functools
>>> functools.__all__
Out[3]: 
['update_wrapper',
 'wraps',
 'WRAPPER_ASSIGNMENTS',
 'WRAPPER_UPDATES',
 'total_ordering',
 'cmp_to_key',
 'lru_cache',
 'reduce',
 'partial',
 'partialmethod',
 'singledispatch']

reduce被网友笑成为被放逐到了functools模块,因为python语言创始人Guido不喜欢函数式编程,于是把reduce移出了builtin模块,而map函数则在社区的强烈要求下得以在builtin立足。另外,lambdafilter也是python函数式编程中常用的builtin函数,这四个函数我在python函数详解曾介绍过,这里不再赘述;我曾在python装饰器详细剖析中简单剖析过update_wrapperwrapsWRAPPER_ASSIGNMENTSWRAPPER_UPDATES的源码,这里也不再赘述。

partial & partialmethod

partial,俗称偏函数,在python函数详解中曾介绍过,其作用是为函数某些参数设置默认值,简化函数signature,其实现大致等同于下面的代码,由此我们可以partial object有三个属性,分别是partial_obj.funcpartial_obj.argspartial_obj.keywords,而并没有自动创建其他一般函数拥有的属性,如__name____doc__

def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*args, *fargs, **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

比如,我们可以使用偏函数创建一个以2为base的int函数basetwo:

>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18

partialmethod类(python3.7.2)是用描述器实现的,(关于描述器请参考我之前的博客python描述器深度解析),该描述器的行为类似于partial,只是它被设计为用作方法定义而不是可直接调用的。

举例:

>>> class Cell(object):
...     def __init__(self):
...         self._alive = False
...     @property
...     def alive(self):
...         return self._alive
...     def set_state(self, state):
...         self._alive = bool(state)
...     set_alive = partialmethod(set_state, True)
...     set_dead = partialmethod(set_state, False)
...
>>> c = Cell()
>>> c.alive
False
>>> c.set_alive()
>>> c.alive
True

cmp_to_key

​ 将comparison function(比较函数)转化为 key function。python中的key function包括sorted(), min(), max(), heapq.nlargest(), itertools.groupby()等等,它们的共同点是至少接受一个序列和一个key function,key function为序列元素生成一个可用作排序的值;而comparison function接受两个参数,并比较这两个参数,根据他们的大小关系返回负值、零或正值。

sorted(iterable, key=cmp_to_key(locale.strcoll))

@total_ordering

​ 在Python面向对象、魔法方法曾说到python有一类对象比较的魔法方法,实现这些方法可以对对象进行比较大小。包括__gt__()__lt__()__eq__()__ne__()__ge__()。根据数学推理,只要我定义了__gt__()__lt__()__ne__()__ge__()中的一个以及定义了__eq__(),其比较大小的所有规则应该是确定的,可是我还是要一一实现剩余三个方法,好累啊。在python3.2新增的total_ordering装饰器则是用来简化该问题,如下官方例子:

@total_ordering
class Student:
    def _is_valid_operand(self, other):
        return (hasattr(other, "lastname") and
                hasattr(other, "firstname"))
    def __eq__(self, other):
        if not self._is_valid_operand(other):
            return NotImplemented
        return ((self.lastname.lower(), self.firstname.lower()) ==
                (other.lastname.lower(), other.firstname.lower()))
    def __lt__(self, other):
        if not self._is_valid_operand(other):
            return NotImplemented
        return ((self.lastname.lower(), self.firstname.lower()) <
                (other.lastname.lower(), other.firstname.lower()))

官方文档特别提示,这可能带来执行效率的低下,如果性能分析发现这会是瓶颈时,不妨自己手动实现吧,哈哈!

@lru_cache

​ 学过操作系统的同学对LRU应该不陌生,就是其中一种经典的页面置换算法,另外还有FIFO、LFU等等。 该函数主要是用来做缓存,把相对耗时的函数结果进行保存,避免传入相同的参数重复计算。同时,缓存并不会无限增长,不用的缓存会被释放。

import functools


@functools.lru_cache(maxsize=20, typed=False)
def add(x, y):
    print(f'[Log]-invoke function now, with params x={x}, y={y}')
    return x + y


print(add(5, 6))
print(add(4, 7))
print(add(5, 6))
print(add(5, 6.0))

[Log]-invoke function now, with params x=5, y=6
11
[Log]-invoke function now, with params x=4, y=7
11
11
[Log]-invoke function now, with params x=5, y=6.0
11.0

​ 上面的例子可以看到,两次调用add(5, 6)实际只发生了一次调用,因为结果已经缓存,无需再次计算。但是add(5, 6.0)却再次调用了,因为我们指定了参数typed=False。也可以看看官方文档给的斐波那契函数的高效实现,常规递归方式会导致很多重复计算,效率低下,缓存已计算可大大提高地柜效率。

@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

>>> [fib(n) for n in range(16)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]

>>> fib.cache_info()
CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)

@singledispatch

​ 这是PEP443新提出的一个单参数泛型函数装饰器,只作用于函数的第一个参数,举例:

>>> from functools import singledispatch
>>> @singledispatch
... def fun(arg, verbose=False):
...     if verbose:
...         print("Let me just say,", end=" ")
...     print(arg)

​ 要将重载的实现添加到函数中,可以使用泛型函数的register()属性。它是一个装饰。对于带有类型注释的函数,装饰器将自动推断第一个参数的类型:

>>> @fun.register
... def _(arg: int, verbose=False):
...     if verbose:
...         print("Strength in numbers, eh?", end=" ")
...     print(arg)
...
>>> @fun.register
... def _(arg: list, verbose=False):
...     if verbose:
...         print("Enumerate this:")
...     for i, elem in enumerate(arg):
...         print(i, elem)

​ 对于不使用类型注释的代码,可以将类型参数显式传递给装饰器本身:

>>> @fun.register(complex)
... def _(arg, verbose=False):
...     if verbose:
...         print("Better than complicated.", end=" ")
...     print(arg.real, arg.imag)
...

​ 为了能够register lambda匿名函数和预先存在的函数,可以采用如下方式定义:

>>> def nothing(arg, verbose=False):
...     print("Nothing.")
...
>>> fun.register(type(None), nothing)

​ 调用时,泛型函数根据第一个参数的类型分派到不同的函数,如果传入了一个没有注册的类型,则调用被@singledispatch装饰的那个函数:

>>> fun("Hello, world.")
Hello, world.
>>> fun("test.", verbose=True)
Let me just say, test.
>>> fun(42, verbose=True)
Strength in numbers, eh? 42
>>> fun(['spam', 'spam', 'eggs', 'spam'], verbose=True)
Enumerate this:
0 spam
1 spam
2 eggs
3 spam
>>> fun(None)
Nothing.
>>> fun(1.23)
0.615

​ 可以通过下面的方式check,给定类型会调用哪个函数:

>>> fun.dispatch(float)
<function fun_num at 0x1035a2840>
>>> fun.dispatch(dict)    # note: default implementation
<function fun at 0x103fe0000>

​ 可以通过只读属性registry查看已经实现的函数:

>>> fun.registry.keys()
dict_keys([<class 'NoneType'>, <class 'int'>, <class 'object'>,
          <class 'decimal.Decimal'>, <class 'list'>,
          <class 'float'>])
>>> fun.registry[float]
<function fun_num at 0x1035a2840>
>>> fun.registry[object]
<function fun at 0x103fe0000>

附录

可变对象 & 不可变对象

​ 一般在函数传参的时候我们才需要考虑一个对象是可变(mutable)还是不可变(immutable)对象,因为这往往会决定,我们的函数有没有副作用(side effects)。我们先来举个例子,该例中lina指向同一个object,因为参数传入参数a是一个可变对象,因此在函数中对它的修改是in-place的,即alin同时被修改了(但是要理解到,实际只发生了一次修改,因为lina指向同一个object)。

# 当参数是可变对象时
>>> def list_add3(lin):
    lin += [3]
    return lin

>>> a = [1, 2, 3]
>>> b = list_add3(a)
>>> b
[1, 2, 3, 3]
>>> a
[1, 2, 3, 3]

​ 但是,在函数中修改不可变对象时,是在内存中开辟一片新空间保存修改后的结果,即a指向原来的object,tin则指向修改后的object。

>>> def tuple_add3(tin):
    tin += (3,)
    return tin

>>> a = (1, 2, 3)
>>> b = tuple_add3(a)
>>> b
(1, 2, 3, 3)
>>> a
(1, 2, 3)

​ 在python中呢,不可变对象包括integers, floats, complex, strings, bytes, tuples, ranges 和 frozensets;可变对象包括lists, dictionaries, bytearrays 和 sets。

闭包

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

​ 我们知道,python函数是第一类对象,它可以作为参数值传递给函数,也可以作为函数的返回值返回。在上面attrgetter()函数的实现中g就是一个闭包,它包含了外层函数的变量attritems

def attrgetter(*items):  # enclosing function
    if any(not isinstance(item, str) for item in items):
        raise TypeError('attribute name must be a string')
    if len(items) == 1:
        attr = items[0]
        def g(obj): # nested function or enclosed function
            return resolve_attr(obj, attr)
    else:
        def g(obj):
            return tuple(resolve_attr(obj, attr) for attr in items)
    return g

>>> f = attrgetter('name')
>>> f.__closure__
Out[19]: (<cell at 0x11a131bb8: str object at 0x10d201538>,)
>>> f.__closure__[0]
Out[20]: <cell at 0x11a131bb8: str object at 0x10d201538>
>>> f.__closure__[0].cell_contents
Out[21]: 'name'

关于python函数和第一类对象更多的理解,可以参考我之前的博客python函数详解python meataclass详解;关于闭包更通俗的讲解可以参考python closures

reference

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值