Могу ли я использовать псевдонимы в списках Python для предотвращения их многократного вычисления? - PullRequest
16 голосов
/ 30 января 2011

Мне часто хочется написать такие списки Python:

nearbyPoints = [(n, delta(n,x)) for n in allPoints if delta(n,x)<=radius]

Это, надеюсь, дает некоторый контекст относительно того, почему я хотел бы сделать это, но бывают и случаи, когда нужно несколько значенийвычисляться / сравниваться для каждого элемента:

newlist = [(x,f(x),g(f(x))) for x in bigList if f(x)<p and g(f(x))<q]

Итак, у меня два вопроса:

  1. будут ли все эти функции оцениваться несколько раз или результат кэшируется?Язык указывает или это зависит от реализации?Я использую 2.6 сейчас, но будет ли 3.x другим?
  2. Есть ли более аккуратный способ написать это?Иногда f и g являются длинными выражениями, а дублирование подвержено ошибкам и выглядит грязно.Я действительно хотел бы иметь возможность написать это:
newList = [(x,a=f(x),b=g(a)) for x in bigList if a<p and b<q]

, но это не работает.Есть ли веская причина не поддерживать этот синтаксис?Можно ли это сделать через что-то вроде this ?Или мне просто нужно использовать несколько списков или цикл for?

Ответы [ 4 ]

11 голосов
/ 19 августа 2015

У меня есть hack для создания псевдонимов в списках / словах.Вы можете использовать трюк for alias_name in [alias_value].Например, у вас есть эта дорогая функция:

def expensive_function(x):
    print("called the very expensive function, that will be $2")
    return x*x + x

И некоторые данные:

data = [4, 7, 3, 7, 2, 3, 4, 7, 3, 1, 1 ,1]

И затем вы хотите применить дорогую функцию к каждому элементу, а также фильтровать на основе этого.То, что вы делаете:

result = [
    (x, expensive)
    for x in data
    for expensive in [expensive_function(x)] #alias
    if expensive > 3
]

print(result)

Second-for будет перебирать только список размера 1, фактически превращая его в псевдоним.Вывод покажет, что дорогая функция вызывается 12 раз, ровно один раз для каждого элемента данных.Тем не менее, результат функции используется (самое большее) дважды, один раз для фильтра и один раз возможно один раз для вывода.

Пожалуйста, всегда проверяйте компоновку таких пониманий, используя несколько строк, как я, идобавьте #alias к строке, где находится псевдоним.Если вы используете псевдоним, понимание становится довольно сложным, и вы должны помочь будущим читателям вашего кода получить то, что вы делаете.Вы знаете, это не perl;).

Для полноты выведите:

called the very expensive function, that will be $2
called the very expensive function, that will be $2
called the very expensive function, that will be $2
called the very expensive function, that will be $2
called the very expensive function, that will be $2
called the very expensive function, that will be $2
called the very expensive function, that will be $2
called the very expensive function, that will be $2
called the very expensive function, that will be $2
called the very expensive function, that will be $2
called the very expensive function, that will be $2
called the very expensive function, that will be $2
[(4, 20), (7, 56), (3, 12), (7, 56), (2, 6), (3, 12), (4, 20), (7, 56), (3, 12)]

Код: http://ideone.com/7mUQUt

10 голосов
/ 30 января 2011

Что касается # 1, да, они будут оцениваться несколько раз.

Что касается # 2, способ сделать это состоит в том, чтобы вычислить и отфильтровать в отдельных пониманиях:

сокращенная версия:

[(x,fx,gx) for (x,fx,gx) in ((x,fx,g(fx)) for (x,fx) in ((x,f(x)) for x in bigList) if fx < p) if gx<q]

Более длинная версия расширена, чтобы было легче следовать:

[(x,f,g) for (x,f,g) in
  ((x,f,g(f)) for (x,f) in
     ((x,f(x)) for x in bigList)
  if f < p)
if g<q]

Это вызовет f и g как можно меньшее количество раз (значения для каждого f(x) не < p никогда не вызовут g, а f будет вызываться только один раз для каждого значения в bigList).

Если вы предпочитаете, вы также можете получить более точный код, используя промежуточные переменные:

a = ( (x,f(x)) for x in bigList )
b = ( (x,fx,g(fx)) for (x,fx) in a if fx<p )
results = [ c for c in b if c[2] < q ] # faster than writing out full tuples

a и b используют выражения-генераторы, чтобы им не приходилось создавать экземпляры списков, а просто оцениваются при необходимости.

4 голосов
/ 10 февраля 2011

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

# First example
def getNearbyPoints(x, radius, points):
    """Yields points where 'delta(x, point) <= radius'"""
    for p in points:
        distance = delta(p, x)
        if distance <= radius:
            yield p, distance

nearbyPoints = list(getNearbyPoints(x, radius, allPoints))


# Second example
def xfg(data, p, q):
    """Yield 3-tuples of x, f(x), g(f(x))"""
    for x in data:
        f = f(x)
        if f < p:
            g = g(f)
            if g < q:
                yield x, f, g

newList = list(xfg(bigList, p, q))
3 голосов
/ 30 января 2011
  1. Если вы вызываете функцию дважды в выражении (в том числе в понимании списка), она действительно будет вызвана дважды.У Python нет возможности узнать, является ли ваша функция чистой или процедурной функцией.Он вызывает его, когда вы говорите, в этом случае дважды.

  2. Нет способа присвоить переменную в понимании списка, потому что в Python присваивание - это оператор, а невыражение.

Похоже, вы должны использовать полный цикл, а не понимание списка.

...