Возник вопрос — можно ли написать функцию в Питоне, которая будет себя вести, в зависимости от аргумента, либо как генератор, либо как функция с коллбэком?
Что-то вроде:
где "что-то" — это либо yield x, если cb is None, либо cb(x)
Соответственно, я ее зову либо
for x in f(): print(x)
либо
f(cb=print)
и в том, и в другом случае она должна напечатать 1 и 2.
Функция f длинная, поэтому писать две версии, различающихся только yield/cb, не хочется (ну и вообще, DRY)
Интересует не только Python3, но и Python2, потому что основная кодовая база до сих пор на 2.7.
(Сейчас складывается впечатление, что если в тексте функции есть yield, то функция автоматически становится генератором, независимо ни от чего вообще, даже если этот yield гарантированно ни разу не позовется)
Здравствуйте, koenjihyakkei, Вы писали:
K>Можно сделать враппер
Да, я сейчас именно так и делаю (у меня это обобщенная функция call_with_cb(cb, f, *args, **kwargs), которая работает с любой функцией-генератором), но это внешнее, а хотелось бы что-то внутри самой функции написать
Здравствуйте, jazzer, Вы писали:
J>Здравствуйте, koenjihyakkei, Вы писали:
K>>Можно сделать враппер
J>Да, я сейчас именно так и делаю (у меня это обобщенная функция call_with_cb(cb, f, *args, **kwargs), которая работает с любой функцией-генератором), но это внешнее, а хотелось бы что-то внутри самой функции написать
причем желательно, чтобы это "что-то" из исходного поста выглядело как вызов функции (а не как yield).
То есть чтобы человек, не видевший никогда в глаза yield, не испугался бы и думал, что он зовет коллбэк.
Здравствуйте, jazzer, Вы писали:
J>(Сейчас складывается впечатление, что если в тексте функции есть yield, то функция автоматически становится генератором, независимо ни от чего вообще, даже если этот yield гарантированно ни разу не позовется)
Здравствуйте, jazzer, Вы писали:
J>Здравствуйте, koenjihyakkei, Вы писали:
K>>Можно сделать враппер
J>Да, я сейчас именно так и делаю (у меня это обобщенная функция call_with_cb(cb, f, *args, **kwargs), которая работает с любой функцией-генератором), но это внешнее, а хотелось бы что-то внутри самой функции написать
Казалось бы, чтобы снаружи не писать call_with_cb, придумали декораторы:
def with_optional_callback(callback_argument_name):
def decorator(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
cb = kwargs.pop(callback_argument_name, None)
seq = f(*args, **kwargs)
if cb is None:
return seq
else:
for item in seq:
cb(item)
return wrapper
return decorator
@with_optional_callback("cb")
def f():
x=1
yield x
x=2
yield x
Теперь вызов f() вернёт генератор, а вызов f(cb=print) позовёт callback.
Re[4]: [Python] Функция двойного назначения (generator/callback)
Здравствуйте, watchmaker, Вы писали:
W>Здравствуйте, jazzer, Вы писали:
J>>Здравствуйте, koenjihyakkei, Вы писали:
K>>>Можно сделать враппер
J>>Да, я сейчас именно так и делаю (у меня это обобщенная функция call_with_cb(cb, f, *args, **kwargs), которая работает с любой функцией-генератором), но это внешнее, а хотелось бы что-то внутри самой функции написать
W>Казалось бы, чтобы снаружи не писать call_with_cb, придумали декораторы
Да, у меня такое есть уже. Хотелось избавиться от yield, чтоб вместо yield был типа вызов коллбека.
Я так понимаю, это невозможно по дизайну языка, чисто потому, что есть inspect.isgeneratorfunction.
J>Да, у меня такое есть уже. Хотелось избавиться от yield, чтоб вместо yield был типа вызов коллбека.
J>Я так понимаю, это невозможно по дизайну языка, чисто потому, что есть inspect.isgeneratorfunction.
Проблема в другом: если это callback, то функция f может его куда-то передать (например, в недра 3rd-party библиотеки, которая не обязательно даже на чистом python написана). Аналогично, полученный генератор тоже можно куда-то передать (в недра другой 3rd-party библиотеки).
Соответственно, каждый вызов next() от генератора должен приводить к переключению из одной части несвязанного кода в другую и обратно. Можно сказать, что нужно иметь одновременно два стека вызовов. В одном потоке CPython так не умеет.
Но это легко делается либо в Stackless Python: нет стека — нет проблем :)
Либо это делается запуском функции в отдельном потоке: два потока — два стека.
(во втором случае, просто создаём очередь из одного элемента, запускаем функцию в потоке и в качестве коллбека передаём метод "положи в очередь", после выхода из функции очередь закрываем; в основном же потоке сразу же возвращаем генератор, который вычитывает элементы из очереди пока они не закончатся)