Heim  >  Artikel  >  Backend-Entwicklung  >  Python-Standardbibliothek functools/itertools/operator

Python-Standardbibliothek functools/itertools/operator

高洛峰
高洛峰Original
2017-02-09 11:04:561751Durchsuche

Einführung

functools, itertools, operator sind die drei Module, die von der Python-Standardbibliothek zur Unterstützung der funktionalen Programmierung bereitgestellt werden Wenn wir diese drei Module richtig verwenden, können wir prägnanteren und lesbareren Pythonic-Code schreiben. Als Nächstes werden wir einige Beispiele verwenden, um die Verwendung der drei Module zu verstehen.

Verwendung von functools

functools ist ein sehr wichtiges Modul in Python, das einige sehr nützliche Funktionen höherer Ordnung bereitstellt. Eine Funktion höherer Ordnung ist eine Funktion, die eine Funktion als Parameter akzeptieren oder eine Funktion als Rückgabewert verwenden kann. Da Funktionen in Python auch Objekte sind, ist es einfach, solche Funktionsmerkmale zu unterstützen.

partial

>>> from functools import partial

>>> basetwo = partial(int, base=2)

>>> basetwo('10010')
18

basetwo('10010') entspricht tatsächlich dem Aufruf von int('10010', base=2). Wenn die Funktion zu viele Parameter hat, können Sie zur Vereinfachung einen neuen Parameter erstellen Die Logik wird verbessert und die Lesbarkeit des Codes verbessert. Der Teil wird tatsächlich intern durch einen einfachen Abschluss implementiert.

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

Partialmethod ähnelt Partial, aber für 绑定一个非对象自身的方法 kann derzeit nur Partialmethod verwendet werden. Schauen wir uns den Unterschied zwischen den beiden im Folgenden an Beispiel .

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'

singledispatch

Obwohl Python keine Methoden mit demselben Namen unterstützt, die unterschiedliche Parametertypen zulassen, können wir singledispatch an 动态指定相应的方法所接收的参数类型 ausleihen, ohne Parameterbeurteilung in die interne Methode einzufügen Es wird eine Beurteilung vorgenommen, um die Lesbarkeit des Codes zu verringern.

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)

Im Folgenden werden @test_method.register(int) und @test_method.register(list) verwendet, um anzugeben, dass, wenn der erste Parameter von test_method int oder list ist, jeweils unterschiedliche Methoden zur Verarbeitung aufgerufen werden.

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

wraps

Der Dekorateur verliert die Attribute __name__ und __doc__ der dekorierten Funktion, die mit @wraps wiederhergestellt werden können.

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'

Wir können update_wrapper auch zum Umschreiben verwenden

from itertools import update_wrapper


def g():
    ...
g = update_wrapper(g, f)


# equal to
@wraps(f)
def g():
    ...

@wraps wird tatsächlich intern basierend auf update_wrapper implementiert.

def wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES):
    def decorator(wrapper):
        return update_wrapper(wrapper, wrapped=wrapped...)
    return decorator

lru_cache

lru_cache und singledispatch sind schwarze Magie, die in der Entwicklung weit verbreitet ist. Als nächstes werfen wir einen Blick auf lru_cache. Für sich wiederholende Rechenaufgaben ist es sehr wichtig, 缓存加速 zu verwenden. Nehmen wir ein Fibonacci-Beispiel, um den Geschwindigkeitsunterschied zwischen der Verwendung von lru_cache und der Nichtverwendung von lru_cache zu sehen.

# 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

Verwenden Sie nicht lru_cache

from clockdeco import clock


@clock
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-2) + fibonacci(n-1)


if __name__==&#39;__main__&#39;:
    print(fibonacci(6))

Das Folgende ist das laufende Ergebnis. Aus dem laufenden Ergebnis ist ersichtlich, dass fibonacci(n) sein wird <🎜 während der Rekursion >, was sehr zeit- und ressourcenintensiv ist. 重复计算

[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

Verwenden Sie 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__==&#39;__main__&#39;:
    print(fibonacci(6))
Die folgenden Ergebnisse werden im Cache abgelegt.

[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 
8
Die oben gewählte Zahl ist nicht groß genug. Interessierte Freunde möchten möglicherweise eine größere Zahl wählen, um den Geschwindigkeitsunterschied zwischen den beiden zu vergleichen

total_ordering

In In Python2 können Sie die Größe von Objekten vergleichen, indem Sie den Rückgabewert von __cmp__ 0/-1/1 anpassen. In Python3 wird __cmp__ aufgegeben, aber wir können total_ordering verwenden und dann __lt__(), __le__(), __gt__(), ändern. __ge__(), __eq__(), __ne__() und andere magische Methoden zum Anpassen der Vergleichsregeln von Klassen. p.s: Wenn Sie es verwenden, müssen Sie eine von __lt__(), __le__(), __gt__(), __ge__() in der Klasse definieren und der Klasse eine __eq__()-Methode hinzufügen.

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&#39;, &#39;a <= b&#39;, &#39;a == b&#39;, &#39;a >= b', 'a > b']:
    print('\n{:<6}:&#39;.format(expr))
    result = eval(expr)
    print(&#39;  result of {}: {}&#39;.format(expr, result))
Das Folgende sind die laufenden Ergebnisse:

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: False
Verwendung von itertools

itertools bietet uns sehr nützliche Funktionen für den Betrieb iterativer Objekte.

Unendlicher Iterator

count

count(start=0, step=1) gibt einen unendlichen ganzzahligen Iterator zurück, der sich jedes Mal um 1 erhöht. Optional können Sie eine Startnummer angeben, die standardmäßig 0 ist.

>>> from itertools import count

>>> for i in zip(count(1), ['a', 'b', 'c']):
...     print(i, end=' ')
...
(1, 'a') (2, 'b') (3, 'c')
cycle

cycle(iterable) wiederholt eine eingehende Sequenz unendlich, aber Sie können die Anzahl der Wiederholungen angeben, indem Sie einen zweiten Parameter angeben.

>>> 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')
repeat

repeat(object[, times]) gibt einen Iterator zurück, dessen Elemente unendlich oft wiederholt werden. Sie können einen zweiten Parameter angeben, um die Anzahl der Wiederholungen zu begrenzen.

>>> 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-over
Iteratoren, die auf der kürzesten Eingabesequenz enden

accumulate

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

itertools.chain(*iterables) kann mehrere Iterables in einem Iterator kombinieren

>>> from itertools import chain

>>> list(chain([1, 2, 3], ['a', 'b', 'c']))
[1, 2, 3, 'a', 'b', 'c']
Das Implementierungsprinzip der Kette ist wie folgt

def chain(*iterables):
    # chain('ABC', 'DEF') --> A B C D E F
    for it in iterables:
        for element in it:
            yield element
chain.from_iterable

chain.from_iterable(iterable) ähnelt Chain, empfängt jedoch nur ein einzelnes Iterable und kombiniert dann die Elemente in diesem Iterable zu einem Iterator.

>>> from itertools import chain

>>> list(chain.from_iterable(['ABC', 'DEF']))
['A', 'B', 'C', 'D', 'E', 'F']
Das Implementierungsprinzip ähnelt auch der Kette

def from_iterable(iterables):
    # chain.from_iterable(['ABC', 'DEF']) --> A B C D E F
    for it in iterables:
        for element in it:
            yield element
compress

compress(data, selectors) erhält zwei Iterables als Parameter und gibt nur die entsprechenden zurück in Selektoren Daten, deren Element True ist, werden gestoppt, wenn einer der Daten/Selektoren erschöpft ist.

>>> list(compress([1, 2, 3, 4, 5], [True, True, False, False, True]))
[1, 2, 5]
zip_longest

zip_longest(*iterables, fillvalue=None) ähnelt zip, der Nachteil von zip besteht jedoch darin, dass der gesamte Durchlauf durchgeführt wird, wenn ein bestimmtes Element in iterable durchlaufen wird Konkret sehen Sie sich bitte das folgende Beispiel für den Unterschied an

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)))
Das Folgende ist das Ausgabeergebnis

zip stops early:
[(0, 0), (1, 1)]

zip_longest processes all of the values:
[(0, 0), (1, 1), (2, None)]

islice

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

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

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

filterfalse(predicate, iterable) 与filter()相反,返回所有predicate返回False的元素。

itertools.filterfalse(is_even, itertools.count()) =>
1, 3, 5, 7, 9, 11, 13, 15, ...

takewhile

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

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

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

Combinatoric generators

product

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

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

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

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

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

itemgetter

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

methodcaller

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

References

DOCUMENTATION-FUNCTOOLS
DOCUMENTATION-ITERTOOLS
DOCUMENTATION-OPERATOR
HWOTO-FUNCTIONAL
HWOTO-SORTING
PYMOTW
FLENT-PYTHON


本文为作者原创,转载请先与作者联系。首发于我的博客

引言

functools, itertools, operator是Python标准库为我们提供的支持函数式编程的三大模块,合理的使用这三个模块,我们可以写出更加简洁可读的Pythonic代码,接下来我们通过一些example来了解三大模块的使用。

functools的使用

functools是Python中很重要的模块,它提供了一些非常有用的高阶函数。高阶函数就是说一个可以接受函数作为参数或者以函数作为返回值的函数,因为Python中函数也是对象,因此很容易支持这样的函数式特性。

partial

>>> from functools import partial

>>> basetwo = partial(int, base=2)

>>> basetwo('10010')
18

basetwo('10010') entspricht tatsächlich dem Aufruf von int('10010', base=2). Wenn die Funktion zu viele Parameter hat, können Sie functools.partial verwenden, um die Logik zu verbessern Die Lesbarkeit des Codes wird teilweise tatsächlich intern durch einen einfachen Abschluss implementiert.

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

Partialmethod ähnelt Partial, aber für 绑定一个非对象自身的方法 kann derzeit nur Partialmethod verwendet werden. Schauen wir uns den Unterschied zwischen den beiden im Folgenden an Beispiel .

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'

singledispatch

Obwohl Python keine Methoden mit demselben Namen unterstützt, die unterschiedliche Parametertypen zulassen, können wir singledispatch an 动态指定相应的方法所接收的参数类型 ausleihen, ohne Parameterbeurteilung in die interne Methode einzufügen Es wird eine Beurteilung vorgenommen, um die Lesbarkeit des Codes zu verringern.

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)

Im Folgenden werden @test_method.register(int) und @test_method.register(list) verwendet, um anzugeben, dass, wenn der erste Parameter von test_method int oder list ist, jeweils unterschiedliche Methoden zur Verarbeitung aufgerufen werden.

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

wraps

Der Dekorateur verliert die Attribute __name__ und __doc__ der dekorierten Funktion, die mit @wraps wiederhergestellt werden können.

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'

Wir können update_wrapper auch zum Umschreiben verwenden

from itertools import update_wrapper


def g():
    ...
g = update_wrapper(g, f)


# equal to
@wraps(f)
def g():
    ...

@wraps wird tatsächlich intern basierend auf update_wrapper implementiert.

def wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES):
    def decorator(wrapper):
        return update_wrapper(wrapper, wrapped=wrapped...)
    return decorator

lru_cache

lru_cache und singledispatch sind schwarze Magie, die in der Entwicklung weit verbreitet ist. Als nächstes werfen wir einen Blick auf lru_cache. Für sich wiederholende Rechenaufgaben ist es sehr wichtig, 缓存加速 zu verwenden. Nehmen wir ein Fibonacci-Beispiel, um den Geschwindigkeitsunterschied zwischen der Verwendung von lru_cache und der Nichtverwendung von lru_cache zu sehen.

# 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

Verwenden Sie nicht lru_cache

from clockdeco import clock


@clock
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-2) + fibonacci(n-1)


if __name__==&#39;__main__&#39;:
    print(fibonacci(6))

Das Folgende ist das laufende Ergebnis. Aus dem laufenden Ergebnis ist ersichtlich, dass fibonacci(n) sein wird <🎜 während der Rekursion >, was sehr zeit- und ressourcenintensiv ist. 重复计算

[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

Verwenden Sie 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__==&#39;__main__&#39;:
    print(fibonacci(6))
Die folgenden Ergebnisse werden im Cache abgelegt.

[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 
8
Die oben gewählte Zahl ist nicht groß genug. Interessierte Freunde möchten möglicherweise eine größere Zahl wählen, um den Geschwindigkeitsunterschied zwischen den beiden zu vergleichen

total_ordering

In In Python2 können Sie die Größe von Objekten vergleichen, indem Sie den Rückgabewert von __cmp__ 0/-1/1 anpassen. In Python3 wird __cmp__ aufgegeben, aber wir können total_ordering verwenden und dann __lt__(), __le__(), __gt__(), ändern. __ge__(), __eq__(), __ne__() und andere magische Methoden zum Anpassen der Vergleichsregeln von Klassen. p.s: Wenn Sie es verwenden, müssen Sie eine von __lt__(), __le__(), __gt__(), __ge__() in der Klasse definieren und der Klasse eine __eq__()-Methode hinzufügen.

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&#39;, &#39;a <= b&#39;, &#39;a == b&#39;, &#39;a >= b', 'a > b']:
    print('\n{:<6}:&#39;.format(expr))
    result = eval(expr)
    print(&#39;  result of {}: {}&#39;.format(expr, result))
Das Folgende sind die laufenden Ergebnisse:

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: False
Verwendung von itertools

itertools bietet uns sehr nützliche Funktionen für den Betrieb iterativer Objekte.

Unendlicher Iterator

count

count(start=0, step=1) gibt einen unendlichen ganzzahligen Iterator zurück, der sich jedes Mal um 1 erhöht. Optional können Sie eine Startnummer angeben, die standardmäßig 0 ist.

>>> from itertools import count

>>> for i in zip(count(1), ['a', 'b', 'c']):
...     print(i, end=' ')
...
(1, 'a') (2, 'b') (3, 'c')
cycle

cycle(iterable) wiederholt eine eingehende Sequenz unendlich, aber Sie können die Anzahl der Wiederholungen angeben, indem Sie einen zweiten Parameter angeben.

>>> 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')
repeat

repeat(object[, times]) gibt einen Iterator zurück, dessen Elemente unendlich oft wiederholt werden. Sie können einen zweiten Parameter angeben, um die Anzahl der Wiederholungen zu begrenzen.

>>> 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-over
Iteratoren, die auf der kürzesten Eingabesequenz enden

accumulate

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

itertools.chain(*iterables) kann mehrere Iterables in einem Iterator kombinieren

>>> from itertools import chain

>>> list(chain([1, 2, 3], ['a', 'b', 'c']))
[1, 2, 3, 'a', 'b', 'c']
Das Implementierungsprinzip der Kette ist wie folgt

def chain(*iterables):
    # chain('ABC', 'DEF') --> A B C D E F
    for it in iterables:
        for element in it:
            yield element
chain.from_iterable

chain.from_iterable(iterable) ähnelt Chain, empfängt jedoch nur ein einzelnes Iterable und kombiniert dann die Elemente in diesem Iterable zu einem Iterator.

>>> from itertools import chain

>>> list(chain.from_iterable(['ABC', 'DEF']))
['A', 'B', 'C', 'D', 'E', 'F']
Das Implementierungsprinzip ähnelt auch der Kette

def from_iterable(iterables):
    # chain.from_iterable(['ABC', 'DEF']) --> A B C D E F
    for it in iterables:
        for element in it:
            yield element
compress

compress(data, selectors) erhält zwei Iterables als Parameter und gibt nur die entsprechenden zurück in Selektoren Daten, deren Element True ist, werden gestoppt, wenn einer der Daten/Selektoren erschöpft ist.

>>> list(compress([1, 2, 3, 4, 5], [True, True, False, False, True]))
[1, 2, 5]
zip_longest

zip_longest(*iterables, fillvalue=None) ähnelt zip, der Nachteil von zip besteht jedoch darin, dass der gesamte Durchlauf durchgeführt wird, wenn ein bestimmtes Element in iterable durchlaufen wird Konkret sehen Sie sich bitte das folgende Beispiel für den Unterschied an

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)))
Das Folgende ist das Ausgabeergebnis

zip stops early:
[(0, 0), (1, 1)]

zip_longest processes all of the values:
[(0, 0), (1, 1), (2, None)]
islice

islice(iterable, stop) oder islice (iterable, start, stop[ , step]) ähnelt in gewisser Weise dem String- und Listen-Slicing in Python, mit der Ausnahme, dass für Start, Start und Schritt keine negativen Werte verwendet werden können.

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

tee(iterable, n=2) gibt n unabhängige Iteratoren zurück, n ist standardmäßig 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
Das Folgende ist das Ausgabeergebnis. Beachten Sie, dass r nach tee(r) als Iterator ungültig geworden ist, sodass die for-Schleife keinen Ausgabewert hat.

i1: [0, 1, 2, 3, 4]
i2: [0, 1, 2, 3, 4]
starmap

starmap(func, iterable) geht davon aus, dass iterable einen Strom von Tupeln zurückgibt und ruft func mit diesen Tupeln als Argumenten auf:

>>> 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) Das Gegenteil von filter() gibt alle Elemente zurück, für die das Prädikat „False“ zurückgibt.

itertools.filterfalse(is_even, itertools.count()) =>
1, 3, 5, 7, 9, 11, 13, 15, ...

takewhile

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

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

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

Combinatoric generators

product

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

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

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

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

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

itemgetter

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

methodcaller

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中文网!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn