carry **arguments through different scopes/functions

Steven D'Aprano steve at pearwood.info
Sun Jan 31 20:53:46 EST 2016


On Sun, 31 Jan 2016 11:19 pm, c.buhtz at posteo.jp wrote:

> I am not sure what the problem is here, so I don't really know how I
> should call the subject for that question. Please offer a better
> subject.
> 
> The code below is a extrem simplified example of the original one. But
> it reproduce the problem very nice. Please focus on the variable
> `return_code`.

The problem with return_code is that you are trying to use it as an "output
parameter" or "call by reference" parameter. You want behaviour like this:


a = 1
b = 2

def test_output_parameter(the_var):
    print(the_var)
    # Set the_var by reference.
    the_var = 999

test_output_parameter(a)  # sets a
test_output_parameter(b)  # sets b
assert (a == 999) and (b == 999)



This cannot work in Python: Python is never call by reference. If somebody
has told you that it is call by reference, they are wrong.

You can *simulate* call by reference output parameters by using a list, but
that's the wrong way to solve this problem. The right way is for your
function to return a value, which you then assign to the variable you want
to change:


a = 1
b = 2

def test_return(the_var):
    print(the_var)
    return 999

a = test_return(a)
b = test_return(b)
assert (a == 999) and (b == 999)




Looking at your example code, we can replace (most of it) with these six
lines:


# Version 1: best, shortest, fastest way, with no callbacks.

ids = [1,2,3,4,5,6,8,9]  # NO 7
print(ids)
if 7 in ids:
    print('7 is in')
else:
    print('no 7 in it')



ignoring what looks like prints trying to debug the "return_code" problem.

If you really need to use a callback structure, then try something like
this, using a generator to return each individual callback result:



# Version 2: a mess -- don't do this unless you must!

def walker(ids, handlerFunction, **handlerArgs):
    for one_id in ids:
        print('handler-call for id {}\t{}'.format(one_id, handlerArgs))
        yield handlerFunction(one_id=one_id, **handlerArgs)

def on_id_callback(one_id, return_code):
    if return_code is False:
        # Why are we skipping this case?
        return False
    return_code = (one_id == 7)
    print('one_id: {}\treturn_code: {}'.format(one_id, return_code))

def seven_in_it(ids):
    return_code = True
    for return_code in walker(ids, on_id_callback, return_code=return_code):
        # This loop runs for the side-effects!
        pass
    return return_code


ids = [1,2,3,4,5,6,8,9]  # NO 7
print(ids)

if seven_in_it(ids):
    print('7 is in')
else:
    print('no 7 in it')




Running that code gives this output:

[1, 2, 3, 4, 5, 6, 8, 9]
handler-call for id 1   {'return_code': True}
one_id: 1       return_code: False
handler-call for id 2   {'return_code': True}
one_id: 2       return_code: False
handler-call for id 3   {'return_code': True}
one_id: 3       return_code: False
handler-call for id 4   {'return_code': True}
one_id: 4       return_code: False
handler-call for id 5   {'return_code': True}
one_id: 5       return_code: False
handler-call for id 6   {'return_code': True}
one_id: 6       return_code: False
handler-call for id 8   {'return_code': True}
one_id: 8       return_code: False
handler-call for id 9   {'return_code': True}
one_id: 9       return_code: False
no 7 in it



But this is truly awful code. My sympathies if you are forced to use it, my
apologies for being so blunt if you wrote it. A better way to deal with
this would be something like this:


# Version 3: better.

def verbose_equals_seven(one_id):
    flag = (one_id == 7)
    print('one_id: {}\treturn_code: {}'.format(one_id, flag))
    return flag

ids = [1,2,3,4,5,6,8,9]  # NO 7
print(ids)

if any(verbose_equals_seven(id) for id in ids):
    print('7 is in')
else:
    print('no 7 in it')




Running this Version 3 code gives this output:


[1, 2, 3, 4, 5, 6, 8, 9]
one_id: 1       return_code: False
one_id: 2       return_code: False
one_id: 3       return_code: False
one_id: 4       return_code: False
one_id: 5       return_code: False
one_id: 6       return_code: False
one_id: 8       return_code: False
one_id: 9       return_code: False
no 7 in it




Not very exciting. But if we try again with a list containing id=7, we see
that any() stops processing as soon as it has a success:

ids = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
print(ids)
if any(verbose_equals_seven(id) for id in ids):
    print('7 is in')
else:
    print('no 7 in it')



prints this output:


[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
one_id: 9       return_code: False
one_id: 8       return_code: False
one_id: 7       return_code: True
7 is in


> In the original code I will
> use some more complexe data structures with `**handlerArgs`.

Have you tried it? If this complex data structure is *mutable*, and you
change it, that change will be seen everywhere. Remember right at the start
of my post I mentioned that you can simulate "output parameters" with a
list? This is similar: because objects are not copied when you pass them to
a function, mutations to the object will be seen no matter where you make
the mutation.



# Version 4: mutating an object instead of returning flags.
# Don't do this unless you really must. Python is not Java
# and code like this is not considered good style

class ComplexData:
    def __init__(self):
        self.flag = False
    def __repr__(self):
        msg = "<%s instance with object id %s and flag %s>"
        return msg % (type(self).__name__, id(self), self.flag)

def walker(ids, handlerFunction, **handlerArgs):
    for one_id in ids:
        print('handler-call for id {}\t{}'.format(one_id, handlerArgs))
        handlerFunction(one_id=one_id, **handlerArgs)

def on_id_callback(one_id, something_complex):
    print('one_id: {}\tsomething_complex: {}'
          .format(one_id, something_complex))
    if something_complex.flag is False:
        something_complex.flag = (one_id == 7)

def seven_in_it(ids):
    data = ComplexData()
    walker(ids, on_id_callback, something_complex=data)
    return data.flag


ids = [9, 8, 7, 6, 5, 4, 3, 2, 1]
print(ids)

if seven_in_it(ids):
    print('7 is in')
else:
    print('no 7 in it')




Running version 4 of the code prints this output:


[9, 8, 7, 6, 5, 4, 3, 2, 1]
handler-call for id 9   {'something_complex': <ComplexData instance with
object id 3082144364 and flag False>}
one_id: 9       something_complex: <ComplexData instance with object id
3082144364 and flag False>
handler-call for id 8   {'something_complex': <ComplexData instance with
object id 3082144364 and flag False>}
one_id: 8       something_complex: <ComplexData instance with object id
3082144364 and flag False>
handler-call for id 7   {'something_complex': <ComplexData instance with
object id 3082144364 and flag False>}
one_id: 7       something_complex: <ComplexData instance with object id
3082144364 and flag False>
handler-call for id 6   {'something_complex': <ComplexData instance with
object id 3082144364 and flag True>}
one_id: 6       something_complex: <ComplexData instance with object id
3082144364 and flag True>
handler-call for id 5   {'something_complex': <ComplexData instance with
object id 3082144364 and flag True>}
one_id: 5       something_complex: <ComplexData instance with object id
3082144364 and flag True>
handler-call for id 4   {'something_complex': <ComplexData instance with
object id 3082144364 and flag True>}
one_id: 4       something_complex: <ComplexData instance with object id
3082144364 and flag True>
handler-call for id 3   {'something_complex': <ComplexData instance with
object id 3082144364 and flag True>}
one_id: 3       something_complex: <ComplexData instance with object id
3082144364 and flag True>
handler-call for id 2   {'something_complex': <ComplexData instance with
object id 3082144364 and flag True>}
one_id: 2       something_complex: <ComplexData instance with object id
3082144364 and flag True>
handler-call for id 1   {'something_complex': <ComplexData instance with
object id 3082144364 and flag True>}
one_id: 1       something_complex: <ComplexData instance with object id
3082144364 and flag True>
7 is in




-- 
Steven




More information about the Python-list mailing list