2

Is there a built-in/recommended way to replace the decorator:

def generator(f):
    return f()

in the following example?

from random import randint

@generator
def mygenerator():
    while True:
        yield randint(0, 9)

for i in mygenerator:
    print i

... as I don't want to write:

for i in mygenerator():
    print i

This is a simplified example. In the actual use-case there is no need/reason to have two instances of mygenerator, therefore I'd like to create the instance right away. Preferably in a way that no other instance can be created.

NichtJens
  • 1,709
  • 19
  • 27
  • 1
    Is there any need/reason *not* to? With such a simple example you could probably do it with `itertools` and not a function. – jonrsharpe May 16 '16 at 22:15
  • Yes. I tried to clarify in my last paragraph, that this totally oversimplified... Just assume, I want to do complex things in the definition of `mygenerator`. And avoid two instances being created. – NichtJens May 16 '16 at 23:33
  • 1
    @azrael: If you want these "complex things" to happen at definition time, what you're asking for won't have that effect. None of the generator function's body will be executed until the first `next`. Also, if you want to avoid creating two iterators, I recommend just not creating two iterators, rather than replacing the function with its return value. – user2357112 May 17 '16 at 00:02
  • I understand what you are saying. For the first part, that is not what I am concerned with. I am fine with the body executed, e.g., in a for-loop. I just don't want the function to be around anymore. So, in principle, I guess I want a generator expression that allows more complex things (as has been the consensus in the other answers) ... – NichtJens May 17 '16 at 00:21
  • http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem – jonrsharpe May 17 '16 at 06:07
  • Most certainly not. I did a thing, and wanted to know whether the exact same thing could be done better/cleaner. In other words, I didn't ask because I'm searching for a solution. It was simply a style question. Are those forbidden? – NichtJens May 17 '16 at 23:01

3 Answers3

2

The built-in way to do it is like this:

def mygenerator():
    while True:
        yield randint(0, 9)

mygenerator = mygenerator()

This at least has the virtue of being clear, so you don't have to look up what @generator does.

kindall
  • 178,883
  • 35
  • 278
  • 309
  • Ah right! THAT is indeed what I should do. I had not thought of simply overwriting the function with its result (even though this is what the decorator does)... – NichtJens May 16 '16 at 23:29
2

Generators can be created using the () syntax - i.e.

mygenerator = (randint(0, 9) for i in range(0, 100))

but your problem, I gather, is that the generator is infinite - you could try this:

import itertools
from random import randint

mygenerator = (randint(0, 9) for i in itertools.count())

See this answer for additional details

Community
  • 1
  • 1
Greg
  • 9,963
  • 5
  • 43
  • 46
  • The infinite loop is not necessarily the problem. As I tried to say, this is a simplified example. Just assume, I want to do complexer things in `mygenerator` that cannot be done as generator expression. But you are certainly right: Those cannot be instantiated twice. – NichtJens May 16 '16 at 23:37
1

What you're doing is about as idiomatic as it can be. Some languages have a binary prefix operator named do that invokes its argument:

generator = do <generator expression>

Python syntax just doesn't allow you to write multiline functions as expressions. You have to use def statements. That's why you can't pass a multiline function or generator to a decorator using a regular invocation in Python either:

generator = decorator(def generator(): yield) # syntax error

You have to define the function or generator up front:

def generator(): yield
generator = decorator(generator)

It's mainly that syntactic limitation that forced Python to adopt the @ syntax for decorating functions that are defined with a def statement. The following code is equivalent to the last example (aside from a subtle difference in when the names are assigned):

@decorator
def generator(): yield

As Greg mentioned, there are generator comprehensions, which work really nicely where you can use them, but if you need more logic in the body, then what you're doing seems like it would be the most Pythonic way. It's just that Python syntax always made functional programming quite clunky.


You asked in the comments about languages that have the do operator. CoffeeScript and Haskell both have it. To be honest, I don't really understand anything the Haskell docs say, but in CoffeeScript, do is useful for creating values within closures.

Take a look at this code (there's an explanation below).

updateElementText = do ->
  element = jQuery "#someElement"
  return (newText) -> element.text newText

Which would look like this in JS:

let updateElementText = function() {
  let element = jQuery("#someElement");
  return function(newText) { element.text(newText) };
}(); // Immediately-Invoked Function Expression (IIFE)

The code assigns updateElementText to the function that's returned on Line 3, which references element by closure. The element is only selected by jQuery once (on Line 2), no matter how many times updateElementText is invoked to update the element. Also note that element is hidden, so it can only be referenced by the function that updates it.

Carl Smith
  • 3,025
  • 24
  • 36
  • Mind pointing to what languages do have such a `do` operator? After reading the answers, I gather what I did was good if I had many of those single-instance generators. However, I do not - it's only one. So, I guess kindall's answer is spot-on. – NichtJens May 16 '16 at 23:43
  • I've added some stuff to my answer, but yeah, kindall is right. It's best to just write it out clearly, and reuse the name, if you're only doing this once. If you had that same pattern in multiple places, your original decorator would be better. – Carl Smith May 17 '16 at 03:11
  • Ah. Now I see how this compares to my question. Thanks for making that connection. Admittedly, the Haskell example is beyond me, too ;-) I guess, one cannot simply jump to the most advanced topic of an unknown language and expect to understand it, though. Seems like, time to learn Haskell. – NichtJens May 17 '16 at 05:40