ActiveState Code

Recipe 284742: Finding out the number of values the caller is expecting


Sometimes you might want to make a function behave differently if the caller is expecting one or several values (e.g. x=func() versus x,y=func()). The expecting() function lets the function implementer find out how many values the caller wants as a function result.

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import inspect,dis

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

def cleverfunc():
    howmany = expecting()
    if howmany == 0:
        print "return value discarded"
    if howmany == 2:
        return 1,2
    elif howmany == 3:
        return 1,2,3
    return 1

def test():
    cleverfunc()
    x = cleverfunc()
    print x
    x,y = cleverfunc()
    print x,y
    x,y,z = cleverfunc()
    print x,y,z

test()

Discussion

The "multiple return values"-idiom is actually just tuples that are unpacked, so the expecting() function implementation relies on the actual bytecode having an UNPACK_SEQUENCE code right after the function call (or POP_TOP if the return value isn't used at all). It has been tested under Python 2.3.3 and no guarantees can be given that it works under any future Python version.

Comments

  1. 1. At 11:53 a.m. on 23 jun 2004, Scott David Daniels said:

    Cute, but don't do this. Functions are easiest to understand when they behave simply, not when they try to be clever.

    Code like:

    x, y, z = getpoint()
    print 'x=%s, y=%s, z=%s' % (x,y,z)
    ...
    

    can be rewritten to be:

    x, y, z = point = getpoint()
    print 'x=%s, y=%s, z=%s' % point
    ...
    

    Neither "x, y, z = point = cleverfunc()" nor "point = x, y, z = cleverfunc()" work.

  2. 2. At 10:48 a.m. on 26 oct 2004, Ian Bollinger said:

    Evil! I tweaked your code a bit so it would work in all use cases, hopefully.

    import sys
    import dis
    
    
    def expecting():
        f = sys._getframe().f_back.f_back
        i = f.f_lasti + 3
        bytecode = f.f_code.co_code
        instruction = ord(bytecode[i])
        while True:
            if instruction == dis.opmap['DUP_TOP']:
                if ord(bytecode[i + 1]) == dis.opmap['UNPACK_SEQUENCE']:
                    return ord(bytecode[i + 2])
                i += 4
                instruction = ord(bytecode[i])
                continue
            if instruction == dis.opmap['STORE_NAME']:
                return 1
            if instruction == dis.opmap['UNPACK_SEQUENCE']:
                return ord(bytecode[i + 1])
            return 0
    
    
    def test():
        def f():
            r = expecting()
            if r == 0:
                return None
            if r == 1:
                return 0
            return range(r)
    
        f()
        a = f()
        a, b = f()
        a, b = c = f()
        a, b = c = d = f()
        a = b = f()
        a = b, c = f()
        a = b = c, d = f()
        a = b, c = d = f()
        a, b = c, d = f()
    
    test()
    

Sign in to comment