22

Is it pythonic to return multiple values from a function in this way?

def f():
    f.x = 1
    f.y = 2
    return f

r = f()
print r.x,r.y
1 2
14
  • 11
    @djechlin I wasn't calling anything out....... Commented Jul 10, 2013 at 14:46
  • 5
    @DavidHeffernan well, "Pythonic" is a term in the literature to mean the "right" way of doing things. So the user really doesn't have to do any further explaining, in that case. Commented Jul 10, 2013 at 15:15
  • 1
    I usually interpret "Pythonic" to mean "using methods generally recommended by the Python programming community". Given that any group of N persons generally has at least N+1 'recommendations', mine may be a distinction without merit. Commented Jul 10, 2013 at 16:28
  • 5
    I think by "pythonic" they mean "idiomatic". You can program in Python as in other GP language (for example, restricting yourself to RPython level of language features) or in million other ways, as opposed to using the specific language features as they were intended, and writing code in the style proposed by relevant PEPs etc. For example I'm sure that what he wants here is to retrn a tuple i.e. x,y=f()or a namedtuple, whereas returning a class (with x and y properties) would probably be sanest, albeit slightly less idiomatic/pythonic way to do it, depending on context. Commented Jul 10, 2013 at 16:44
  • 4
    @djechlin: Angry much? Every language has idiomatic and non-idiomatic ways of doing things. "Pythonic" just means "in the generally accepted Python idiom." If you don't like it, use Perl, where "there's more than one way to do it" is accepted and encouraged. Just don't complain when you have to maintain someone else's code where they used a bunch of tricks you aren't familiar with... Commented Jul 10, 2013 at 17:57

8 Answers 8

39

You're not "returning chained values", you're creating a function which returns itself, after setting variables on itself.

The problem with this is that if you reinvoke the function (assuming it isn't just a constant function as shown in your example) is that every single appearance of the function (and understand that r is the same as f in your code) will have those values change. You'll have this problem whether or not your programme uses multiple threads.

The normal way to return multiple values is simply to return a tuple, which can be the source of a destructuring (sequence) assignment. Alternatively, if you want to manage a bunch of variables together, you would use an object. That's what they're for.

Sign up to request clarification or add additional context in comments.

Comments

33

No. You are changing a global object that is not thread safe.

More common is

return 1, 2

or, if you want to have names,

return {'x': 1, 'y': 2}

3 Comments

Or an object, if there's justification for it.
@MartijnPieters Yes. I was thinking on templates for the second one
Thread safety isn't even the main problem. It will give unexpected results in a single thread.
20

It's not pythonic, it is not even reasonable for any language that potentially supports such a construct.

What you do is that you use the function's global state as a carrier of your output values. To return a value from a function you should use, well, a return value, not the called function. In your example you cannot be really sure what is your return value:

>> def f(x):
...   f.x=x
...   return f
...
>>> z=f(1)
>>> z.x
1
>>> f(2)    # <--- alters global state of the function
<function f at 0x10045c5f0>
>>> z.x
2           # <--- errr, five lines ago z.x was 1, no?

You can use namedtuple or a custom type (although using type() might be perceived as too low level):

>>> def dict_as_tuple(**kwargs):
...     return type('CustomType', (object,), kwargs)()
...
>>> z = dict_as_tuple(x=1, y=2)
>>> z.x
1
>>> z.y
2

Regarding the method chaining, a common way is to return self (if you want to change state of the object) or new object of the same type (objects are immutable, which is good)

>>> class C(object):
...   def __init__(self, x):
...      self.x = x
...   def add(self, y):
...     return C(self.x + y)
...   def mul(self, y):
...     return C(self.x * y)
...
>>> C(0).add(1).mul(10).x
10

Comments

18

All of the advice given so far is good, but doesn't show much code, here they are, one by one.

The most common answer was 'return a tuple', which would look like this

def f():
    return 1, 2

x, y = f()

A related answer was 'return a namedtuple':

from collections import namedtuple

Point = namedtuple('Point', 'x y')

def f():
    return Point(1, 2)

r = f()
print r.x, r.y

another idea was to use 'objects'.

class Foo(object):
    def __init__(self, x, y)
        self.x, self.y = x, y

r = Foo(1, 2)
print r.x, r.y

You mention 'chaining' in your question; as though you want repeated calls to f() to give different results. That's doable, too, with generators.

def f_gen():
    for x in range(1, 10)
        yield x, x + 1

f = f_gen()
print f()
print f()

1 Comment

I'd give more than +1 if I could. Very clean code, making all the options clear!
7

This is what's wrong:

>>> def f(a):
...   f.x = a
...   return f
...
>>> r = f(1)
>>> print r.x
1
>>> p = f(2)
>>> print p.x
2
>>> print r.x
2

It isn't expected that r is also changed! So not good practice to do it this way.

Comments

4

You can return a tuple:

def f():
    return 1,2

x,y = f()
print x,y
...
(1,2)

Comments

4

See other comments to your question if it's pythonic...
As an easy solution you could use a class instead..

class cls:
    def __init__(self):
        self.x, self.y = 1, 2
r = cls()
print(r.x, r.y) # 1 2

Comments

1

Another option, if you need to retain some state information, is to use a class and overload the __call__ method.

class f:
    def __call__(self):
        self.x = 1
        self.y = 2
        return self.x, self.y

r = f()
print(r())

That will print:

(1, 2)

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.