Maison >développement back-end >Tutoriel Python >Bibliothèque standard Python functools/itertools/operator
Introduction
functools
, itertools
, operator
sont les trois modules fournis par la bibliothèque standard Python pour prendre en charge la programmation fonctionnelle en utilisant. ces trois modules correctement, nous pouvons écrire du code Pythonique plus concis et lisible. Ensuite, nous utiliserons quelques exemples pour comprendre l'utilisation des trois modules.
functools est un module très important en Python, qui fournit des fonctions d'ordre élevé très utiles. Une fonction d'ordre supérieur est une fonction qui peut accepter une fonction comme paramètre ou utiliser une fonction comme valeur de retour. Les fonctions en Python étant également des objets, il est facile de prendre en charge de telles fonctionnalités.
>>> from functools import partial >>> basetwo = partial(int, base=2) >>> basetwo('10010') 18
basetwo('10010')
équivaut en fait à appeler int('10010', base=2)
Lorsque la fonction a trop de paramètres, vous pouvez en créer une nouvelle en utilisant la fonction functools.partial pour simplifier. la logique et améliore la lisibilité du code, et le partiel est en fait implémenté en interne via une simple fermeture.
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
partialmethod est similaire à partial, mais pour 绑定一个非对象自身的方法
, seule la méthode partielle peut être utilisée pour le moment. Jetons un coup d'œil à la différence entre les deux à travers ce qui suit. exemple .
from functools import partial, partialmethod def standalone(self, a=1, b=2): "Standalone function" print(' called standalone with:', (self, a, b)) if self is not None: print(' self.attr =', self.attr) class MyClass: "Demonstration class for functools" def __init__(self): self.attr = 'instance attribute' method1 = functools.partialmethod(standalone) # 使用partialmethod method2 = functools.partial(standalone) # 使用partial
>>> o = MyClass() >>> o.method1() called standalone with: (<__main__.MyClass object at 0x7f46d40cc550>, 1, 2) self.attr = instance attribute # 不能使用partial >>> o.method2() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: standalone() missing 1 required positional argument: 'self'
Bien que Python ne prenne pas en charge les méthodes du même nom autorisant différents types de paramètres, nous pouvons emprunter singledispatch à 动态指定相应的方法所接收的参数类型
sans mettre de jugement de paramètre dans la méthode interne un jugement est fait pour réduire la lisibilité du code.
from functools import singledispatch class TestClass(object): @singledispatch def test_method(arg, verbose=False): if verbose: print("Let me just say,", end=" ") print(arg) @test_method.register(int) def _(arg): print("Strength in numbers, eh?", end=" ") print(arg) @test_method.register(list) def _(arg): print("Enumerate this:") for i, elem in enumerate(arg): print(i, elem)
Ce qui suit utilise @test_method.register(int) et @test_method.register(list) pour spécifier que lorsque le premier paramètre de test_method est int ou list, différentes méthodes sont appelées respectivement pour le traitement.
>>> TestClass.test_method(55555) # call @test_method.register(int) Strength in numbers, eh? 55555 >>> TestClass.test_method([33, 22, 11]) # call @test_method.register(list) Enumerate this: 0 33 1 22 2 11 >>> TestClass.test_method('hello world', verbose=True) # call default Let me just say, hello world
Le décorateur perdra les attributs __name__ et __doc__ de la fonction décorée, qui peuvent être restaurés à l'aide de @wraps.
from functools import wraps def my_decorator(f): @wraps(f) def wrapper(): """wrapper_doc""" print('Calling decorated function') return f() return wrapper @my_decorator def example(): """example_doc""" print('Called example function')
>>> example.__name__ 'example' >>> example.__doc__ 'example_doc' # 尝试去掉@wraps(f)来看一下运行结果,example自身的__name__和__doc__都已经丧失了 >>> example.__name__ 'wrapper' >>> example.__doc__ 'wrapper_doc'
Nous pouvons également utiliser update_wrapper pour réécrire
from itertools import update_wrapper def g(): ... g = update_wrapper(g, f) # equal to @wraps(f) def g(): ...
@wraps est en fait implémenté en interne sur la base de update_wrapper.
def wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES): def decorator(wrapper): return update_wrapper(wrapper, wrapped=wrapped...) return decorator
lru_cache et singledispatch sont de la magie noire largement utilisée dans le développement. Jetons ensuite un coup d'œil à lru_cache. Pour les tâches de calcul répétitives, il est très important d'utiliser 缓存加速
Prenons un exemple de Fibonacci pour voir la différence de vitesse entre utiliser lru_cache et ne pas utiliser lru_cache.
# clockdeco.py import time import functools def clock(func): @functools.wraps(func) def clocked(*args, **kwargs): t0 = time.time() result = func(*args, **kwargs) elapsed = time.time() - t0 name = func.__name__ arg_lst = [] if args: arg_lst.append(', '.join(repr(arg) for arg in args)) if kwargs: pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())] arg_lst.append(', '.join(pairs)) arg_str = ', '.join(arg_lst) print('[%0.8fs] %s(%s) -> %r ' % (elapsed, name, arg_str, result)) return result return clocked
Ne pas utiliser lru_cache
from clockdeco import clock @clock def fibonacci(n): if n < 2: return n return fibonacci(n-2) + fibonacci(n-1) if __name__=='__main__': print(fibonacci(6))
Ce qui suit est le résultat en cours. On peut voir à partir du résultat en cours que fibonacci(n) sera. <🎜 pendant la récursion >, ce qui prend beaucoup de temps et de ressources. 重复计算
[0.00000119s] fibonacci(0) -> 0 [0.00000143s] fibonacci(1) -> 1 [0.00021172s] fibonacci(2) -> 1 [0.00000072s] fibonacci(1) -> 1 [0.00000095s] fibonacci(0) -> 0 [0.00000095s] fibonacci(1) -> 1 [0.00011444s] fibonacci(2) -> 1 [0.00022793s] fibonacci(3) -> 2 [0.00055265s] fibonacci(4) -> 3 [0.00000072s] fibonacci(1) -> 1 [0.00000072s] fibonacci(0) -> 0 [0.00000095s] fibonacci(1) -> 1 [0.00011158s] fibonacci(2) -> 1 [0.00022268s] fibonacci(3) -> 2 [0.00000095s] fibonacci(0) -> 0 [0.00000095s] fibonacci(1) -> 1 [0.00011349s] fibonacci(2) -> 1 [0.00000072s] fibonacci(1) -> 1 [0.00000095s] fibonacci(0) -> 0 [0.00000095s] fibonacci(1) -> 1 [0.00010705s] fibonacci(2) -> 1 [0.00021267s] fibonacci(3) -> 2 [0.00043225s] fibonacci(4) -> 3 [0.00076509s] fibonacci(5) -> 5 [0.00142813s] fibonacci(6) -> 8 8
Utiliser lru_cache
import functools from clockdeco import clock @functools.lru_cache() # 1 @clock # 2 def fibonacci(n): if n < 2: return n return fibonacci(n-2) + fibonacci(n-1) if __name__=='__main__': print(fibonacci(6))Voici les résultats en cours d'exécution. Les résultats calculés sont mis dans le cache.
[0.00000095s] fibonacci(0) -> 0 [0.00005770s] fibonacci(1) -> 1 [0.00015855s] fibonacci(2) -> 1 [0.00000286s] fibonacci(3) -> 2 [0.00021124s] fibonacci(4) -> 3 [0.00000191s] fibonacci(5) -> 5 [0.00024652s] fibonacci(6) -> 8 8Le nombre que nous avons choisi ci-dessus n'est pas assez grand. Les amis intéressés souhaiteront peut-être choisir un nombre plus grand pour comparer la différence de vitesse entre les deux total_orderingDans. Python2, vous pouvez comparer la taille des objets en personnalisant la valeur de retour de __cmp__ 0/-1/1 En Python3, __cmp__ est abandonné, mais on peut utiliser total_ordering puis modifier __lt__(), __le__(), __gt__(), __ge__(), __eq__(), __ne__() et d'autres méthodes magiques pour personnaliser les règles de comparaison de la classe. p.s : Si vous l'utilisez, vous devez définir l'un des __lt__(), __le__(), __gt__(), __ge__() dans la classe et ajouter une méthode __eq__() à la classe.
import functools @functools.total_ordering class MyObject: def __init__(self, val): self.val = val def __eq__(self, other): print(' testing __eq__({}, {})'.format( self.val, other.val)) return self.val == other.val def __gt__(self, other): print(' testing __gt__({}, {})'.format( self.val, other.val)) return self.val > other.val a = MyObject(1) b = MyObject(2) for expr in ['a < b', 'a <= b', 'a == b', 'a >= b', 'a > b']: print('\n{:<6}:'.format(expr)) result = eval(expr) print(' result of {}: {}'.format(expr, result))Voici les résultats en cours d'exécution :
a < b : testing __gt__(1, 2) testing __eq__(1, 2) result of a < b: True a <= b: testing __gt__(1, 2) result of a <= b: True a == b: testing __eq__(1, 2) result of a == b: False a >= b: testing __gt__(1, 2) testing __eq__(1, 2) result of a >= b: False a > b : testing __gt__(1, 2) result of a > b: FalseUtilisation d'itertools itertools nous fournit des fonctions très utiles pour faire fonctionner des objets itératifs. Itérateur infinicountcount(start=0, step=1) renverra un itérateur entier infini, augmentant de 1 à chaque fois. Vous pouvez éventuellement fournir un numéro de départ, qui est 0 par défaut.
>>> from itertools import count >>> for i in zip(count(1), ['a', 'b', 'c']): ... print(i, end=' ') ... (1, 'a') (2, 'b') (3, 'c')cyclecycle(iterable) répétera une séquence entrante à l'infini, mais vous pouvez spécifier le nombre de répétitions en fournissant un deuxième paramètre.
>>> from itertools import cycle >>> for i in zip(range(6), cycle(['a', 'b', 'c'])): ... print(i, end=' ') ... (0, 'a') (1, 'b') (2, 'c') (3, 'a') (4, 'b') (5, 'c')repeatrepeat(object[, times]) renvoie un itérateur dont les éléments sont répétés à l'infini. Vous pouvez fournir un deuxième paramètre pour limiter le nombre de répétitions.
>>> from itertools import repeat >>> for i, s in zip(count(1), repeat('over-and-over', 5)): ... print(i, s) ... 1 over-and-over 2 over-and-over 3 over-and-over 4 over-and-over 5 over-and-overItérateurs se terminant sur la séquence d'entrée la plus courteaccumulateaccumulate(iterable[, func])
>>> from itertools import accumulate >>> import operator >>> list(accumulate([1, 2, 3, 4, 5], operator.add)) [1, 3, 6, 10, 15] >>> list(accumulate([1, 2, 3, 4, 5], operator.mul)) [1, 2, 6, 24, 120]chain
>>> from itertools import chain >>> list(chain([1, 2, 3], ['a', 'b', 'c'])) [1, 2, 3, 'a', 'b', 'c']Le principe de mise en œuvre de la chaîne est le suivant
def chain(*iterables): # chain('ABC', 'DEF') --> A B C D E F for it in iterables: for element in it: yield elementchain.from_iterablechain.from_iterable(iterable) est similaire à chain, mais il ne reçoit qu'un seul itérable puis combine les éléments de cet itérable en un itérateur.
>>> from itertools import chain >>> list(chain.from_iterable(['ABC', 'DEF'])) ['A', 'B', 'C', 'D', 'E', 'F']Le principe d'implémentation est également similaire à chain
def from_iterable(iterables): # chain.from_iterable(['ABC', 'DEF']) --> A B C D E F for it in iterables: for element in it: yield elementcompresscompress(data, selectors) reçoit deux itérables en paramètres et ne renvoie que ceux correspondants dans les sélecteurs Data dont l'élément est True, s'arrête lorsqu'une des données/sélecteurs est épuisée.
>>> list(compress([1, 2, 3, 4, 5], [True, True, False, False, True])) [1, 2, 5]zip_longestzip_longest(*iterables, fillvalue=None) est similaire à zip, mais l'inconvénient de zip est que lorsqu'un certain élément d'un itérable est parcouru, la traversée entière sera stop. Plus précisément, veuillez consulter l'exemple suivant pour la différence
from itertools import zip_longest r1 = range(3) r2 = range(2) print('zip stops early:') print(list(zip(r1, r2))) r1 = range(3) r2 = range(2) print('\nzip_longest processes all of the values:') print(list(zip_longest(r1, r2)))Ce qui suit est le résultat de sortie
zip stops early: [(0, 0), (1, 1)] zip_longest processes all of the values: [(0, 0), (1, 1), (2, None)]
islice(iterable, stop) or islice(iterable, start, stop[, step]) 与Python的字符串和列表切片有一些类似,只是不能对start、start和step使用负值。
>>> from itertools import islice >>> for i in islice(range(100), 0, 100, 10): ... print(i, end=' ') ... 0 10 20 30 40 50 60 70 80 90
tee(iterable, n=2) 返回n个独立的iterator,n默认为2。
from itertools import islice, tee r = islice(count(), 5) i1, i2 = tee(r) print('i1:', list(i1)) print('i2:', list(i2)) for i in r: print(i, end=' ') if i > 1: break
下面是输出结果,注意tee(r)后,r作为iterator已经失效,所以for循环没有输出值。
i1: [0, 1, 2, 3, 4] i2: [0, 1, 2, 3, 4]
starmap(func, iterable)假设iterable将返回一个元组流,并使用这些元组作为参数调用func:
>>> from itertools import starmap >>> import os >>> iterator = starmap(os.path.join, ... [('/bin', 'python'), ('/usr', 'bin', 'java'), ... ('/usr', 'bin', 'perl'), ('/usr', 'bin', 'ruby')]) >>> list(iterator) ['/bin/python', '/usr/bin/java', '/usr/bin/perl', '/usr/bin/ruby']
filterfalse(predicate, iterable) 与filter()相反,返回所有predicate返回False的元素。
itertools.filterfalse(is_even, itertools.count()) => 1, 3, 5, 7, 9, 11, 13, 15, ...
takewhile(predicate, iterable) 只要predicate返回True,不停地返回iterable中的元素。一旦predicate返回False,iteration将结束。
def less_than_10(x): return x < 10 itertools.takewhile(less_than_10, itertools.count()) => 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 itertools.takewhile(is_even, itertools.count()) => 0
dropwhile(predicate, iterable) 在predicate返回True时舍弃元素,然后返回其余迭代结果。
itertools.dropwhile(less_than_10, itertools.count()) => 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, ... itertools.dropwhile(is_even, itertools.count()) => 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...
groupby(iterable, key=None) 把iterator中相邻的重复元素
挑出来放在一起。p.s: The input sequence needs to be sorted on the key value in order for the groupings to work out as expected.
[k for k, g in groupby('AAAABBBCCDAABBB')] --> A B C D A B
[list(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D
>>> import itertools >>> for key, group in itertools.groupby('AAAABBBCCDAABBB'): ... print(key, list(group)) ... A ['A', 'A', 'A', 'A'] B ['B', 'B', 'B'] C ['C', 'C'] D ['D'] A ['A', 'A'] B ['B', 'B', 'B']
city_list = [('Decatur', 'AL'), ('Huntsville', 'AL'), ('Selma', 'AL'), ('Anchorage', 'AK'), ('Nome', 'AK'), ('Flagstaff', 'AZ'), ('Phoenix', 'AZ'), ('Tucson', 'AZ'), ... ] def get_state(city_state): return city_state[1] itertools.groupby(city_list, get_state) => ('AL', iterator-1), ('AK', iterator-2), ('AZ', iterator-3), ... iterator-1 => ('Decatur', 'AL'), ('Huntsville', 'AL'), ('Selma', 'AL') iterator-2 => ('Anchorage', 'AK'), ('Nome', 'AK') iterator-3 => ('Flagstaff', 'AZ'), ('Phoenix', 'AZ'), ('Tucson', 'AZ')
product(*iterables, repeat=1)
product(A, B) returns the same as ((x,y) for x in A for y in B)
product(A, repeat=4) means the same as product(A, A, A, A)
from itertools import product def show(iterable): for i, item in enumerate(iterable, 1): print(item, end=' ') if (i % 3) == 0: print() print() print('Repeat 2:\n') show(product(range(3), repeat=2)) print('Repeat 3:\n') show(product(range(3), repeat=3))
Repeat 2: (0, 0) (0, 1) (0, 2) (1, 0) (1, 1) (1, 2) (2, 0) (2, 1) (2, 2) Repeat 3: (0, 0, 0) (0, 0, 1) (0, 0, 2) (0, 1, 0) (0, 1, 1) (0, 1, 2) (0, 2, 0) (0, 2, 1) (0, 2, 2) (1, 0, 0) (1, 0, 1) (1, 0, 2) (1, 1, 0) (1, 1, 1) (1, 1, 2) (1, 2, 0) (1, 2, 1) (1, 2, 2) (2, 0, 0) (2, 0, 1) (2, 0, 2) (2, 1, 0) (2, 1, 1) (2, 1, 2) (2, 2, 0) (2, 2, 1) (2, 2, 2)
permutations(iterable, r=None)返回长度为r的所有可能的组合。
from itertools import permutations def show(iterable): first = None for i, item in enumerate(iterable, 1): if first != item[0]: if first is not None: print() first = item[0] print(''.join(item), end=' ') print() print('All permutations:\n') show(permutations('abcd')) print('\nPairs:\n') show(permutations('abcd', r=2))
下面是输出结果
All permutations: abcd abdc acbd acdb adbc adcb bacd badc bcad bcda bdac bdca cabd cadb cbad cbda cdab cdba dabc dacb dbac dbca dcab dcba Pairs: ab ac ad ba bc bd ca cb cd da db dc
combinations(iterable, r) 返回一个iterator,提供iterable中所有元素可能组合的r元组。每个元组中的元素保持与iterable返回的顺序相同。下面的实例中,不同于上面的permutations,a总是在bcd之前,b总是在cd之前,c总是在d之前。
from itertools import combinations def show(iterable): first = None for i, item in enumerate(iterable, 1): if first != item[0]: if first is not None: print() first = item[0] print(''.join(item), end=' ') print() print('Unique pairs:\n') show(combinations('abcd', r=2))
下面是输出结果
Unique pairs: ab ac ad bc bd cd
combinations_with_replacement(iterable, r)函数放宽了一个不同的约束:元素可以在单个元组中重复,即可以出现aa/bb/cc/dd等组合。
from itertools import combinations_with_replacement def show(iterable): first = None for i, item in enumerate(iterable, 1): if first != item[0]: if first is not None: print() first = item[0] print(''.join(item), end=' ') print() print('Unique pairs:\n') show(combinations_with_replacement('abcd', r=2))
下面是输出结果
aa ab ac ad bb bc bd cc cd dd
operator.attrgetter(attr)和operator.attrgetter(*attrs)
After f = attrgetter('name'), the call f(b) returns b.name.
After f = attrgetter('name', 'date'), the call f(b) returns (b.name, b.date).
After f = attrgetter('name.first', 'name.last'), the call f(b) returns (b.name.first, b.name.last).
我们通过下面这个例子来了解一下itergetter的用法。
>>> class Student: ... def __init__(self, name, grade, age): ... self.name = name ... self.grade = grade ... self.age = age ... def __repr__(self): ... return repr((self.name, self.grade, self.age)) >>> student_objects = [ ... Student('john', 'A', 15), ... Student('jane', 'B', 12), ... Student('dave', 'B', 10), ... ] >>> sorted(student_objects, key=lambda student: student.age) # 传统的lambda做法 [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)] >>> from operator import itemgetter, attrgetter >>> sorted(student_objects, key=attrgetter('age')) [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)] # 但是如果像下面这样接受双重比较,Python脆弱的lambda就不适用了 >>> sorted(student_objects, key=attrgetter('grade', 'age')) [('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]
attrgetter的实现原理:
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
operator.itemgetter(item)和operator.itemgetter(*items)
After f = itemgetter(2), the call f(r) returns r[2].
After g = itemgetter(2, 5, 3), the call g(r) returns (r[2], r[5], r[3]).
我们通过下面这个例子来了解一下itergetter的用法
>>> student_tuples = [ ... ('john', 'A', 15), ... ('jane', 'B', 12), ... ('dave', 'B', 10), ... ] >>> sorted(student_tuples, key=lambda student: student[2]) # 传统的lambda做法 [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)] >>> from operator import attrgetter >>> sorted(student_tuples, key=itemgetter(2)) [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)] # 但是如果像下面这样接受双重比较,Python脆弱的lambda就不适用了 >>> sorted(student_tuples, key=itemgetter(1,2)) [('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]
itemgetter的实现原理
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
operator.methodcaller(name[, args...])
After f = methodcaller('name'), the call f(b) returns b.name().
After f = methodcaller('name', 'foo', bar=1), the call f(b) returns b.name('foo', bar=1).
methodcaller的实现原理
def methodcaller(name, *args, **kwargs): def caller(obj): return getattr(obj, name)(*args, **kwargs) return caller
DOCUMENTATION-FUNCTOOLS
DOCUMENTATION-ITERTOOLS
DOCUMENTATION-OPERATOR
HWOTO-FUNCTIONAL
HWOTO-SORTING
PYMOTW
FLENT-PYTHON
本文为作者原创,转载请先与作者联系。首发于我的博客
functools
, itertools
, operator
是Python标准库为我们提供的支持函数式编程的三大模块,合理的使用这三个模块,我们可以写出更加简洁可读的Pythonic代码,接下来我们通过一些example来了解三大模块的使用。
functools是Python中很重要的模块,它提供了一些非常有用的高阶函数。高阶函数就是说一个可以接受函数作为参数或者以函数作为返回值的函数,因为Python中函数也是对象,因此很容易支持这样的函数式特性。
>>> from functools import partial >>> basetwo = partial(int, base=2) >>> basetwo('10010') 18
basetwo('10010')
équivaut en fait à appeler int('10010', base=2)
Lorsque le nombre de paramètres de la fonction est trop important, vous pouvez créer une nouvelle fonction en utilisant functools.partial en simplifiant le. logique pour améliorer la lisibilité du code, partial est en fait implémenté en interne via une simple fermeture.
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
partialmethod est similaire à partial, mais pour 绑定一个非对象自身的方法
, seule la méthode partielle peut être utilisée pour le moment. Jetons un coup d'œil à la différence entre les deux à travers ce qui suit. exemple .
from functools import partial, partialmethod def standalone(self, a=1, b=2): "Standalone function" print(' called standalone with:', (self, a, b)) if self is not None: print(' self.attr =', self.attr) class MyClass: "Demonstration class for functools" def __init__(self): self.attr = 'instance attribute' method1 = functools.partialmethod(standalone) # 使用partialmethod method2 = functools.partial(standalone) # 使用partial
>>> o = MyClass() >>> o.method1() called standalone with: (<__main__.MyClass object at 0x7f46d40cc550>, 1, 2) self.attr = instance attribute # 不能使用partial >>> o.method2() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: standalone() missing 1 required positional argument: 'self'
Bien que Python ne prenne pas en charge les méthodes du même nom autorisant différents types de paramètres, nous pouvons emprunter singledispatch à 动态指定相应的方法所接收的参数类型
sans mettre de jugement de paramètre dans la méthode interne un jugement est fait pour réduire la lisibilité du code.
from functools import singledispatch class TestClass(object): @singledispatch def test_method(arg, verbose=False): if verbose: print("Let me just say,", end=" ") print(arg) @test_method.register(int) def _(arg): print("Strength in numbers, eh?", end=" ") print(arg) @test_method.register(list) def _(arg): print("Enumerate this:") for i, elem in enumerate(arg): print(i, elem)
Ce qui suit utilise @test_method.register(int) et @test_method.register(list) pour spécifier que lorsque le premier paramètre de test_method est int ou list, différentes méthodes sont appelées respectivement pour le traitement.
>>> TestClass.test_method(55555) # call @test_method.register(int) Strength in numbers, eh? 55555 >>> TestClass.test_method([33, 22, 11]) # call @test_method.register(list) Enumerate this: 0 33 1 22 2 11 >>> TestClass.test_method('hello world', verbose=True) # call default Let me just say, hello world
Le décorateur perdra les attributs __name__ et __doc__ de la fonction décorée, qui peuvent être restaurés à l'aide de @wraps.
from functools import wraps def my_decorator(f): @wraps(f) def wrapper(): """wrapper_doc""" print('Calling decorated function') return f() return wrapper @my_decorator def example(): """example_doc""" print('Called example function')
>>> example.__name__ 'example' >>> example.__doc__ 'example_doc' # 尝试去掉@wraps(f)来看一下运行结果,example自身的__name__和__doc__都已经丧失了 >>> example.__name__ 'wrapper' >>> example.__doc__ 'wrapper_doc'
Nous pouvons également utiliser update_wrapper pour réécrire
from itertools import update_wrapper def g(): ... g = update_wrapper(g, f) # equal to @wraps(f) def g(): ...
@wraps est en fait implémenté en interne sur la base de update_wrapper.
def wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES): def decorator(wrapper): return update_wrapper(wrapper, wrapped=wrapped...) return decorator
lru_cache et singledispatch sont de la magie noire largement utilisée dans le développement. Jetons ensuite un coup d'œil à lru_cache. Pour les tâches de calcul répétitives, il est très important d'utiliser 缓存加速
Prenons un exemple de Fibonacci pour voir la différence de vitesse entre utiliser lru_cache et ne pas utiliser lru_cache.
# clockdeco.py import time import functools def clock(func): @functools.wraps(func) def clocked(*args, **kwargs): t0 = time.time() result = func(*args, **kwargs) elapsed = time.time() - t0 name = func.__name__ arg_lst = [] if args: arg_lst.append(', '.join(repr(arg) for arg in args)) if kwargs: pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())] arg_lst.append(', '.join(pairs)) arg_str = ', '.join(arg_lst) print('[%0.8fs] %s(%s) -> %r ' % (elapsed, name, arg_str, result)) return result return clocked
Ne pas utiliser lru_cache
from clockdeco import clock @clock def fibonacci(n): if n < 2: return n return fibonacci(n-2) + fibonacci(n-1) if __name__=='__main__': print(fibonacci(6))
Ce qui suit est le résultat en cours. On peut voir à partir du résultat en cours que fibonacci(n) sera. <🎜 pendant la récursion >, ce qui prend beaucoup de temps et de ressources. 重复计算
[0.00000119s] fibonacci(0) -> 0 [0.00000143s] fibonacci(1) -> 1 [0.00021172s] fibonacci(2) -> 1 [0.00000072s] fibonacci(1) -> 1 [0.00000095s] fibonacci(0) -> 0 [0.00000095s] fibonacci(1) -> 1 [0.00011444s] fibonacci(2) -> 1 [0.00022793s] fibonacci(3) -> 2 [0.00055265s] fibonacci(4) -> 3 [0.00000072s] fibonacci(1) -> 1 [0.00000072s] fibonacci(0) -> 0 [0.00000095s] fibonacci(1) -> 1 [0.00011158s] fibonacci(2) -> 1 [0.00022268s] fibonacci(3) -> 2 [0.00000095s] fibonacci(0) -> 0 [0.00000095s] fibonacci(1) -> 1 [0.00011349s] fibonacci(2) -> 1 [0.00000072s] fibonacci(1) -> 1 [0.00000095s] fibonacci(0) -> 0 [0.00000095s] fibonacci(1) -> 1 [0.00010705s] fibonacci(2) -> 1 [0.00021267s] fibonacci(3) -> 2 [0.00043225s] fibonacci(4) -> 3 [0.00076509s] fibonacci(5) -> 5 [0.00142813s] fibonacci(6) -> 8 8
Utiliser lru_cache
import functools from clockdeco import clock @functools.lru_cache() # 1 @clock # 2 def fibonacci(n): if n < 2: return n return fibonacci(n-2) + fibonacci(n-1) if __name__=='__main__': print(fibonacci(6))Voici les résultats en cours d'exécution. Les résultats calculés sont mis dans le cache.
[0.00000095s] fibonacci(0) -> 0 [0.00005770s] fibonacci(1) -> 1 [0.00015855s] fibonacci(2) -> 1 [0.00000286s] fibonacci(3) -> 2 [0.00021124s] fibonacci(4) -> 3 [0.00000191s] fibonacci(5) -> 5 [0.00024652s] fibonacci(6) -> 8 8Le nombre que nous avons choisi ci-dessus n'est pas assez grand. Les amis intéressés souhaiteront peut-être choisir un nombre plus grand pour comparer la différence de vitesse entre les deux total_orderingDans. Python2, vous pouvez comparer la taille des objets en personnalisant la valeur de retour de __cmp__ 0/-1/1 En Python3, __cmp__ est abandonné, mais on peut utiliser total_ordering puis modifier __lt__(), __le__(), __gt__(), __ge__(), __eq__(), __ne__() et d'autres méthodes magiques pour personnaliser les règles de comparaison de la classe. p.s : Si vous l'utilisez, vous devez définir l'un des __lt__(), __le__(), __gt__(), __ge__() dans la classe et ajouter une méthode __eq__() à la classe.
import functools @functools.total_ordering class MyObject: def __init__(self, val): self.val = val def __eq__(self, other): print(' testing __eq__({}, {})'.format( self.val, other.val)) return self.val == other.val def __gt__(self, other): print(' testing __gt__({}, {})'.format( self.val, other.val)) return self.val > other.val a = MyObject(1) b = MyObject(2) for expr in ['a < b', 'a <= b', 'a == b', 'a >= b', 'a > b']: print('\n{:<6}:'.format(expr)) result = eval(expr) print(' result of {}: {}'.format(expr, result))Voici les résultats en cours d'exécution :
a < b : testing __gt__(1, 2) testing __eq__(1, 2) result of a < b: True a <= b: testing __gt__(1, 2) result of a <= b: True a == b: testing __eq__(1, 2) result of a == b: False a >= b: testing __gt__(1, 2) testing __eq__(1, 2) result of a >= b: False a > b : testing __gt__(1, 2) result of a > b: FalseUtilisation d'itertools itertools nous fournit des fonctions très utiles pour faire fonctionner des objets itératifs. Itérateur infinicountcount(start=0, step=1) renverra un itérateur entier infini, augmentant de 1 à chaque fois. Vous pouvez éventuellement fournir un numéro de départ, qui est 0 par défaut.
>>> from itertools import count >>> for i in zip(count(1), ['a', 'b', 'c']): ... print(i, end=' ') ... (1, 'a') (2, 'b') (3, 'c')cyclecycle(iterable) répétera une séquence entrante à l'infini, mais vous pouvez spécifier le nombre de répétitions en fournissant un deuxième paramètre.
>>> from itertools import cycle >>> for i in zip(range(6), cycle(['a', 'b', 'c'])): ... print(i, end=' ') ... (0, 'a') (1, 'b') (2, 'c') (3, 'a') (4, 'b') (5, 'c')repeatrepeat(object[, times]) renvoie un itérateur dont les éléments sont répétés à l'infini. Vous pouvez fournir un deuxième paramètre pour limiter le nombre de répétitions.
>>> from itertools import repeat >>> for i, s in zip(count(1), repeat('over-and-over', 5)): ... print(i, s) ... 1 over-and-over 2 over-and-over 3 over-and-over 4 over-and-over 5 over-and-overItérateurs se terminant sur la séquence d'entrée la plus courteaccumulateaccumulate(iterable[, func])
>>> from itertools import accumulate >>> import operator >>> list(accumulate([1, 2, 3, 4, 5], operator.add)) [1, 3, 6, 10, 15] >>> list(accumulate([1, 2, 3, 4, 5], operator.mul)) [1, 2, 6, 24, 120]chain
>>> from itertools import chain >>> list(chain([1, 2, 3], ['a', 'b', 'c'])) [1, 2, 3, 'a', 'b', 'c']Le principe de mise en œuvre de la chaîne est le suivant
def chain(*iterables): # chain('ABC', 'DEF') --> A B C D E F for it in iterables: for element in it: yield elementchain.from_iterablechain.from_iterable(iterable) est similaire à chain, mais il ne reçoit qu'un seul itérable puis combine les éléments de cet itérable en un itérateur.
>>> from itertools import chain >>> list(chain.from_iterable(['ABC', 'DEF'])) ['A', 'B', 'C', 'D', 'E', 'F']Le principe d'implémentation est également similaire à chain
def from_iterable(iterables): # chain.from_iterable(['ABC', 'DEF']) --> A B C D E F for it in iterables: for element in it: yield elementcompresscompress(data, selectors) reçoit deux itérables en paramètres et ne renvoie que ceux correspondants dans les sélecteurs Data dont l'élément est True, s'arrête lorsqu'une des données/sélecteurs est épuisée.
>>> list(compress([1, 2, 3, 4, 5], [True, True, False, False, True])) [1, 2, 5]zip_longestzip_longest(*iterables, fillvalue=None) est similaire à zip, mais l'inconvénient de zip est que lorsqu'un certain élément d'un itérable est parcouru, la traversée entière sera stop. Plus précisément, veuillez consulter l'exemple suivant pour la différence
from itertools import zip_longest r1 = range(3) r2 = range(2) print('zip stops early:') print(list(zip(r1, r2))) r1 = range(3) r2 = range(2) print('\nzip_longest processes all of the values:') print(list(zip_longest(r1, r2)))Ce qui suit est le résultat de sortie
zip stops early: [(0, 0), (1, 1)] zip_longest processes all of the values: [(0, 0), (1, 1), (2, None)]isliceislice(iterable, stop) ou islice (iterable, start, stop[ , step]) est quelque peu similaire au découpage de chaîne et de liste de Python, sauf que les valeurs négatives ne peuvent pas être utilisées pour start, start et step.
>>> from itertools import islice >>> for i in islice(range(100), 0, 100, 10): ... print(i, end=' ') ... 0 10 20 30 40 50 60 70 80 90teetee(iterable, n=2) renvoie n itérateurs indépendants, n par défaut est 2.
from itertools import islice, tee r = islice(count(), 5) i1, i2 = tee(r) print('i1:', list(i1)) print('i2:', list(i2)) for i in r: print(i, end=' ') if i > 1: breakCe qui suit est le résultat de sortie. Notez qu'après tee(r), r est devenu invalide en tant qu'itérateur, donc la boucle for n'a pas de valeur de sortie.
i1: [0, 1, 2, 3, 4] i2: [0, 1, 2, 3, 4]starmapstarmap(func, iterable) suppose que iterable renverra un flux de tuples et appelle func avec ces tuples comme arguments :
>>> from itertools import starmap >>> import os >>> iterator = starmap(os.path.join, ... [('/bin', 'python'), ('/usr', 'bin', 'java'), ... ('/usr', 'bin', 'perl'), ('/usr', 'bin', 'ruby')]) >>> list(iterator) ['/bin/python', '/usr/bin/java', '/usr/bin/perl', '/usr/bin/ruby']filterfalse filterfalse(predicate, iterable) L'opposé de filter(), renvoie tous les éléments pour lesquels le prédicat renvoie False.
itertools.filterfalse(is_even, itertools.count()) => 1, 3, 5, 7, 9, 11, 13, 15, ...
takewhile(predicate, iterable) 只要predicate返回True,不停地返回iterable中的元素。一旦predicate返回False,iteration将结束。
def less_than_10(x): return x < 10 itertools.takewhile(less_than_10, itertools.count()) => 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 itertools.takewhile(is_even, itertools.count()) => 0
dropwhile(predicate, iterable) 在predicate返回True时舍弃元素,然后返回其余迭代结果。
itertools.dropwhile(less_than_10, itertools.count()) => 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, ... itertools.dropwhile(is_even, itertools.count()) => 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...
groupby(iterable, key=None) 把iterator中相邻的重复元素
挑出来放在一起。p.s: The input sequence needs to be sorted on the key value in order for the groupings to work out as expected.
[k for k, g in groupby('AAAABBBCCDAABBB')] --> A B C D A B
[list(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D
>>> import itertools >>> for key, group in itertools.groupby('AAAABBBCCDAABBB'): ... print(key, list(group)) ... A ['A', 'A', 'A', 'A'] B ['B', 'B', 'B'] C ['C', 'C'] D ['D'] A ['A', 'A'] B ['B', 'B', 'B']
city_list = [('Decatur', 'AL'), ('Huntsville', 'AL'), ('Selma', 'AL'), ('Anchorage', 'AK'), ('Nome', 'AK'), ('Flagstaff', 'AZ'), ('Phoenix', 'AZ'), ('Tucson', 'AZ'), ... ] def get_state(city_state): return city_state[1] itertools.groupby(city_list, get_state) => ('AL', iterator-1), ('AK', iterator-2), ('AZ', iterator-3), ... iterator-1 => ('Decatur', 'AL'), ('Huntsville', 'AL'), ('Selma', 'AL') iterator-2 => ('Anchorage', 'AK'), ('Nome', 'AK') iterator-3 => ('Flagstaff', 'AZ'), ('Phoenix', 'AZ'), ('Tucson', 'AZ')
product(*iterables, repeat=1)
product(A, B) returns the same as ((x,y) for x in A for y in B)
product(A, repeat=4) means the same as product(A, A, A, A)
from itertools import product def show(iterable): for i, item in enumerate(iterable, 1): print(item, end=' ') if (i % 3) == 0: print() print() print('Repeat 2:\n') show(product(range(3), repeat=2)) print('Repeat 3:\n') show(product(range(3), repeat=3))
Repeat 2: (0, 0) (0, 1) (0, 2) (1, 0) (1, 1) (1, 2) (2, 0) (2, 1) (2, 2) Repeat 3: (0, 0, 0) (0, 0, 1) (0, 0, 2) (0, 1, 0) (0, 1, 1) (0, 1, 2) (0, 2, 0) (0, 2, 1) (0, 2, 2) (1, 0, 0) (1, 0, 1) (1, 0, 2) (1, 1, 0) (1, 1, 1) (1, 1, 2) (1, 2, 0) (1, 2, 1) (1, 2, 2) (2, 0, 0) (2, 0, 1) (2, 0, 2) (2, 1, 0) (2, 1, 1) (2, 1, 2) (2, 2, 0) (2, 2, 1) (2, 2, 2)
permutations(iterable, r=None)返回长度为r的所有可能的组合。
from itertools import permutations def show(iterable): first = None for i, item in enumerate(iterable, 1): if first != item[0]: if first is not None: print() first = item[0] print(''.join(item), end=' ') print() print('All permutations:\n') show(permutations('abcd')) print('\nPairs:\n') show(permutations('abcd', r=2))
下面是输出结果
All permutations: abcd abdc acbd acdb adbc adcb bacd badc bcad bcda bdac bdca cabd cadb cbad cbda cdab cdba dabc dacb dbac dbca dcab dcba Pairs: ab ac ad ba bc bd ca cb cd da db dc
combinations(iterable, r) 返回一个iterator,提供iterable中所有元素可能组合的r元组。每个元组中的元素保持与iterable返回的顺序相同。下面的实例中,不同于上面的permutations,a总是在bcd之前,b总是在cd之前,c总是在d之前。
from itertools import combinations def show(iterable): first = None for i, item in enumerate(iterable, 1): if first != item[0]: if first is not None: print() first = item[0] print(''.join(item), end=' ') print() print('Unique pairs:\n') show(combinations('abcd', r=2))
下面是输出结果
Unique pairs: ab ac ad bc bd cd
combinations_with_replacement(iterable, r)函数放宽了一个不同的约束:元素可以在单个元组中重复,即可以出现aa/bb/cc/dd等组合。
from itertools import combinations_with_replacement def show(iterable): first = None for i, item in enumerate(iterable, 1): if first != item[0]: if first is not None: print() first = item[0] print(''.join(item), end=' ') print() print('Unique pairs:\n') show(combinations_with_replacement('abcd', r=2))
下面是输出结果
aa ab ac ad bb bc bd cc cd dd
operator.attrgetter(attr)和operator.attrgetter(*attrs)
After f = attrgetter('name'), the call f(b) returns b.name.
After f = attrgetter('name', 'date'), the call f(b) returns (b.name, b.date).
After f = attrgetter('name.first', 'name.last'), the call f(b) returns (b.name.first, b.name.last).
我们通过下面这个例子来了解一下itergetter的用法。
>>> class Student: ... def __init__(self, name, grade, age): ... self.name = name ... self.grade = grade ... self.age = age ... def __repr__(self): ... return repr((self.name, self.grade, self.age)) >>> student_objects = [ ... Student('john', 'A', 15), ... Student('jane', 'B', 12), ... Student('dave', 'B', 10), ... ] >>> sorted(student_objects, key=lambda student: student.age) # 传统的lambda做法 [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)] >>> from operator import itemgetter, attrgetter >>> sorted(student_objects, key=attrgetter('age')) [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)] # 但是如果像下面这样接受双重比较,Python脆弱的lambda就不适用了 >>> sorted(student_objects, key=attrgetter('grade', 'age')) [('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]
attrgetter的实现原理:
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
operator.itemgetter(item)和operator.itemgetter(*items)
After f = itemgetter(2), the call f(r) returns r[2].
After g = itemgetter(2, 5, 3), the call g(r) returns (r[2], r[5], r[3]).
我们通过下面这个例子来了解一下itergetter的用法
>>> student_tuples = [ ... ('john', 'A', 15), ... ('jane', 'B', 12), ... ('dave', 'B', 10), ... ] >>> sorted(student_tuples, key=lambda student: student[2]) # 传统的lambda做法 [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)] >>> from operator import attrgetter >>> sorted(student_tuples, key=itemgetter(2)) [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)] # 但是如果像下面这样接受双重比较,Python脆弱的lambda就不适用了 >>> sorted(student_tuples, key=itemgetter(1,2)) [('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]
itemgetter的实现原理
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
operator.methodcaller(name[, args...])
After f = methodcaller('name'), the call f(b) returns b.name().
After f = methodcaller('name', 'foo', bar=1), the call f(b) returns b.name('foo', bar=1).
methodcaller的实现原理
def methodcaller(name, *args, **kwargs): def caller(obj): return getattr(obj, name)(*args, **kwargs) return caller
更多Python标准库之functools/itertools/operator相关文章请关注PHP中文网!