Welcome, guest | Sign In | My Account | Store | Cart

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, 37 lines
 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()

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.

2 comments

Scott David Daniels 19 years, 10 months ago  # | flag

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.

Ian Bollinger 19 years, 5 months ago  # | flag

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()