Портирование с Ruby на Python: что делать с «yield» - PullRequest
2 голосов
/ 29 марта 2019

В настоящее время я пытаюсь перенести часть кода с 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?

Ответы [ 2 ]

3 голосов
/ 29 марта 2019

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

Ruby's 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 (именно это Rubyists предпочитает for):

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

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

В вашем случае, если вы оставите yield точно так же, как в вашем Python, строка, которая его потребляет, изменится с * 1046 в 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), но я хотел иметь код, который выглядит одинаково для обоих языков, для сравнения.

1 голос
/ 29 марта 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 с четырьмя аргументами, поэтому теоретически в вашей реализации этого метода вы должныпередать ему функцию обратного вызова.

...