Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it pythonic for a function to return chained values / set variables on itself?

Tags:

python

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
like image 436
user2568730 Avatar asked Jul 10 '13 14:07

user2568730


4 Answers

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.

like image 197
Marcin Avatar answered Oct 22 '22 14:10

Marcin


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}
like image 38
JBernardo Avatar answered Oct 22 '22 14:10

JBernardo


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
like image 39
Jakub M. Avatar answered Oct 22 '22 15:10

Jakub M.


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()
like image 41
SingleNegationElimination Avatar answered Oct 22 '22 16:10

SingleNegationElimination