9

I've been converting Ruby code to Python code and now I'm stuck with this function that contains yield:

def three_print():
    yield
    yield
    yield

I would like to call the function and tell it to print "Hello" three times because of the three yield statements. As the function does not take any arguments I get an error. Can you tell me the easiest way to get it working? Thank you.

Danil Speransky
  • 29,891
  • 5
  • 68
  • 79
  • Other than adding an argument? – Ignacio Vazquez-Abrams Aug 26 '17 at 18:56
  • 4
    Note that the `yield` keyword in Ruby has very different behavior than the `yield` keyword in Python. – Christian Dean Aug 26 '17 at 18:57
  • 1
    **Note to potential answers:** Please read the question carefully. The OP is looking to translate code with _specific semantics_ from ruby to Python. While answers like `print('Hello\n'*3)` are technically correct, they are missing the most important point: The behavior of the `yield` keyword in Ruby vs Python. – Christian Dean Aug 26 '17 at 19:05

5 Answers5

16

yield in Ruby and yield in Python are two very different things.

In Ruby yield runs a block passed as a parameter to the function.

Ruby:

def three
  yield
  yield
  yield
end

three { puts 'hello '} # runs block (prints "hello") three times

In Python yield throws a value from a generator (which is a function that uses yield) and stops execution of the function. So it's something completely different, more likely you want to pass a function as a parameter to the function in Python.

Python:

def three(func):
  func()
  func()
  func()

three(lambda: print('hello')) # runs function (prints "hello") three times

Python Generators

The code below (code you've provided) is a generator which returns None three times:

def three():
   yield
   yield
   yield

g = three() #=> <generator object three at 0x7fa3e31cb0a0>
next(g) #=> None
next(g) #=> None
next(g) #=> None
next(g) #=> StopIteration

The only way that I can imagine how it could be used for printing "Hello" three times -- using it as an iterator:

for _ in three():
    print('Hello')

Ruby Analogy

You can do a similar thing in Ruby using Enumerator.new:

def three
  Enumerator.new do |e|
    e.yield # or e << nil
    e.yield # or e << nil
    e.yield # or e << nil
  end
end

g = three
g.next #=> nil
g.next #=> nil
g.next #=> nil
g.next #=> StopIteration

three.each do
  puts 'Hello'
end
Danil Speransky
  • 29,891
  • 5
  • 68
  • 79
  • 2
    You should mention that the Ruby equivalent to the [Python `yield` **keyword**](https://docs.python.org/3.7/reference/expressions.html#yieldexpr) is the [Ruby `Enumerator::Yielder#yield` **method**](http://ruby-doc.org/core/Enumerator.html#method-c-new) (also aliased to `Enumerator::Yielder#<<`, and neither properly documented, unfortunately). – Jörg W Mittag Aug 27 '17 at 07:36
7

yield in Python doesn't work like in Ruby. In particular, it doesn't mean "execute the block argument here". This function will never execute callbacks.

In Python, yield is for creating iterators (specifically generators), and the function you've posted will return an iterator that produces None 3 times. You can loop over the iterator and print "Hello" for each value, ignoring the None:

for _ in three_print():
    print("Hello")

which is the closest you'd get to the Ruby behavior, but still fundamentally different in terms of underlying mechanics.

Alternatively, if you do want a function that will execute a callback 3 times, that would be

def f(callback):
    callback()
    callback()
    callback()

and you could call it as

f(lambda: print("Hello"))
user2357112
  • 260,549
  • 28
  • 431
  • 505
1

You can use :

def three_print():
  yield"Hello\n"*3
print(''.join(list(three_print())))

# Hello
# Hello
# Hello
DjaouadNM
  • 22,013
  • 4
  • 33
  • 55
0

You can yield in a loop:

def hello():
   for i in range(3):
      yield "hello"

print(list(hello()))

Output:

['hello', 'hello', 'hello']

In Python3.3 and greater, you can use the yield from statement:

def hello():
   yield from ["hello" for i in range(3)]

print(list(hello()))

Output:

['hello', 'hello', 'hello']
Ajax1234
  • 69,937
  • 8
  • 61
  • 102
  • Minor nitpick: The `yield from` syntax is only available in Python _3.3_ and greater. See [here](https://www.python.org/dev/peps/pep-0380/). – Christian Dean Aug 26 '17 at 19:13
  • @ChristianDean Thanks! I did not know that. Fixed now. – Ajax1234 Aug 26 '17 at 19:15
  • _Eh_, still not exactly correct. It's only available in Python 3.3 and greater. Any Python 3.x version will not work. It needs to specifically be Python 3.3+. – Christian Dean Aug 26 '17 at 19:16
0
def three_print():
  yield("Hello")
  yield("Hello")
  yield("Hello")

since there are three yields, you need to call three_print().next() three times to get the "Hello" string to output

Max
  • 1,283
  • 9
  • 20