Вопрос по python – Когда «starmap» может быть предпочтительнее, чем «понимание списка»

6

Отвечая на вопросНеуклюжий расчет различий между возрастающим набором чисел, есть ли более красивый способ?Я придумал два решения, одно сList Comprehension и другое использованиеitertools.starmap.

Мне,list comprehension Синтаксис выглядит более ясным, читабельным, менее многословным и более Pythonic. Но все же какstarmap Мне было интересно, должна быть причина для этого.

Мой вопрос когдаstarmap может быть предпочтительнее, чемList Comprehension?

Note Если это вопрос стиля, то это определенно противоречитThere should be one-- and preferably only one --obvious way to do it.

Head to Head Comparison

Readability counts. --- LC

Это снова вопрос восприятия, но для меняLC более читабельно, чемstarmap. To use starmapЛибо вам нужно импортироватьoperatorили определитьlambda или какой-то явныйmulti-variable функция и тем не менее дополнительный импорт изitertools.

Performance --- LC

>>> def using_star_map(nums):
    delta=starmap(sub,izip(nums[1:],nums))
    return sum(delta)/float(len(nums)-1)
>>> def using_LC(nums):
    delta=(x-y for x,y in izip(nums[1:],nums))
    return sum(delta)/float(len(nums)-1)
>>> nums=[random.randint(1,10) for _ in range(100000)]
>>> t1=Timer(stmt='using_star_map(nums)',setup='from __main__ import nums,using_star_map;from itertools import starmap,izip')
>>> t2=Timer(stmt='using_LC(nums)',setup='from __main__ import nums,using_LC;from itertools import izip')
>>> print "%.2f usec/pass" % (1000000 * t1.timeit(number=1000)/100000)
235.03 usec/pass
>>> print "%.2f usec/pass" % (1000000 * t2.timeit(number=1000)/100000)
181.87 usec/pass
Возможно, вы захотите опубликовать пример кода, который не требует обширного исправления, прежде чем его можно будет использовать. Marcin
@jamylak: Спасибо, что указали на это. Но, к сожалению, это не меняет разницу в производительности. Abhijit
Я не думаю, что было бы справедливо сравнивать их так, как вы. Обе функции должны сохранять различия вdeltas потому что на данный моментusing_star_map менее читабелен, так как все в одной строке. Измените это на:deltas = starmap(sub,zip(nums[1:],nums)) sum(deltas)/float(len(nums)-1) jamylak
Это не было предназначено, но мы также говорим о читабельности. jamylak

Ваш Ответ

3   ответа
12

map()/starmap() наиболее уместны, когда вы буквально просто вызываете функцию для каждого элемента в списке. В этом случае они немного понятнее:

(f(x) for x in y)
map(f, y) # itertools.imap(f, y) in 2.x

(f(*x) for x in y)
starmap(f, y)

Как только вы начнете нуждаться вlambda или жеfilter Кроме того, вам следует переключиться на выражение списка компоновщика / генератора, но в случаях, когда оно представляет собой единственную функцию, синтаксис кажется очень многословным для выражения генератора понимания списка.

Oниare взаимозаменяемы и, в случае сомнений, придерживайтесь выражения генератора, так как оно более читабельно в целом, но в простом случае (map(int, strings), starmap(Vector, points)) с помощьюmap()/starmap()  иногда может сделать вещи проще для чтения.

Example:

Пример, где я думаюstarmap() более читабельно:

from collections import namedtuple
from itertools import starmap

points = [(10, 20), (20, 10), (0, 0), (20, 20)]

Vector = namedtuple("Vector", ["x", "y"])

for vector in (Vector(*point) for point in points):
    ...

for vector in starmap(Vector, points):
    ...

И дляmap():

values = ["10", "20", "0"]

for number in (int(x) for x in values):
    ...

for number in map(int, values):
    ...
Performance:
python -m timeit -s "from itertools import starmap" -s "from operator import sub" -s "numbers = zip(range(100000), range(100000))" "sum(starmap(sub, numbers))"                         
1000000 loops, best of 3: 0.258 usec per loop

python -m timeit -s "numbers = zip(range(100000), range(100000))" "sum(x-y for x, y in numbers)"                          
1000000 loops, best of 3: 0.446 usec per loop

Для построенияnamedtuple:

python -m timeit -s "from itertools import starmap" -s "from collections import namedtuple" -s "numbers = zip(range(100000), reversed(range(100000)))" -s "Vector = namedtuple('Vector', ['x', 'y'])" "list(starmap(Vector, numbers))"
1000000 loops, best of 3: 0.98 usec per loop

python -m timeit -s "from collections import namedtuple" -s "numbers = zip(range(100000), reversed(range(100000)))" -s "Vector = namedtuple('Vector', ['x', 'y'])" "[Vector(*pos) for pos in numbers]"
1000000 loops, best of 3: 0.375 usec per loop

В моих тестах, где мы говорим об использовании простых функций (нетlambda), starmap() быстрее, чем эквивалентное выражение генератора. Естественно, производительность должна занимать место для удобочитаемости, если только она не является проверенным узким местом.

Пример того, какlambda убивает любой прирост производительности, такой же пример, как в первом наборе, но сlambda вместоoperator.sub():

python -m timeit -s "from itertools import starmap" -s "numbers = zip(range(100000), range(100000))" "sum(starmap(lambda x, y: x-y, numbers))" 
1000000 loops, best of 3: 0.546 usec per loop
@akaRem Lattyware всегда использует python 3.
map(f, y) эквивалентно для[f(x) for x in y] и не для(f(x) for x in y)потому что это не генератор. Это выполняется сразу.
@ Lattyware: я согласен с картой, но когда вы имеете дело сstarmapвообще говоря, что включает в себя дополнительныеimport, может бытьmultivariable function definition если неlambda и тем не менее в конечном итоге импортoperatorsи мой текущий вопрос заключается в удобстве использованияstarmap. Abhijit
@Abhijit Я добавил пример варианта использования дляstarmap(), Как я говорю, где вам нужноlambda ты толкнулmap()/starmap() помимо того, что они предназначены для использования, и вы должны переключиться на выражение генератора.
@akaRem Извините, я говорю о Python 3.x - действительно, в 2.x это правда. Обновлено, чтобы уточнить.
0

About Starmap.. L = [(0,1,2),(3,4,5),(6,7,8),..].

Генерация генератора будет выглядеть так

(f(a,b,c) for a,b,c in L)

или же

(f(*item) for item in L) 

И starmap будет выглядеть так

starmap(f, L)

Третий вариант легче и короче. Но первый очень очевиден, и он не заставляет меня думать, что он делает.

Хорошо. Теперь я хочу написать более сложный встроенный код ..

some_result = starmap(f_res, [starmap(f1,L1), starmap(f2,L2), starmap(f3,L3)])

Эта строка не очевидна, но все же легко понять .. В генерации генератора это будет выглядеть так:

some_result = (f_res(a,b,c) for a,b,c in [(f1(a,b,c) for a,b,c in L1), (f2(a,b,c) for a,b,c in L2), (f3(a,b,c) for a,b,c in L3)])

Как видите, он длинный, тяжелый для понимания и не может быть помещен в одну строку, потому что его длина превышает 79 символов (PEP 8). Еще более короткий вариант плох

some_result = (f_res(*item) for item [(f1(*item) for item in L1), (f(*item2) for item in L2), (f3(*item) for item in L3)])

Слишком много символов ... Слишком много скобок ... Слишком много шума.

So. Starmap in some cases is a very useful tool. With it you can write less code that is simpler to understand.

EDIT добавлены некоторые фиктивные тесты

from timeit import timeit
print timeit("from itertools import starmap\nL = [(0,1,2),(3,4,5),(6,7,8)]\nt=list((max(a,b,c)for a,b,c in L))")
print timeit("from itertools import starmap\nL = [(0,1,2),(3,4,5),(6,7,8)]\nt=list((max(*item)for item in L))")
print timeit("from itertools import starmap\nL = [(0,1,2),(3,4,5),(6,7,8)]\nt=list(starmap(max,L))")

выходы (python 2.7.2)

5.23479851154
5.35265309689
4.48601346328

Таким образом, starmap здесь даже на 15% быстрее.

Я бы сказал, что в вашем более сложном случае обаstarmap() и выражения генератора не являются хорошим решением. В этот момент более читабельно можно развернуть ваш код (например, в полноценный генератор).
Это нормально, тогда я это исправлю.
Английский не мой родной язык ... Я имею в виду, что есть много разных персонажей, которые создают визуальный шум. И поэтому этот код сложен даже для простого чтения (без попыток понять).
«Слишком много шума» грамматическая ошибка или это намеренно?
3

который вы найдете более читабельным.

В отношении «есть только один способ сделать это», Свен Марнах любезно предоставил этоЦитата Гвидо:

“You may think this violates TOOWTDI, but as I've said before, that was a white lie (as well a cheeky response to Perl's slogan around 2000). Being able to express intent (to human readers) often requires choosing between multiple forms that do essentially the same thing, but look different to the reader.”

В горячей точке производительности вы можете выбрать решение, которое работает быстрее всего (что, я думаю, в этом случае будетstarmap основанный один).

По производительности - starmap работает медленнее из-за его разрушения; однако starmap здесь не нужен:

from timeit import Timer
import random
from itertools import starmap, izip,imap
from operator import sub

def using_imap(nums):
    delta=imap(sub,nums[1:],nums[:-1])
    return sum(delta)/float(len(nums)-1)

def using_LC(nums):
    delta=(x-y for x,y in izip(nums[1:],nums))
    return sum(delta)/float(len(nums)-1)

nums=[random.randint(1,10) for _ in range(100000)]
t1=Timer(stmt='using_imap(nums)',setup='from __main__ import nums,using_imap')
t2=Timer(stmt='using_LC(nums)',setup='from __main__ import nums,using_LC')

На моем компьютере:

>>> print "%.2f usec/pass" % (1000000 * t1.timeit(number=1000)/100000)
172.86 usec/pass
>>> print "%.2f usec/pass" % (1000000 * t2.timeit(number=1000)/100000)
178.62 usec/pass

imap выходит чуть-чуть быстрее, вероятно, потому что он избегает архивирования / разрушения.

Просто замечание о производительности: если вы на самом деле вызываете уже существующую функцию,starmap кажется немного быстрее в моемtimeit тесты, но если вам нужно определитьlambda чтобы сделать это, выражение генератора почти наверняка победит, избегая вызовов функций.
@ Lattyware: Конечно, это универсальное правило может быть применено для объяснения любой несоответствия! :)
@SvenMarnach Или еще проще -'practicality beats purity', Всегда будет несколько способов сделать что-то - это реальность силы языка. Подлинное значение строки Zen of Python заключается в том, чтобы уменьшить помехи в языке и избежать бессмысленных синонимов.
@Abhijit Это хорошее пасхальное яйцо, и во многом это хороший совет («простой - лучше, чем сложный»), но только потому, что оно включено в интерпретатор, оно не превращает его в компьютерно-научный вид закон.
квотированиеGuido van Rossum: & # x201C; Вы можете подумать, что это нарушает TOOWTDI, но, как я уже говорил, это была ложная ложь (а также дерзкий ответ на лозунг Perl около 2000 года). Чтобы выразить свое намерение (читателям-людям), часто требуется выбор между несколькими формами, которые делают одно и то же, но выглядят по-разному для читателя. & # X201D;

Похожие вопросы