LINUX.ORG.RU

возврат значения из генератора

 , ,


0

3

Использую фреймворк cototb (это Python + FPGA). И есть такая проблема: для работы с временем, т.е. ожидания положительных фронтов клоков, требуется задействовать ключевое слово yield. Эта функция считается что она генератор. Насколько мне известно из примеров в инете, из генераторов значения возвращается тоже словом yield. Как же мне тогда вернуть значение?

@cocotb.coroutine
def is_ch_active(channel):
	active = False
	chan_0_adr = 0x20
	chan_size = 7
	dut.cs_b = 0
	dut.rd_b = 0
	n_wait = 3
	chan_id = channel
	base_addr = chan_0_adr + chan_id * chan_size
	dut.adr = (base_addr + 6) * 4 # channel enable
	for wait in range(n_wait):
		yield posedge(dut.itfc_clk)
	v = int(dut.dout)
	dut.cs_b = 1
	dut.rd_b = 1
	yield posedge(dut.itfc_clk)
	if v & (1<<0):
		active = True
	yield active
Пока что вышеприведенный код дает неверный результат:
is_active_before = misc.is_ch_active(channel)
is_active_after = misc.is_ch_active(channel)

is_active before= <cocotb.decorators.RunningCoroutine object at 0x2aaad013af10>, after= <cocotb.decorators.RunningCoroutine object at 0x2aaad013add0>

Нужно просто вернуть то значение active - True или False. А может есть способ это обойти?

Твой метод сейчас не возвращает значения, а возвращает генератор. Чтобы получить текущее значение надо использовать либо цикл, либо функцию next(), либо ещё чего.

В твоём случае должно сработать так:

g = misc.is_ch_active(channel)
is_active_before = next(g)
...

Если генератору больше нечего генерировать, то он бросает исключение StopIteration. В цикле оно корректно обрабатывается, но тебе, видимо, надо самому его оформлять.

anonymous ()
Ответ на: комментарий от anonymous
is_active_before = next(misc.is_ch_active(channel))
Send raised exception: RunningCoroutine object is not an iterator

Почему-то не подходит...

Однако если я пишут там return active то оно уже жалуется что нельзя вернуть из генератора... «Python SyntaxError: (“'return' with argument inside generator”,)»

Есть ли обходной путь? Может модифицировать глобальную переменную?

I-Love-Microsoft ★★★★★ ()
Ответ на: комментарий от I-Love-Microsoft

можно попробовать iter() но если уж это корутина, то вероятно можно поробовать val = yield from misc.is_ch_active(channel), хотя оно, конечно по-другому работает

Dred ★★★★★ ()

Глянул код этого cocotb и, если я правильно понял, coroutine — это обёртка, которая создаёт RunningCoroutine, который, в свою очередь, последовательно ожидает выполнения всех триггеров, возвращаемых из описанного тобой генератора.

У меня нет возможности протестировать это, но я бы сказал, что нужно через yield возвращать триггер (в твоём случае что-то вроде RisingEdge или какой-то свой, унаследованный от Trigger или _Edge)

Я бы сказал, что код будет примерно таким:

@cocotb.coroutine
def is_ch_active(channel):
	n_wait = 3
	for wait in range(n_wait):
		# signal = ?
		yield RisingEdge(signal)
	
	# v = ...
	if not v & (1<<0):
		raise TestFailure("Inactive")
grazor ()

Очень советую разобраться в корутинах, основанных на генераторах. Действовать на обум тут не поможет.

В твоем случае, судя по коду библиотеки, нужно метнуть exception

raise ReturnValue(active)
Vovka-Korovka ★★★★★ ()
Последнее исправление: Vovka-Korovka (всего исправлений: 1)
Ответ на: комментарий от I-Love-Microsoft

Однако если я пишут там return active то оно уже жалуется что нельзя вернуть из генератора...

В третьем можно, но это просто синтаксический сахар. На самом деле, просто метнется StopIteration(<something>). А во втором нужно явно кидать.

Vovka-Korovka ★★★★★ ()
Ответ на: комментарий от I-Love-Microsoft

val = yield from misc.is_ch_active(channel)

Yield from работает только в 3-ем питоне. Кроме того, чтобы получать значения через yield ты должен находиться в корутине.

Vovka-Korovka ★★★★★ ()
Ответ на: комментарий от I-Love-Microsoft

raise - это законно?

Да. Это стандартный способ возвращения значений из корутин, основанных на генераторах. В том же торнадо так и делается.

А как его с другой стороны ловить?

А тебе и не надо. За тебя это делает декоратор @coroutine.

Vovka-Korovka ★★★★★ ()
Ответ на: комментарий от Vovka-Korovka

Да, кстати, я прошляпил слово from, сейчас попробовал и во «втором питоне» вижу SyntaxError: invalid syntax там где from написал.

I-Love-Microsoft ★★★★★ ()
Ответ на: комментарий от I-Love-Microsoft

Судя по коду, да

Можно определить своё исключение для этого. Что-то вроде:

	if v & (1<<0):
		raise Exception(1)
	else:
		raise TestFailure(0)

А вызывать coroutine через call

is_active = misc.is_ch_active(channel)

try:
	is_active(*args, **kwargs)
except Exception as e:
	is_active_before = e.args[0]

grazor ()
Ответ на: комментарий от grazor

Не может найти args, ругается и выпадает с ошибкой.

@cocotb.coroutine
def is_ch_active(dut, report, channel):
	active = False
...
	if v & (1<<0):
		active = True
	report.write("is_ch_active %0d -> %r\n" % (channel, active))

	if active:
		raise Exception(1)
	else:
		raise Exception(0)

Пробовал так, но вот при таком вызове вываливается:

		is_active_before = False
		try:
			yield misc.is_ch_active(dut, report, channel)
		except Exception as e:
			is_active_before = e.args[0]
С ошибкой:
# 280.00ns ERROR    ..utine.is_ch_active.0x2aaad013bf50           result.py:51   in raise_error                     Send raised exception: 0
 File ".../cocotb/cocotb/decorators.py", line 114, in send
    return self._coro.send(value)
  File ".../misc.py", line 60, in is_ch_active
   raise Exception(0)
Иными словами, raise отрабатывает, но почему-тому вместо того чтобы молча записать нужный мне результат, оно не делает то что в «except Exception as e:» а вообще вываливается из всего теста. Мне кажется оно не поймало Exception...

I-Love-Microsoft ★★★★★ ()
Ответ на: комментарий от grazor

Сделал:

		is_active_before = False
		try:
			yield misc.is_ch_active(dut, report, channel)
		except Exception as e:
			is_active_before = e.retval

		yield misc.setup_target_n(dut, channel, target)

		is_active_after = False
		try:
			yield misc.is_ch_active(dut, report, channel)
		except Exception as e:
			is_active_after = e.retval

		yield misc.dump_regs(dut, report)
		report.write("is_active before= %r, after= %r\n" % (is_active_before, is_active_after))

is_ch_active 3 -> False
is_ch_active 3 -> True
is_active before= False, after= False

Видно что внутри функция is_ch_active видит что значение было False а затем стало True, однако через raise значение так и не доходит. Попробую отладочный вывод добавить, чтобы убедиться что оно заходит внутрь...

I-Love-Microsoft ★★★★★ ()
Ответ на: комментарий от grazor

Я предположил что если изменить так:

		is_active_before = False
		try:
			yield misc.is_ch_active(dut, report, channel)
		except ReturnValue as e:
			report.write("exception before %r\n" % e.retval)
			is_active_before = e.retval
т.е. вместо Exception поместить «except ReturnValue as e» то заработает. Однако вижу что в файле так и не появляется строчка «exception before», но всё что дальше - в файле появляется.

I-Love-Microsoft ★★★★★ ()
Последнее исправление: I-Love-Microsoft (всего исправлений: 1)
Ответ на: комментарий от I-Love-Microsoft

Он и ловится внутрях @coroutine, а значение потом сохраняется в объекте RunningCoroutine, который возвращается тебе. Насколько я понял по комментарию в коде, тебе нужно потом вызвать у возвращаемого объекта метод .join(), который вернет триггер, который сработает, когда корутина отработает и вернет значение.

Vovka-Korovka ★★★★★ ()
Ответ на: комментарий от I-Love-Microsoft

В том же месте, где проверяется тип исключения (я выше давал ссылку) происходит

self.retval = e.retval
raise CoroutineComplete(...)

И дальше это исключение не распространяется, да. Это значение можно достать из RunningCoroutine, который возвращается при вызове.

result = is_active(...).retval
grazor ()
Ответ на: комментарий от Virtuos86

Это не работает. В результате получаются

is_active before= <cocotb.triggers._RisingEdge object at 0x2aaad010be90>, after= <cocotb.triggers._RisingEdge object at 0x2aaad010be90>

Я предполагаю что раз там есть ТРИ вызова yield, без первых двух я обойтись никак не могу, то надо три раза вызвать next...

I-Love-Microsoft ★★★★★ ()

Dred Leron grazor Vovka-Korovka Virtuos86

Всех благодарю! Ваша помощь была очень важна чтобы разобраться с теме! Рабочее решение оказалось компиляцией всех предложенных способов. Я чисто как из «теоремы о бесконечном количестве мартышек» комбинируя различные способы допер до нижеследующего:

@cocotb.coroutine
def is_ch_active(dut, report, channel):
	active = False
...
	yield posedge(dut.itfc_clk)
	if v & (1<<0):
		active = True
	report.write("is_ch_active %0d -> %r\n" % (channel, active))

	if active:
		report.write("is_ch_active * 111\n")
		raise ReturnValue(True)
	else:
		report.write("is_ch_active * 000\n")
		raise ReturnValue(False)
		gen_before = misc.is_ch_active(dut, report, channel)
		yield gen_before
		is_active_before = gen_before.retval

		yield misc.setup_target_n(dut, channel, target)

		gen_after = misc.is_ch_active(dut, report, channel)
		yield gen_after
		is_active_after = gen_after.retval

		yield misc.dump_regs(dut, report)
		report.write("is_active before= %r, after= %r\n" % (is_active_before, is_active_after))
Почему-то это оказалось рабочим решением:

is_ch_active 9 -> False

is_ch_active * 000

is_ch_active 9 -> True

is_ch_active * 111

is_active before= False, after= True

Почему именно так работает?

I-Love-Microsoft ★★★★★ ()
Ответ на: комментарий от I-Love-Microsoft

gen_before = misc.is_ch_active(dut, report, channel) помещает инстанс класса (корутину) cocotb.RunningCoroutine в переменную.

yield gen_after — вызывает метод send корутины, передавая в него саму корутину.

    def send(self, value):
        try:
            return self._coro.send(value)
        ...
            raise
        except ReturnValue as e:
            self.retval = e.retval

Дальше, как видно, корутина (value) передается в изначальный генератор is_ch_active, вход происходит в точку, где находится первая инструкция yield (yield одновременно может отдавать значение из генератора, и принимать). Эта точка — yield posedge(dut.itfc_clk) (можно посмотреть:

    val = yield posedge(dut.itfc_clk)
    if val is not None: print val

).

Дальше доходит до первого raise ReturnValue... и т.д.

Virtuos86 ★★★★★ ()
Последнее исправление: Virtuos86 (всего исправлений: 1)
Ответ на: комментарий от I-Love-Microsoft

Однако если я пишут там return active то оно уже жалуется что нельзя вернуть из генератора... «Python SyntaxError: (“'return' with argument inside generator”,)»

Третий питон тебе нужен.

holuiitipun ()
12 октября 2016 г.

holuiitipun Virtuos86 grazor Vovka-Korovka cototb (Python + FPGA) поддерживает Python 3.x, там стало быть можно возвращать значения из генераторов без гемора? Я почему вспомнил, в текущем проекте много придется вызывать этих yield, приходится по три строчки писать вместо одной. Но может можно как-то в одну строку засунуть?

gen__reg2_0x38 = misc.read_reg(dut, 0x38)
yield gen__reg2_0x38
reg2_0x38 = int(gen__reg2_0x38.retval)
Хочется как-то исхитриться и сунуть это в функцию... Как сую в функцию, так оно на эту функцию жалуется что типа она тоже стала генератором.

I-Love-Microsoft ★★★★★ ()
Ответ на: комментарий от I-Love-Microsoft

Некропостер ты. У меня уже давно в памяти чистый лист, хотя я, как видно из комментариев, немного вникал.

Как сую в функцию, так оно на эту функцию жалуется что типа она тоже стала генератором.

Естественно: функция с yield это и есть генератор.

Может, вечером посмотрю, сейчас некогда.

Virtuos86 ★★★★★ ()
Ответ на: комментарий от Virtuos86

Может, вечером посмотрю, сейчас некогда.

Наверное, это скорее вопрос к языку, а не к фреймворку что я задействовал. В принципе всё работает, но надо как-то сократить код, сделать нечто вроде, оперируя терминами языка C++, шаблона или inline. Три строки засунуть в одну:

reg2_0x38 = int((yield (misc.read_reg(dut, 0x38))).retval)

I-Love-Microsoft ★★★★★ ()
		gen__reg_0x38 = misc.read_reg(dut, 0x38)
		yield gen__reg_0x38
		reg_0x38 = int(gen__reg_0x38.retval)

		reg2_0x38 = int((yield (misc.read_reg(dut, 0x38))).retval)

		dut.log.info("reg_0x38= %d, reg2_0x38= %d" % (reg_0x38, reg2_0x38))

Вторая версия (4-я строка) валится с ошибкой «Send raised exception: 'int' object has no attribute 'retval'». Однако, число скобок верное. Не хочет первая конструкция лезть в одну строку. Если принципиально эти строки не суются в одну, подошла бы функция, это даже более предпочтительно, но yield «заражает» и её...

I-Love-Microsoft ★★★★★ ()
Последнее исправление: I-Love-Microsoft (всего исправлений: 1)
Ответ на: комментарий от I-Love-Microsoft

там стало быть можно возвращать значения из генераторов без гемора?

return в генераторе в 3-ем питоне просто генерирует исключение StopIteration со значением, указанным в return.

Vovka-Korovka ★★★★★ ()
Ответ на: комментарий от I-Love-Microsoft

Ну так в четвертой строчке сначала скобочки отрабатывают, а у тебя к этому моменту gen__reg_0x38, видимо, еще просто обычный int. И только после yield он станет объектом.

Vovka-Korovka ★★★★★ ()
Ответ на: комментарий от I-Love-Microsoft
def subgen(dut):
    gen__reg2_0x38 = misc.read_reg(dut, 0x38)
    yield gen__reg2_0x38
    reg2_0x38 = int(gen__reg2_0x38.retval)
    return reg2_0x38

def foo():
    ...
    reg2_0x38 = yield from subgen(dut)
    ...

Я, конечно, сомневаюсь, но так не пойдёт?

Virtuos86 ★★★★★ ()
Ответ на: комментарий от Virtuos86
@cocotb.coroutine
def __read_reg(dut, reg):
	dut.cc.brdg_addr = reg
	dut.cc.brdg_rd_n = 0
	yield posedge(dut.clock100)
	dut.cc.brdg_rd_n = 1
	yield posedge(dut.clock100)
	yield posedge(dut.clock100)
	res = int(dut.cc.brdg_dout)
	raise ReturnValue(res)

def read_reg(dut, reg):
	gen = __read_reg(dut, reg)
	yield gen
	res = int(gen.retval)
	return res
		yield misc.write_reg(dut, 0x38, 555)
		reg_0x38 = yield from misc.read_reg(dut, 0x38)
		dut.log.info("NEW reg_0x38= %d" % (reg_0x38))
misc.py", line 37
    return res
SyntaxError: 'return' with argument inside generator

Оно все равно считает функцию read_reg генератором и не хочет чтобы оно выдавало ретурны...

Ладно, наверное не стоит упираться из за лишних строчек кода и избыточных переменных. Попробую потом перевести проект на Python 3.x

I-Love-Microsoft ★★★★★ ()
Ответ на: комментарий от I-Love-Microsoft

gen = __read_reg(dut, reg)

Матерь Божья, ты в генератор пихаешь еще один? В чем суть выделения отдельно __read_reg, почему его нельзя поместить в read_reg?

SyntaxError: 'return' with argument inside generator

Странная ошибка.

Virtuos86 ★★★★★ ()
Ответ на: комментарий от I-Love-Microsoft

Оно все равно считает функцию read_reg генератором и не хочет чтобы оно выдавало ретурны...

Еще раз: генератор — это функция, которая содержит инструкцию или выражение yield. Оно ничего не считает, ты сам написал генератор.

return res в read_reg нужен, потому что это будет последнее значение, которое выдаст итератор, полученный при вызове генератора. Почему так? Если ты не знал, в тело каждой функции при компиляции в байткод добавляется строка return None. Это нужно, чтобы каждая функция была функцией, т.е. возвращала хоть какой-то результат. Нельзя заменить

def read_reg(dut, reg):
	gen = __read_reg(dut, reg)
	yield gen
	res = int(gen.retval)
	return res
на
def read_reg(dut, reg):
	gen = __read_reg(dut, reg)
	yield gen
	res = int(gen.retval)
	yield res
потому что в итоге получится
def read_reg(dut, reg):
	gen = __read_reg(dut, reg)
	yield gen
	res = int(gen.retval)
	yield res
        return None
что в сочетании с yield from приведет к тому, что в reg_0x38 окажется не res, а None (если не вообще недоотработавший итератор, который поломает программу).

Virtuos86 ★★★★★ ()

SyntaxError: 'return' with argument inside generator

Ты точно на третьем питоне тестировал, не на 2.7, например?

Virtuos86 ★★★★★ ()
Ответ на: комментарий от Virtuos86
def subgen(dut):
    gen__reg2_0x38 = misc.read_reg(dut, 0x38)
    yield gen__reg2_0x38
    reg2_0x38 = int(gen__reg2_0x38.retval)
    return reg2_0x38

Я повторил этот пример :) Потому и удивился - один генератор обернули в еще один, получилось масляное масло :)

I-Love-Microsoft ★★★★★ ()
Последнее исправление: I-Love-Microsoft (всего исправлений: 1)
Ответ на: комментарий от I-Love-Microsoft

Извини, пожалуйста, но зачем ты тогда упоминал Python 3?)) Я написал под третий питон код, а ты запускаешь на втором.

Virtuos86 ★★★★★ ()
Ответ на: комментарий от I-Love-Microsoft

Мне-то откуда знать, что misc.read_reg у тебя — генератор? Я думал, функция, которая какой-то объект возвращает.

Virtuos86 ★★★★★ ()
Ответ на: комментарий от Virtuos86

К сожалению, у мну пока 2.7.11, мечтаю только о 3.x. А там в третьем питоняке твой пример кода заработал бы? А в 2.7 я могу только так обходиться?

Просто хочется свернуть работу с генератором (который читает что-то и возвращает) - в одну строку.

I-Love-Microsoft ★★★★★ ()
Ответ на: комментарий от I-Love-Microsoft

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

                yield misc.write_reg(dut, 0x38, 555)
		reg_0x38 = yield from misc.read_reg(dut, 0x38)
		dut.log.info("NEW reg_0x38= %d" % (reg_0x38))
? У меня уже всякая ерунда лезет в голову, типа «написать контекстный менеджер».

Virtuos86 ★★★★★ ()
Ответ на: комментарий от I-Love-Microsoft
@cocotb.coroutine
def __read_reg(dut, reg):
	dut.cc.brdg_addr = reg
	dut.cc.brdg_rd_n = 0
	yield posedge(dut.clock100)
	dut.cc.brdg_rd_n = 1
	yield posedge(dut.clock100)
	yield posedge(dut.clock100)
	res = int(dut.cc.brdg_dout)
	raise ReturnValue(res)

@cocotb.coroutine
def read_reg(dut, reg):
	gen = yield from __read_reg(dut, reg)
	res = int(gen.retval)
	raise ReturnValue(res)
		yield misc.write_reg(dut, 0x38, 555)
		reg_0x38 = (yield from misc.read_reg(dut, 0x38).retval
		dut.log.info("NEW reg_0x38= %d" % (reg_0x38))

Что выдает этот вариант?

Virtuos86 ★★★★★ ()
12 декабря 2016 г.
Ответ на: комментарий от Virtuos86

Перевел свои тесты на Python 3.5, теперь могу проверить эту конструкцию:

@cocotb.coroutine
def read_reg(dut, reg):
	dut.cc.brdg_addr = reg
	dut.cc.brdg_rd_n = 0
	yield posedge(dut.clock100)
	dut.cc.brdg_rd_n = 1
	yield posedge(dut.clock100)
	yield posedge(dut.clock100)
	res = int(dut.cc.brdg_dout)
	raise ReturnValue(res)
		yield misc.write_reg(dut, 0x38, 555)
		reg_0x38 = yield from misc.read_reg(dut, 0x38)
		dut.log.info("TEST READ reg_0x38= %s" % (str(reg_0x38)))
В результате:
Send raised exception: iter() returned non-iterator of type 'RunningCoroutine'
Может там в конце надо ставить yield ReturnValue(res)? Я не представляю как выполнить это простейшее действие - просто вернуть число из этого генератора.

I-Love-Microsoft ★★★★★ ()
Ответ на: комментарий от I-Love-Microsoft

Я уже с десяток способов опробовал, но всё без результатов. Например:

@cocotb.coroutine
def __read_reg(dut, reg):
	dut.cc.brdg_addr = reg
	dut.cc.brdg_rd_n = 0
	yield posedge(dut.clock100)
	dut.cc.brdg_rd_n = 1
	yield posedge(dut.clock100)
	yield posedge(dut.clock100)
	res = int(dut.cc.brdg_dout)
	raise StopIteration(res)

@cocotb.coroutine
def read_reg(dut, reg):
	try:
		yield __read_reg(dut, reg)
	except StopIteration as e:
		dut.log.info("read_reg -> %s" % e.value())
		return e.value()
	return 0
И многие другие варианты...

I-Love-Microsoft ★★★★★ ()
Ответ на: комментарий от Virtuos86

Вот! Я перешел на Python 3.5, написал на форум и мне ответили: https://github.com/potentialventures/cocotb/issues/505

Видимо from было лишним словом!

@cocotb.coroutine
def read_reg(dut, reg):
	dut.cc.brdg_addr = reg
	dut.cc.brdg_rd_n = 0
	yield posedge(dut.clock100)
	dut.cc.brdg_rd_n = 1
	yield posedge(dut.clock100)
	yield posedge(dut.clock100)
	res = int(dut.cc.brdg_dout)
	raise ReturnValue(res)
yield misc.write_reg(dut, 0x37, 157)
read = yield misc.read_reg(dut, 0x37)
dut.log.info("NEW READ read= %d" % read)

Вот так - работает! Уррра!

I-Love-Microsoft ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.