Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find out into how many values a return value will be unpacked

Tags:

python

I have a function, and when it is called, I'd like to know what the return value is going to be assigned to - specifically when it is unpacked as a tuple. So:

a = func()         # n = 1

vs.

a, b, c = func()   # n = 3

I want to use the value of n in func. There must be some magic with inspect or _getframe that lets me do this. Any ideas?


Disclaimer (because this seems to be neccessary nowadays): I know this is funky, and bad practice, and shouldn't be used in production code. It actually looks like something I'd expect in Perl. I'm not looking for a different way to solve my supposed "actual" problem, but I'm curious how to achive what I asked for above. One cool usage of this trick would be:

ONE, TWO, THREE = count()
ONE, TWO, THREE, FOUR = count()

with

def count():
    n = get_return_count()
    if not n:
        return
    return range(n)
like image 989
jdm Avatar asked May 10 '13 11:05

jdm


People also ask

How do I fix too many values to unpack expected 2?

The ValueError: too many values to unpack (expected 2)” occurs when you do not unpack all of the items in a list. A common mistake is trying to unpack too many values into variables. We can solve this by ensuring the number of variables equals the number of items in the list to unpack.

What does too many values to unpack mean Python?

Conclusion. The “valueerror: too many values to unpack (expected 2)” error occurs when you do not unpack all the items in a list. This error is often caused by trying to iterate over the items in a dictionary. To solve this problem, use the items() method to iterate over a dictionary.

What is the unpacking operator in Python?

Both * and ** are the operators that perform packing and unpacking in Python. The * operator (quite often associated with args) can be used with any iterable (such as a tuple, list and strings), whereas the ** operator, (quite often associated with kwargs) can only be used on dictionaries.


2 Answers

Adapted from http://code.activestate.com/recipes/284742-finding-out-the-number-of-values-the-caller-is-exp/:

import inspect
import dis

def expecting(offset=0):
    """Return how many values the caller is expecting"""
    f = inspect.currentframe().f_back.f_back
    i = f.f_lasti + offset
    bytecode = f.f_code.co_code
    instruction = ord(bytecode[i])
    if instruction == dis.opmap['UNPACK_SEQUENCE']:
        return ord(bytecode[i + 1])
    elif instruction == dis.opmap['POP_TOP']:
        return 0
    else:
        return 1

def count():
    # offset = 3 bytecodes from the call op to the unpack op
    return range(expecting(offset=3))

Or as an object that can detect when it is unpacked:

class count(object):
    def __iter__(self):
        # offset = 0 because we are at the unpack op
        return iter(range(expecting(offset=0)))
like image 119
ecatmur Avatar answered Nov 17 '22 04:11

ecatmur


There is little magic about how Python does this.

Simply put, if you use more than one target name on the left-hand side, the right-hand expression must return a sequence of matching length.

Functions that return more than one value really just return one tuple. That is a standard Python structure, a sequence of a certain length. You can measure that length:

retval = func()

print len(retval)

Assignment unpacking is determined at compile time, you cannot dynamically add more arguments on the left-hand side to suit the function you are calling.

Python 3 lets you use a splat syntax, a wildcard, for capturing the remainder of a unpacked assignment:

a, b, *c = func()

c will now be a list with any remaining values beyond the first 2:

>>> def func(*a): return a
... 
>>> a, b, *c = func(1, 2)
>>> a, b, c
(1, 2, [])
>>> a, b, *c = func(1, 2, 3)
>>> a, b, c
(1, 2, [3])
>>> a, b, *c = func(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: need more than 1 value to unpack
like image 41
Martijn Pieters Avatar answered Nov 17 '22 04:11

Martijn Pieters