8

Imagine I've got a Python module with some function in it:

def sumvars(x, y, z):
    s = x
    s += y
    s += z
    return s

But sometimes I want to get results of some intermediate calculations (for example, I could have a function which reverses a matrix and would like to know the determinant which has been calculated as an intermediate step as well). Obviously, I wouldn't want to redo those calculations again if they were already done within that function.

My first idea is to return a dict:

def sumvars(x, y, z):
    d = {}
    s = x
    d['first_step'] = s
    s += y
    d['second_step'] = s
    s += z
    d['final'] = s
    return d

But I don't recall any functions in numpy or scipy which return dicts and so it seems like this might be not a good idea. (Why?) Also routinely I'll always have to type sumvars(x,y,z)['final'] for a default return value...

Another option I see is creating global variables but seems wrong having a bunch of them in my module, I would need to remember their names and in addition not being attached to the function itself looks like a bad design choice.

What would be the proper function design for such situation?

sashkello
  • 17,306
  • 24
  • 81
  • 109
  • The question is how you intend to use this function. There's nothing wrong with returning a dict of values, but that means that anyone who uses this function will have to know that and know how to make use of the returned dict. – BrenBarn Feb 26 '14 at 06:23
  • If you are going to be using some intermediate result a lot, then perhaps it is best to make a separate function for that. – Jayanth Koushik Feb 26 '14 at 06:24
  • In your case, why not use a list to store the intermediate results instead ? – zhangxaochen Feb 26 '14 at 06:26
  • @JayanthKoushik That would still require to have same thing evaluated twice. – sashkello Feb 26 '14 at 06:26
  • @zhangxaochen List, tuple, dict, whatever, are all the same concept in this case. Dict is better because it has names for things and thus better for readability. – sashkello Feb 26 '14 at 06:27
  • @BrenBarn Yep, assume there is a brilliantly written clear documentation with bright smart people reading and understanding it... – sashkello Feb 26 '14 at 06:28
  • If you're concerned about it being "annoying" (or breaking backwards compatibility) just have two functions: `def sumvars(x, y, z): return sumvars_with_intermediates(x,y,z).final` – roippi Feb 26 '14 at 06:32
  • @sashkello if you just want to see the result of all steps then you can use empty string and concatenate every steps result and return it at the end. – Tanveer Alam Feb 26 '14 at 06:32
  • @roippi How is it different from zhangxaochen's answer? Maybe you can add another answer with more information... – sashkello Feb 26 '14 at 06:45
  • **Related**: [How can I use `return` to get back multiple values from a loop? Can I put them in a list?](/questions/44564414) – Karl Knechtel Jan 03 '23 at 01:47

8 Answers8

5

Put the common calculation into its own function as Jayanth Koushik recommended if that calculation can be named appropriately. If you want to return many values (an intermediate result and a final result) from a single function then a dict may be an overkill depending on what is your goal but in python it is much more natural to simply return a tuple if your function has many values to return:

def myfunc():
    intermediate = 5
    result = 6
    return intermediate, result

# using the function:
intermediate, result = myfunc()
pasztorpisti
  • 3,760
  • 1
  • 16
  • 26
5

Not sure if function attributes is a good idea:

In [569]: def sumvars(x, y, z):
     ...:     s = x
     ...:     sumvars.first_step = s
     ...:     s += y
     ...:     sumvars.second_step = s
     ...:     s += z
     ...:     return s


In [570]: res=sumvars(1,2,3)
     ...: print res, sumvars.first_step, sumvars.second_step
     ...: 
6 1 3

Note: as @BrenBarn mentioned, this idea is just like global variables, your previously calculated "intermediate results" could not be stored when you want to reuse them.

zhangxaochen
  • 32,744
  • 15
  • 77
  • 108
  • Wow! Wouldn't think that it would even compile, but seems ideal from the usability point of view. – sashkello Feb 26 '14 at 06:32
  • Smuggling data in a function's `__dict__` is always cute, but I really recommend strongly against doing this in a real API. – roippi Feb 26 '14 at 06:36
  • 2
    I'd say this is a bad idea for more or less the same reason as global variables. Every call to the function will stomp on the previously stored values, so if you're calling it from multiple places in a call stack it can quickly get confusing. – BrenBarn Feb 26 '14 at 06:37
  • @BrenBarn yes it's like globals... it just comes up with me. – zhangxaochen Feb 26 '14 at 06:43
  • @BrenBarn Would it be OK if sumvars is never ever called in parallel? I do understand the implications otherwise... – sashkello Feb 26 '14 at 06:44
  • @sashkello: Again, it's up to you, but it makes the API more convoluted. (It's not just a matter of being called "in parallel" as in in different threads. If function A calls `sumvars`, then calls function B, which calls `sumvars`, and then later in function A it tries to read `sumvars.first_step`, it will be reading the value from B's call, not its own.) – BrenBarn Feb 26 '14 at 06:45
  • Although I would never do this in an API, there's still something bewitchingly clever about it :). – Jeremy West Feb 26 '14 at 06:56
  • 2
    I dont' think it's a good practice to use the method attributes, since they are almost like using global variables. In this example, sumvars.first_step will change evertime someone calls the sumvars method. – ignacio.munizaga Feb 26 '14 at 06:57
  • In python basically everything is an object and at the same time a "dictionary" (even the global/local contexts) and as a result you can attach additional values to them - this solution basically exploits this (attaches attributes to function objects) but for the same reason others already mentioned I would be against this solution, I don't like "global variables" (especially with threads) and python has better solutions to this problem. For the newbie python learner it may be exciting news that functions are also objects with attributes so the answer has some educational value too. – pasztorpisti Feb 26 '14 at 19:48
5

Generally when you have two different ways you want to return data, go ahead and make two different functions. "Flat is better than nested", after all. Just have one call the other so that you Don't Repeat Yourself.

For example, in the standard library, urllib.parse has parse_qs (which returns a dict) and parse_qsl (which returns a list). parse_qs just then calls the other:

def parse_qs(...):

    parsed_result = {}
    pairs = parse_qsl(qs, keep_blank_values, strict_parsing,
                      encoding=encoding, errors=errors)
    for name, value in pairs:
        if name in parsed_result:
            parsed_result[name].append(value)
        else:
            parsed_result[name] = [value]
    return parsed_result

Pretty straightforward. So in your example it seems fine to have

def sumvars(x, y, z):
    return sumvars_with_intermediates(x, y, z).final

def sumvars_with_intermediates(x, y, z):
    ...
    return my_namedtuple(final, first_step, second_step)

(I favor returning namedtuples instead of dicts from my APIs, it's just prettier)

Another obvious example is in re: re.findall is its own function, not some configuration flag to search.

Now, the standard library is a sprawling thing made by many authors, so you'll find counterexamples to every example. You'll far more often see the above pattern rather than one omnibus function that accepts some configuration flags, though, and I find it far more readable.

roippi
  • 25,533
  • 4
  • 48
  • 73
2

Just came up with this idea which could be a better solution:

def sumvars(x, y, z, mode = 'default'):
    d = {}
    s = x
    d['first_step'] = s
    s += y
    d['second_step'] = s
    s += z
    d['final'] = s
    if mode == 'default':
        return s
    else:
        return d
sashkello
  • 17,306
  • 24
  • 81
  • 109
2

I belive the proper solution is to use a class, to have a better grasp of what you are modeling. For example in the case of the Matrix, you could simply store the determinant in the "determinant" attribute.

Here is an example using your matrix example.

class Matrix:
    determinant = 0

    def calculate_determinant(self):
        #calculations
        return determinant

    def some_method(self, args):
       # some calculations here

       self.determinant = self.calculate_determinant()

       # other calculations

 matrix = Matrix()
 matrix.some_method(x, y, z)
 print matrix.determinant

This also allows you to separate your method into simpler methods, like one for calculating the determinant of your matrix.

ignacio.munizaga
  • 1,553
  • 1
  • 23
  • 28
  • 1
    this is just like returning a `dict`, just with mildly different syntax and the overhead of needing to instantiate a class before use. – roippi Feb 26 '14 at 06:39
  • Not quite, the main difference I think is that a class lets you work with de modeling of your problem in an structured way. For exmaple, using a "Circle" class to generate circles with properties you want to calculate a single time; area, perimeter, etc. – ignacio.munizaga Feb 26 '14 at 06:41
  • Well, this is kind of avoiding global variables by wrapping it all into separate class. The problem is that I'd need to create classes which I don't really want. It would be a good solution, otherwise, and I certainly do such things quite routinely... – sashkello Feb 26 '14 at 06:50
2

Another variation:

def sumvars(x, y, z, d=None):
  s = x
  if not d is None:
    d['first_step'] = s
  s += y
  if not d is None:
    d['second_step'] = s
  s += z
  return s

The function always returns the desired value without packing it into a tuple or dictionary. The intermediate results are still available, but only if requested. The call

sumvars(1, 2, 3)

just returns 6 without storing intermediate values. But the call

d = {}
sumvars(1, 2, 3, d)

returns the same answer 6 and inserts the intermediate calculations into the supplied dictionary.

Jeremy West
  • 11,495
  • 1
  • 18
  • 25
1

Option 1. Make two separate functions.

Option 2. Use a generator:

>>> def my_func():
...     yield 1
...     yield 2
... 
>>> result_gen = my_func()
>>> result_gen
<generator object my_func at 0x7f62a8449370>
>>> next(result_gen)
1
>>> next(result_gen)
2
>>> next(result_gen)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>
warvariuc
  • 57,116
  • 41
  • 173
  • 227
  • Well, then to return the final value I'd need to call function many times... and also remember how many times I should call it for each thing, seems like it would be very hard to use this. What if I add another intermediate result? All my existing code breaks... – sashkello Feb 26 '14 at 06:36
  • Refer to my matrix example. Imagine there are few intermediate things I'd like to return. 1. How do I call function if I don't want intermediate results? 2. How do I access a particular intermediate result, i.e. 'determinant', do I have to remember the number of this result? – sashkello Feb 26 '14 at 06:39
  • There are many ways to do what you want. In your case I think it's better your function to return a `dict`, a `tuple` or an object with attributes containing all the results. – warvariuc Feb 26 '14 at 06:41
0

Inspired by @zhangxaochen solution, here's my take on your problem using class attributes:

class MyClass():
    def __init__(self):
        self.i = 4

    def f(self):
        s = self.i
        MyClass.first_step = s
        print(MyClass.first_step)

        s += self.i
        MyClass.second_step = s
        print(MyClass.second_step)

        s += self.i
        return s

def main():
    x = MyClass()
    print(x.f()) # print final s
    print(x.first_step)
    print(x.second_step)
    print(MyClass.second_step)

Note: I included several prints to make it more explicit how attribute values can be retrieved.

Result:

4
8
12
4
8
8
sol4ris
  • 41
  • 5