Метапрограммирование со словарным пониманием

#python #dataframe #list-comprehension #dictionary-comprehension

#python #фрейм данных #список-понимание #понимание словаря

Вопрос:

Итак, у меня есть куча функций, которые я хочу создать с разными параметрами. Один из параметров df будет предоставлен вызывающим эти функции. Я думал, что понял это, но когда я действительно использовал его, каждая созданная функция имела одинаковые параметры, последнюю комбинацию в последовательности понимания списка. странно.

 from itertools import product
feature_functions = {
    **{f'{col}{i}': lambda x: createFeature(df=x, i=i, col=col, name=f'{col}{i}')
        for col, i in product(['New', 'Lost', 'Change'], list(range(1, 31)))},
 

как я уже сказал, я подумал, что это довольно гладко, но когда я использовал это так:

 feature_functions['New1'](df)
 

Я получил этот результат, что означает, что он использовал «Изменение» и 30 для каждой лямбда-функции:

 # feature pd.Series:
0     NaN
      ...   
4593  1.002706
Name: Change30, Length: 4594, dtype: float64
 

Я попробовал несколько вещей, но ничего не изменилось. Как я неправильно использую это понимание словаря?

РЕДАКТИРОВАТЬ: Кстати, одна вещь, которую я сделал, чтобы убедиться, что это правильно, это поместить lambda x: ... в кавычки, затем я мог просто распечатать все это, и это выглядело довольно хорошо. итак, каким-то образом лямбда-выражение мешает? Я попытался обернуть его, (lambda x: ...) но это ничего не дало.

 {'New1': "lambda x: createFeature(df=x, i=1, col=New, name='New1')",
 'New2': "lambda x: createFeature(df=x, i=2, col=New, name='New2')",
 'New3': "lambda x: createFeature(df=x, i=3, col=New, name='New3')",
 'New4': "lambda x: createFeature(df=x, i=4, col=New, name='New4')",
 ... 
}
 

Комментарии:

1. Хотелось бы, чтобы у меня было больше времени, чтобы глубже разобраться в этом, потому что это кажется интересным. Я бы предположил, что это как-то связано с тем, что закрытие неправильно фиксирует измененные значения. Возможно, использование functools.partial может быть способом заставить это работать?

Ответ №1:

Вы действительно близки. Здесь не используйте lambda , а partial функционируйте из functools модуля:

 # dummy function
def createFeature(df, i, col, name):
    print(df)
    print(i, col, name)

feature_functions = {
    **{f'{col}{i}': partial(createFeature, i=i, col=col, name=f'{col}{i}')
        for col, i in product(['New', 'Lost', 'Change'], list(range(1, 31)))}}
 

Использование:

 >>> feature_functions['New1'](pd.DataFrame)
Empty DataFrame
Columns: []
Index: []
1 New New1

>>> feature_functions['Lost23'](pd.DataFrame())
Empty DataFrame
Columns: []
Index: []
23 Lost Lost23

>>> feature_functions['Change12'](pd.DataFrame())
Empty DataFrame
Columns: []
Index: []
12 Change Change12
 

Ответ №2:

Хорошо, это очень интересно. Если вы создаете функцию, которая возвращает ваш лямбда, например:

 def createFeatureCreator(i, col):
   return lambda x: createFeature(df=x, i=i, col=col, name=f'{col}{i}')
 

и сделайте свое понимание (я удалил много ложных вещей, которые у вас были):

 feature_functions = {f'{col}{i}': createFeatureCreator(i, col)
        for col, i in product(['New', 'Lost', 'Change'], range(1, 31))}
 

это работает так, как вы и ожидали.

Причина, по которой конструкция «лямбда» не работает напрямую, на самом деле очень интересна: лямбда захватывает среду. Понимание dict — это единая среда, в которой переменные i и col изменяются на каждой итерации цикла. Когда создается лямбда-выражение (а на самом деле создается 93 разных лямбда-выражения), все они захватывают одну и ту же среду, поэтому при их выполнении значения i и col являются последним значением, которое они имели в среде (f-строка расширяется до вызова функции, который не выполняется, потому что он находится внутрилямбда-выражение, и оно выполняется только тогда, когда вы на самом деле вызываете функцию, поэтому name также кажется «неправильным»).

Комментарии:

1. Я предпочитаю этот ответ, потому что это просто родной python (без импорта partial из functools). Изолируйте лямбда. Я думаю, что большинство людей предпочитают частичный вариант, утверждая, что он более «питонический», поскольку он был создан именно для такого рода вещей. Однако ваш мне нравится больше, потому что я использовал его постоянно, а затем, работая на других языках, в которых не было такой концепции, но были лямбда-или встроенные функции, я сначала не был уверен, что делать, поскольку я использовал partial в качестве опоры.

2. о, кстати, я нашел еще один безумный способ сделать это {f'{col}{i}': eval(f"lambda x: createFeature(df=x, i={i}, col='{col}', name='{col}{i}')") for ...}

3. @LegitStack Это тоже работает, поскольку лямбда-выражение ничего не использует из среды понимания, но я бы максимально избегал использования оценок. Они, как правило, опасны, помимо того, что имеют плохую производительность (каждая итерация цикла eval будет анализироваться).