Feb 032016
 

Since Python is call-by-object(*1), a function that mutates a mutable argument, changes that object in the caller’s scope. some code to illustrate:

>>> mobject = {'magic': True}
>>> id(mobject)
140330485577440
>>> 
>>> def toggle(mdata):
...    '''flip the state of magic'''
...    mdata['magic'] = not mdata['magic']
... 
>>> 
>>> toggle(mobject)
>>> mobject
{'magic': False}
>>> id(mobject)
140330485577440

So hopefully this does not surprise you. If it does, please see the two links in the footnotes(*1) as they explain it quiet well.

My question deals with the implicit nature of the mutation. Not that Python is somehow wrong, it is the fact that the function usage does not convey the mutation to the reader as pointedly as I want. Coming from other languages that are call by value, a function that wanted to mutate an argument and get it back into the caller’s scope had to return the mutated value.

>>> def toggle_explicit(mdata):                                                 
...    '''flip the state of magic and return'''                                
...    mdata['magic'] = not mdata['magic']
...    return mdata
... 
>>> mobject = toggle_explicit(mobject)
>>> mobject
{'magic': True}
>>> id(mobject)
140330485577440
>>> 

Now I know that the above code is most definitely NOT call by value, but I do feel that it is more explicit about my intention. Even though the assignment is not needed for the actual effect. i.e.:

>>> toggle_explicit(mobject)
{'magic': False}
>>> mobject
{'magic': False}

So why does toggle_explicit() give me the warm fuzzies? Where as toggle() requires the reader to know what is going on. Is it just me shaking the cruft of call-by-value languages off? What do you do, when you mutate state within a function? Is the toggle_explicit() form not/less Pythonic?

— Footnotes —

(*1) Raymond Hettinger in this SO article references a post by Fredrik Lundh’s on “Call By Object

 Posted by at 3:14 am

  4 Responses to “Mutant Registration: Implicit or Explicit”

  1. Part of the problem here might be the use of the term “scope”. The object is NOT “changed in the caller’s scope”, because in Python it is the *names* that are in the caller’s scope, not the objects. An object in Python has no scope. It exists separately, can be shared by everyone (i.e. bound to names that exist in any scope), and is automatically destroyed (ignoring circular references) when no scope contains any references to it.

    • @Peter I concede the “scope” of the mutable object is not in a scope, however, the identifier in the caller’s scope is attached to the same object that is being modified in the function’s scope by another identifier. Hence the side-effect results and my question as to what would be the more Pythonic way.

  2. I find toggle_explicit a little bit discomforting.

    I would expect a function to either to be a ‘procedure’ that works via a side-effect, or work as a pure function (i.e. return a new dictionary, leaving the old one alone, no side-effects).

    Making it look functional, but have unexpected side-effects, is dangerous. It means I can’t (for example) memoize/cache the result.

    So, one approach would be to make it pure, and return a new copy of the object, leaving the original alone.

    Another approach to consider would be see if mobject could be made an instance of a class with a toggle() method.

    • @Julian – you raise an interesting point about not being able to memoize/cache the result. I had not that about that. Doing a copy.deepcopy would certainly be a performance penalty and another knock against the toggle_explicit form.

      As to using an instance of MObject class and using a toggle method, I had purposely left that out. I had hope to focus on whether or not the “side-effect” form was considered acceptable

Sorry, the comment form is closed at this time.