views:

128

answers:

7

I didn't know you could do this:

def tom():
    print "tom's locals: ", locals()

def dick(z):
    print "z.__name__ = ", z.__name__
    z.guest = "Harry"
    print "z.guest = ", z.guest
    print "dick's locals: ", locals()

tom()              #>>> tom's locals:  {}
#print tom.guest    #AttributeError: 'function' object has no attribute 'guest'
print "tom's dir:", dir(tom)  # no 'guest' entry

dick( tom)         #>>> z.__name__ =  tom
                   #>>> z.guest =  Harry
                   #>>> dick's locals:  {'z': <function tom at 0x02819F30>}
tom()              #>>> tom's locals:  {}
#print dick.guest  #AttributeError: 'function' object has no attribute 'guest'

print tom.guest    #>>> Harry
print "tom's dir:", dir(tom)  # 'guest' entry appears

Function tom() has no locals. Function dick() knows where tom() lives and puts up Harry as 'guest' over at tom()'s place. harry doesn't appear as a local at tom()'s place, but if you ask for tom's guest, harry answers. harry is a new attribute at tom().

UPDATE: From outside tom(), you can say "print dir(tom)" and see the the tom-object's dictionary. (You can do it from inside tom(), too. So tom could find out he had a new lodger, harry, going under the name of 'guest'.)

So, attributes can be added to a function's namespace from outside the function? Is that often done? Is it acceptable practice? Is it recommended in some situations? Is it actually vital at times? (Is it Pythonic?)

UPDATE: Title now says 'attributes'; it used to say 'variables'. Here's a PEP about Function Attributes.

+3  A: 

In python, a namespace is just a dictionary object, mapping variable name as a string (in this case, 'guest') to a value (in this case, 'Harry'). So as long as you have access to an object, and it's mutable, you can change anything about its namespace.

On small projects, it's not a huge problem, and lets you hack things together faster, but incredibly confusing on larger projects, where your data could be modified from anywhere.

There are ways of making attributes of classes "more private", such as Name Mangling.

Smashery
Huh. It really _is_ that simple, isn't it? GvR was brave to put such openness into the language in the first place! I wonder if there are cases of introspection, recursion, messaging --- something --- where it proves very useful.
behindthefall
+1  A: 

tom.guest is just a property on the tom function object, it has nothing to do with the scope or locals() inside that function, and nothing to do with that fact that tom is a function, it would work on any object.

Knio
+3  A: 

I think you might be conflating the concepts of local variables and function attributes. For more information on Python function attributes, see the SO question Python function attributes - uses and abuses.

Greg Hewgill
I certainly am! Where's a good description of the difference? BTW, I can say "print dir(tom)" and see the object's dictionary; however, how do I say the equivalent thing from within tom()? (I've tried a number of things that don't work ...)
behindthefall
Oops. I may just have answered my own question. That seems to work from inside tom(), too.
behindthefall
That's right. You will find that `dir(tom)` will be a different list than `locals()` from within tom.
Greg Hewgill
A: 

Python functions are lexically scoped so there is no way to add variables to the function outside of its defined scope.

However, the function still will have access to all parent scopes, if you really wanted to design the system like that (generally considered bad practice though):

>>> def foo():
>>>     def bar():
>>>         print x
>>>     x = 1
>>>     bar()
1

Mutating function variables is mostly a bad idea, since functions are assumed to be immutable. The most pythonic way of implementing this behavior is using classes and methods instead.

fengb
Are functions assumed to be immutable? Python does have closures, and the closed-over values can be mutable types. I do agree that in general your functions should be immutable unless there is good reason to do something tricky like a closure.
steveha
Even for a closure, having a mutable function would be generally very confusing. There might be some use cases, but generally the answer is don't use functions like that; use a class instead and overwrite __call__ to make it look like a function if really necessary.
fengb
+1  A: 

I have used this in the past to make a self-contained function with "enums" that go along with it.

Suppose I were implementing a seek() function. The built-in Python one (on file objects) takes an integer to tell it how to operate; yuck, give me an enum please.

def seek(f, offset, whence=0):
    return f.seek(offset, whence)

seek.START = 0
seek.RELATIVE = 1
seek.END = 2

f = open(filename)

seek(f, 0, seek.START)  # seek to start of file
seek(f, 0, seek.END)  # seek to end of file

What do you think, too tricky and weird? I do like how it keeps the "enum" values bundled together with the function; if you import the function from a module, you get its "enum" values as well, automatically.

steveha
_I_ like it. I also keep getting this niggling little feeling that there's something incredibly useful to be gained by being able to pack some information into a function you're about to call, perhaps recursively. Or something. Can't put my finger on it.
behindthefall
A: 

Python API documentation generation tools, such as pydoc and epydoc, use introspection to determine a function's name and docstring (available as the __name__ and __doc__ attributes). Well-behaved function decorators are expected to preserve these attributes, so such tools continue to work as expected (i.e. decorating a function should preserve the decorated function's documentation). You do this by copying these attributes from the decorated function to the decorator. Take a look at update_wrapper in the functools module:

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
WRAPPER_UPDATES = ('__dict__',)

def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):
    """Update a wrapper function to look like the wrapped function

       wrapper is the function to be updated
       wrapped is the original function
       ...
    """
    for attr in assigned:
        setattr(wrapper, attr, getattr(wrapped, attr))
    for attr in updated:
        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
    ...

So, that's at least one example where modifying function attributes is useful and accepted.

It some situations, it can be useful to "annotate" a function by setting an attribute; Django uses this in a few places:

  • You can set alters_data to True on model methods that change the database, preventing them from being called in templates.

  • You can set allow_tags on model methods that will be displayed in the admin, to signify that the method returns HTML content, which shouldn't be automatically escaped.

As always, use your judgement. If modifying attributes is accepted practice (for example, when writing a decorator), then by all means go ahead. If it's going to be part of a well documented API, it's probably fine too.

elo80ka
+4  A: 

@behindthefall, the motivation to give function objects generic assignable attributes (they didn't use to have them) was that, absent such possibilities, real and popular frameworks were abusing what few assignable attributes existed (typically __doc__) to record information about each given function object. So there was clearly a "pent-up demand" for this functionality, so Guido decided to address it directly (adding an optional dict to each function object to record its attributes isn't a big deal -- most function objects don't need it, and it is optional, so the cost is just 4 bytes for a null pointer;-).

Assigning such attributes in arbitrary places would be very bad practice, making the code harder to understand for no real benefit, but they're very useful when used in a controlled way -- for example, a decorator could usefully record all kinds of things about the function being decorated, and the context in which the decoration occurred, as attributes of the wrapper function, allowing trivially-easy introspection of such metadata to occur later at any time, as needed.

As other answers already pointed out, local variables (which are per-instance, not per-function object!) are a completely disjoint namespace from a function object's attributes held in its __dict__.

Alex Martelli
Hi, Alex. I ran into this in some code at http://pythonprogramming.hottit.com/functional_programming. It was used to implement a pretty-printing "trace" function that took the function to be traced as a parameter. It's presented in the context of recursion, so I thought it might also be useful to pass along state info or some such.Can you direct me to a discussion of the per-instance vs. per-function object distinction?
behindthefall
@behindthefall, I'm not aware of any such existing distinction, but recursion is exactly what makes it salient. In recursion you can have multiple instance of the _same_ function object executing -- their local variables will all be distinct, but their attributes are all the same, as it's the _same_ function object!
Alex Martelli
behindthefall
Oh boy, did I make a typo in that URL in the first comment!! It's "jottit.com", _not_ "hottit", which takes you to a place that's, well, supposedly hot ... to each his or her own, I guess. Anyway, pythonprogramming.jottit.com/functional_programming.
behindthefall