functools模块

1 functools函数

functools模块用于高阶函数:作用与或者返回其它函数的函数。一般来说,对于该模块,任何可调用对象都可以视为一个函数。

functools模块定义了以下函数:

1.1 functools.cmp_to_key(func)

版本3.2中新增。

将旧风格的比较函数转换为key函数。用于接收key函数的工具(例如sorted()min()max()heapq.nlargest()heapq.nsmallest()itertools.groupby())。该函数主要用作支持比较函数的Python 2转换工具。

比较函数可以是任何可调用的对象,接收两个参数,比较它们,如果小于返回负数,相等返回0,大于返回正数。key函数是一个可调用对象,接收一个参数,并返回另一个值用于排序的键。

示例:

sorted(iterable, key=cmp_to_key(locale.strcoll)) # locale-aware sort order

1.2 @functools.lru_cache(maxsize=128, typed=False)

版本3.2中新增。
版本3.3中修改:增加可选参数typed参数。

装饰器用一个有记忆的调用包装一个函数,它可以保存最近maxsize次调用。当使用同样的参数定期调用费时或I/O绑定的函数时,它可以节省时间。

因为使用字典缓存结果,所以函数的位置和关键字参数必须是hashable

如果maxsize设置为None,则禁用LRU功能,并且缓存可以无限增长。当maxsize设置为$ 2^n $时,性能最佳。

如果typed设置为真,则不同类型的函数参数会分别缓存。例如,f(3)f(3.0)将视为不同结果的不同调用。

为了帮助测量缓存的有效性并调整maxsize参数,包装函数使用cache_info()函数返回一个命名元组,包括hitsmissesmaxsizecurrsize。在多线程环境中,hitsmisses是近似值。

装饰器还提供了cache_clear()函数用于清除缓存,或者让缓存失效。

原始的底层函数通过wrapped属性访问。这对于内省,绕过缓存,或者重新装饰函数很有用。

当最近调用是即将调用的最佳调用因子时(例如,新闻服务器上的最受欢迎文章常常每天改变),LRU(least recently used)缓存效果最好。缓存的大小限制确保缓存不会在长时间运行的进程(如web服务器)上不受限制的增长。

用于静态Web内容的LRU缓存示例:

@lru_cache(maxsize=32)
def get_pep(num):
    'Retrieve text of a Python Enhancement Proposal'
    resource = 'http://www.python.org/dev/peps/pep-%04d/' % num
    try:
        with urllib.request.urlopen(resource) as s:
            return s.read()
    except urllib.error.HTTPError:
        return 'Not Found'
        
>>> for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991:
...     pep = get_pep(n)
...     print(n, len(pep))

>>> get_pep.cache_info()
CacheInfo(hits=3, misses=8, maxsize=32, currsize=8)

使用缓存实现动态编程技术高效计算斐波那契数列的示例:

@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)

1.3 @functools.total_ordering

版本3.2中新增。
版本3.4中修改:如果是不能识别的类型,现在支持从底层比较函数返回NotImplemented。

给定的一个类定义了一个或多个富比较方法,该类装饰器提供剩下的。这简化了指定所有富比较操作的工作量。

类必须定义__lt__()__le__()__gt__()__ge__()的其中一个。此外,类应该提供一个__eq__()方法。

示例:

@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.lowher(), 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()))

注意:虽然该装饰器能很容易的创建行为良好的完全有序类型,但会导致衍生出的比较函数执行的更慢,以及更复杂的堆栈跟踪。如果性能基准测试表明这是程序的瓶颈,则实现所有六个富比较函数可能会是提高速度的方式。

1.4 functools.partial(func, *args, **keywords)

返回一个新的partial对象,该对象被调用时,类似使用位置参数args和关键字参数keywords调用func。如果调用时提供了更多参数,它们会被添加到args。如果提供了额外的关键字参数,它们会扩展和覆盖keywords。大致等价于:

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

partial()用于冻结函数的某些参数和/或关键字参数,生成一个简化的签名对象。例如,用于创建一个类似int()函数的可调用对象,其中base参数默认为2:

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

1.5 类方法 functools.partialmethod(func, *args, **keywords)

版本3.4中新增。

返回一个行为类似partial的新partialmethod描述符,除了它是用于方法定义,而不是直接调用。

func必须是一个descriptor或者可调用对象(两个对象都像常规函数一样作为descriptor)。

func是一个descriptor(比如普遍的Python函数,classmethod()staticmethod()abstractmethod(),或者其它partialmethod实例时,get的调用会委托给底层的descriptor,并返回一个适当的partial对象。

func不是可调用的descriptor时,会动态创建一个适当的绑定方法。用于方法时,该行为类似普通的Python函数:self参数会插入为第一个位置参数,甚至在传递给partialmethod构造器的argskeywords之前。

示例:

>>> 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    

1.6 functools.reduce(function, iterable[, initializer])

将两个参数的function从左至右依次作用于序列中的项,减少序列为单个值。例如,reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])会计算((((1+2)+3)+4)+5)。左边的参数x是计算出的值,右边的参数y是从序列中更新的值。如果提供了可选参数initializer,在计算中,它会放在序列项之前,当序列为空时,提供一个默认值。如果没指定initializer,并且序列只有包含一项,会返回第一项。

大致等价于:

def reduce(function, iterable, initializer=None):
    it = iter(iterable)
    if initializer is None:
        value = next(it)
    else:
        value = initializer
    for element in it:
        value = function(value, element)
    return value

1.7 @functools.singledispatch(default)

版本3.4中新增。

将函数转换为single-dispatch generic函数。

使用@singledispatch装饰器定义generic函数。注意,dispatch发生在第一个参数的类型上,相应的创建函数:

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

使用generic函数的register()属性添加函数的重载实现。这是一个装饰器,接受一个类型参数,并装饰实现该类型操作的函数:

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

为了能够注册lambda表达式和预先存在的函数,register()可以用于函数形式:

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

register()属性返回未装饰的函数,可以使用装饰堆叠,pickling,以及为每个变体单独创建单元测试:

>>> @fun.register(float)
... @fun.register(Decimal)
... def fun_num(arg, verbose=False):
...     if verbose:
...         print('Half of your number:', end=' ')
...     print(arg / 2)
...
>>> fun_num is fun
False

调用时,generic函数根据第一个参数的类型dispatch

>>> 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

当没有注册特定类型的实现时,其方法解析顺序用于查找更通用的实现。用@singledispatch装饰的原始函数是为object类型注册的,如果没有找到更好的实现,则使用它。

使用dispatch()属性查看generic函数为指定类型选择哪个实现:

>>> fun.dispatch(float)
<function fun_num at 0x1035a2840>
>>> fun.dispatch(dict)
<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>

1.8 functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

版本3.2中新增:自动添加__wrapped__属性。
版本3.2中新增:默认拷贝__annotations__属性。
版本3.2中修改:缺少属性不再触发AttributeError。
版本3.4中修改:现在__wrapped__属性总是引用wrapped函数,即使该属性定义了__wrapped__属性。

更新一个wrapper函数,让它看起来像一个wrapped函数。可选参数是元组,用来指定原函数的哪些属性直接分配给wrapper函数的匹配属性,wrapper函数的哪些属性从原函数的相应属性更新。这些参数的默认值是模块级别的常量WRAPPER_ASSIGNMENTS(分配wrapper函数的__module____name____qualname____annotations__和文档字符串__doc__)和WRAPPER_UPDATES(哪些更新wrapper函数的__dict__,比如实例的字典)。

为了内省和其它目的(比如绕过缓存装饰器lru_cache())允许访问原始函数,该函数自动在wrapper函数添加一个__wrapped__属性,应用wrapped函数。

该函数的主要用途是,在装饰器函数中包装被装饰的函数,并返回wrapper。如果wrapper函数没有更新,返回函数的元数据将反射wrapper的定义,而不是原始函数的定义,原始函数的定义通常没有意义。

除了函数,update_wrapper()可以与其它可调用对象一起使用。出现在assignedupdated参数中,而不在被包装对象中的任何属性都会被忽略(比如,该函数不会试图在wrapper函数中设置它们)。如果wrapper函数本身缺少updated中的属性,仍然会抛出AttributeError

1.9 @functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

这是一个便捷函数,定义一个wrapper函数时,作为函数装饰器调用update_wrapper()。它等价于partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)。例如:

>>> from functools import wraps
>>> def my_decorator(f):
...     @wraps(f)
...     def wrapper(*args, **kwds):
...         print('Calling decorated function')
...         return f(*args, **kwds)
...     return wrapper
...
>>> @my_decorator
... def example():
...     """Docstring"""
...     print('Called example function')
...
>>> example()
Calling decorated function
Called example function
>>> example.__name__
'example'
>>> example.__doc__
'Docstring'

没有使用这个装饰工厂,example函数的名称将是'wrapper',并且原始example()docstring将会丢失。

2 partial对象

partial()创建可调用的partial对象。它们有三个只读属性:

2.1 partial.func

一个可调用的对象或函数。调用partial对象会转为使用新的参数和关键字参数调用func

2.2 partial.args

最左边的位置参数会优先作为位置参数提供给partial对象调用。

2.3 partial.keywords

partial对象被调用时提供关键字参数。

partial对象与函数对象类似,它们可以被调用,有弱引用,并且可以有属性。但有一些重要的区别。对于实例,__name____doc__属性不会自动创建。同时,在类中定义的partial对象的行为类似静态方法,在实例属性查找时,不会转换为绑定方法。

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

推荐阅读更多精彩内容

  • functools.partial functools.partial 通过包装手法,允许我们 "重新定义" 函数...
    笨手笨脚越阅读 2,111评论 1 2
  • functools模块用于高级函数:作用于或返回其他函数的函数,一般来说,任何可调用对象都可以作为这个模块的用途来...
    第八共同体阅读 2,987评论 0 2
  • Python进阶框架 希望大家喜欢,点赞哦首先感谢廖雪峰老师对于该课程的讲解 一、函数式编程 1.1 函数式编程简...
    Gaolex阅读 5,489评论 6 53
  • 怎样用这样的字眼来命名 源于我喜欢的 开始遇到他 以为世界变的春暖花开 可是 与其不然 没想到 在我疯狂爱的自拔的...
    薄荷糖么么茶阅读 597评论 0 50
  • 我爸爸这么年轻的时候 当他这么年轻的时候我可能更小 不过我能找到我最小的时候的照片只有这一张了 就在今天我见到了或...
    杨康燚阅读 138评论 0 0