Отложить выполнение функции синтаксически - PullRequest
0 голосов
/ 16 мая 2018

У меня есть довольно обширный инструмент моделирования, написанный на python, который требует от пользователя вызова функций для настройки среды в строгом порядке, поскольку np.ndarrays сначала создаются (и изменяются путем добавления и т. Д.) и после этого определяются представления памяти конкретных ячеек этих массивов.
В настоящее время для каждой части среды требуется около 4 различных вызовов функций, с простыми >> 100 частями.
Таким образом, мне нужно объединить вызовы функций каждой части путем синтаксического (не основанного на таймерах) отсрочки выполнения некоторых функций до тех пор, пока не будут выполнены все предыдущие функции, при этом сохраняя строгий порядок, позволяющий использовать представления памяти .

Кроме того, все функции, вызываемые пользователем, используют PEP 3102 аргументы в стиле только для ключевых слов, чтобы уменьшить вероятность ошибок ввода и все они методы экземпляра с self в качестве первого параметра, с self, содержащим ссылки на массивы для построения представлений памяти.

Моя текущая реализация использует list s для хранения функций и dict для аргументов ключевых слов каждой функции. Это показано здесь, опуская параметры класса и self, чтобы сделать его коротким:

def fun1(*, x, y):  # easy minimal example function 1
    print(x * y)
def fun2(*, x, y, z):  # easy minimal example function 2
    print((x + y) / z)

fun_list = []  # list to store the functions and kwargs
fun_list.append([fun1, {'x': 3.4, 'y': 7.0}])  # add functions and kwargs
fun_list.append([fun2, {'x':1., 'y':12.8, 'z': np.pi}])
fun_list.append([fun2, {'x':0.3, 'y':2.4, 'z': 1.}])

for fun in fun_list:
    fun[0](**fun[1])

Я хотел бы реализовать использование decorator, чтобы отложить выполнение функции, добавив generator, чтобы иметь возможность передавать все аргументы функциям при их вызове, но не выполнять их, как показано ниже:

def postpone(myfun):  # define generator decorator
    def inner_fun(*args, **kwargs):
        yield myfun(*args, **kwargs)
    return inner_fun

fun_list_dec = []  # list to store the decorated functions
fun_list_dec.append(postpone(fun1)(x=3.4, y=7.0))  # add decorated functions
fun_list_dec.append(postpone(fun2)(x=1., y=12.8, z=np.pi))
fun_list_dec.append(postpone(fun2)(x=0.3, y=2.4, z=1.))

for fun in fun_list_dec:  # execute functions
    next(fun)

Какой самый лучший (самый питонический) метод для этого? Есть ли недостатки?
И самое главное: мои ссылки на np.ndarrays, переданные функциям в self, все еще будут ссылками, так что адреса памяти этих массивов будут по-прежнему правильными при выполнении функций, , если адреса памяти изменить между сохранить вызовы функций в списке (или оформить их) и выполнить их?
Скорость выполнения здесь не имеет значения.

Ответы [ 2 ]

0 голосов
/ 16 мая 2018

Поскольку комментировать код будет слишком сложно и он основан на ответе juanpa.arrivillaga, я добавлю полный пост с кратким объяснением того, что я имею в виду, обновив ссылку на массивы:

def fun1(*, x, y):  # easy minimal example function 1
    print(x * y)

arr = np.random.rand(5)
f1_lam = lambda:fun1(x=arr, y=5.)
f1_par = partial(fun1, x=arr, y=5.)
f1_lam()  # Out[01]: [0.55561103 0.9962626  3.60992174 2.55491852 3.9402079 ]
f1_par()  # Out[02]: [0.55561103 0.9962626  3.60992174 2.55491852 3.9402079 ]

# manipulate array so that the memory address changes and
# passing as reference is "complicated":
arr = np.append(arr, np.ones((2,1)))
f1_lam()  # Out[03]: [0.55561103 0.9962626  3.60992174 2.55491852 3.9402079  5. 5.]
f1_par()  # Out[02]: [0.55561103 0.9962626  3.60992174 2.55491852 3.9402079 ]

Поведение lambda именно то, что я искал в этом вопросе.

Мои примеры с dict и decorator s не работают, также как и functools.partial. Есть идеи, почему lambda работает? И просто из интереса: есть ли способ обновить ссылки на массивы в dict, чтобы он также работал таким образом?

0 голосов
/ 16 мая 2018

Использование генераторов здесь не имеет особого смысла. Вы по сути имитируете частичное применение. Следовательно, это похоже на сценарий использования functools.partial. Так как вы придерживаетесь аргументов только по ключевым словам, это будет прекрасно работать:

In [1]: def fun1(*, x, y):  # easy minimal example function 1
   ...:     print(x * y)
   ...: def fun2(*, x, y, z):  # easy minimal example function 2
   ...:     print((x + y) / z)
   ...:

In [2]: from functools import partial

In [3]: fun_list = []

In [4]: fun_list.append(partial(fun1, x=3.4, y=7.0))

In [5]: fun_list.append(partial(fun2, x=1., y=12.8, z=3.14))

In [6]: fun_list.append(partial(fun2, x=0.3, y=2.4,z=1.))

In [7]: for f in fun_list:
   ...:     f()
   ...:
23.8
4.3949044585987265
2.6999999999999997

У вас нет для использования functools.partial, вы можете подать частичное заявление "вручную", просто чтобы продемонстрировать:

In [8]: fun_list.append(lambda:fun1(x=5.4, y=8.7))

In [9]: fun_list[-1]()
46.98
...