Выражения псевдонима внутри Python

Я часто нахожу, что хочу писать описания списков 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]

У меня есть два вопроса:

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

но это не сработает. Есть ли веская причина не поддерживать этот синтаксис? Можно это можно сделать с помощью этого? Или мне просто нужно использовать несколько listcomps или for-loop?

Ответ 1

Что касается №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 используйте выражения генератора так, чтобы им не нужно было фактически создавать списки и просто оцениваться при необходимости.

Ответ 2

У меня есть hack, чтобы создавать псевдонимы внутри понимания списка /dict. Вы можете использовать трюк 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)

Второй вариант будет перебирать только список размером 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

Ответ 3

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

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

Ответ 4

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

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

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