Перенос с Ruby на Python: что делать с yield

В настоящее время я пытаюсь перенести фрагмент кода с Ruby на Python, чтобы провести небольшое исследование алгоритмов. У меня нет опыта работы с Ruby и я не знаю, как обрабатывать ключевое слово yield.

Код предназначен для алгоритма сравнения Майерса и полностью описан в этой блог

Это фрагмент кода, который я не понимаю:

  while x > prev_x and y > prev_y
    yield x - 1, y - 1, x, y
    x, y = x - 1, y - 1
  end

Есть ли способ приблизиться к этому в Python?


person phibel    schedule 29.03.2019    source источник
comment
Это может помочь: stackoverflow.com/q/3066703/10077   -  person Fred Larson    schedule 29.03.2019


Ответы (2)


Почти идентично. Хотя семантика yield Python и Ruby несколько различается, в данном случае они почти полностью совпадают.

Ruby yield вызывает блок, который передается в функцию, передавая ей параметры.

Python yield делает функцию генератором и генерирует из нее один вывод.


Оба они имеют смысл только в контексте функции, поэтому ваш цикл while слишком короткий контекст для его использования. Но давайте возьмем что-то подобное в качестве упрощенного примера на Ruby:

def numbers_and_doubles(n)
  i = 0
  while i < n
    yield i, 2 * i
    i += 1
  end
end

Эта функция принимает блок с одним параметром, затем генерирует числа до этого числа вместе с их двойником и выполняет этот блок с этими параметрами:

numbers_and_doubles(5) do |num, double|
  puts "#{num} * 2 = #{double}"
end

Поскольку блоки в основном то же самое, что и функции обратного вызова, они эквивалентны этому Python:

def numbers_and_doubles(n, callback):
    i = 0
    while i < n:
        callback(i, i*2)
        i += 1

def output(num, double):
    print(f"{num} * 2 = {double}")

numbers_and_doubles(5, output)

С другой стороны, Python yield создает генератор - функцию, которая возвращает функцию, которая может создавать значения по запросу:

def numbers_and_doubles(n):
    i = 0
    while i < n:
        yield i, 2 * i
        i += 1

Самый естественный способ использовать генератор - использовать цикл for:

for num, double in numbers_and_doubles(5):
    print(f"{num} * 2 = {double}")

В Ruby ближайший дословный перевод - Enumerator:

def numbers_and_doubles(n)
  Enumerator.new do |yielder|
    i = 0
    while i < n
      yielder.yield(i, i*2)
      i += 1
    end
  end
end

и наиболее естественный способ использовать Enumerator - использовать each (это то, что рубисты предпочитают for):

numbers_and_doubles(5).each do |num, double|
  puts "#{num} * 2 = #{double}"
end

Но, как я уже сказал, несмотря на то, что они делают что-то немного другое, исходный Ruby выше (с yield) удивительно похож на исходный Python выше (с yield). Их употребление немного отличается, но соответствует идиоме каждого языка.

В вашем случае, если вы оставите yield точно так же, как в вашем Python, строка, которая его потребляет, изменится с Ruby

backtrack do |prev_x, prev_y, x, y|

к Python

for prev_x, prev_y, x, y in backtrack():

Вы можете узнать больше на доходности Python и доходности Ruby.


Обратите внимание, что наиболее естественный способ написания этого цикла - не while ни на одном из языков (я бы использовал range в Python и times в Ruby), но я хотел иметь для сравнения код, похожий на оба языка.

person Amadan    schedule 29.03.2019

Посмотрим на код из блога:

def diff
  diff = []

  backtrack do |prev_x, prev_y, x, y|
    a_line, b_line = @a[prev_x], @b[prev_y]

    if x == prev_x
      diff.unshift(Diff::Edit.new(:ins, nil, b_line))
    elsif y == prev_y
      diff.unshift(Diff::Edit.new(:del, a_line, nil))
    else
      diff.unshift(Diff::Edit.new(:eql, a_line, b_line))
    end
  end

  diff
end

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

person neversleep    schedule 29.03.2019