239

Not many are aware of this feature, but Python's functions (and methods) can have attributes. Behold:

>>> def foo(x):
...     pass
...     
>>> foo.score = 10
>>> dir(foo)
['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__get__', '__getattribute__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name', 'score']
>>> foo.score
10
>>> foo.score += 1
>>> foo.score
11

What are the possible uses and abuses of this feature in Python ? One good use I'm aware of is PLY's usage of the docstring to associate a syntax rule with a method. But what about custom attributes ? Are there good reasons to use them ?

Eli Bendersky
  • 263,248
  • 89
  • 350
  • 412
  • 2
    Is this very surprising? In general, Python objects support ad-hoc attributes. Of course, some do not, particularly those with builtin type. To me, those those that do not support this seem to be the exceptions, not the rule. – allyourcode Nov 27 '12 at 07:01
  • 3
    One Application in Django: [Customize the admin change list](https://docs.djangoproject.com/en/1.6/intro/tutorial02/#customize-the-admin-change-list) – Grijesh Chauhan Nov 15 '13 at 13:27
  • 4
    Check out [PEP 232](http://www.python.org/dev/peps/pep-0232/). – user140352 Jul 17 '09 at 18:36
  • Here are PEP 232's [additional uses for function attributes](http://mail.python.org/pipermail/python-dev/2000-April/003364.html) – hobs Aug 19 '13 at 18:48
  • 2
    @GrijeshChauhan I came to this question after seeing these docs! – Alexander Suraphel Dec 27 '15 at 14:03
  • 12
    Pity that this is closed, I wanted to add that you can attach any custom exceptions that the function might raise, to provide easy access when catching it in the calling code. I'd provide an illustrative example, but that's best done in an answer. – Will Hardy Mar 14 '17 at 15:59
  • 1
    @allyourcode Yes it is surprising. In most languages a method is not an object. – personal_cloud Sep 18 '17 at 01:30
  • 2
    Pity that this is closed. CherryPy is a nice example of how this is used. – personal_cloud Sep 18 '17 at 01:31
  • Here's [an example of use](https://github.com/django/django/blob/b0654fd6fafc28c3b0476cf2fa0d4eefe4162425/django/core/checks/registry.py#L45) in the django source code – Han Lazarus Oct 03 '19 at 00:49
  • 1
    The Django docs were reorganized. The new link for @GrijeshChauhan's comment is [Customize the admin change list](https://docs.djangoproject.com/en/3.1/intro/tutorial07/#customize-the-admin-change-list) – Mike Slinn Apr 08 '21 at 13:43
  • After 20+ years of python programming I've just used this feature for the first time. I wrote a function to be used as an argparse argument `type` (i.e. to parse string to some other type), and as it's one I'm anticipating reusing, I gave it a `help` attribute, containing a string which is a sensible default for an `add_argument()`'s `help` parameter in cases where this type is used. Very niche, but definitely handy. – gimboland Oct 13 '21 at 21:27

8 Answers8

183

I typically use function attributes as storage for annotations. Suppose I want to write, in the style of C# (indicating that a certain method should be part of the web service interface)

class Foo(WebService):
    @webmethod
    def bar(self, arg1, arg2):
         ...

then I can define

def webmethod(func):
    func.is_webmethod = True
    return func

Then, when a webservice call arrives, I look up the method, check whether the underlying function has the is_webmethod attribute (the actual value is irrelevant), and refuse the service if the method is absent or not meant to be called over the web.

Martin v. Löwis
  • 124,830
  • 17
  • 198
  • 235
  • 4
    Do you think there are down-sides to this? e.g. What if two libraries try to write the same ad-hoc attribute? – allyourcode Nov 27 '12 at 06:58
  • 31
    I was thinking of doing exactly this. Then I stopped myself. "Is this a bad idea?" I wondered. Then, I wandered over to SO. After some bumbling around, I found this question/answer. Still not sure if this is a good idea. – allyourcode Nov 27 '12 at 07:05
  • 11
    This is definitely the most legit use of function attributes of all the answers (as of Nov, 2012). Most (if not all) the other answers use function attributes as a replacement for global variables; however, they do NOT get rid of global state, which is exactly the problem with global variables. This is different, because once the value is set, it does not change; it is constant. A nice consequence of this is that you don't run into synchronization problems, which are inherent to global variables. Yes, you can provide your own synchronization, but that's the point: it's not safe automatically. – allyourcode Nov 27 '12 at 07:17
  • Indeed, I say, as long as attribute doesn't change behaviour of the function in question, it's good. Compare to `.__doc__` – Dima Tisnek Dec 12 '12 at 12:34
  • This approach can also be used to attach output description to the decorated function, which is missing in python 2.*. – Juh_ Oct 15 '13 at 13:37
  • How do I intervene during the method lookup to verify whether the being-called method is a webservice or not? in other words, where do I put the logic that says `if webservice: # allow else: #don't` – Hamza Ouaghad May 05 '16 at 16:00
  • I have tried to do exactly this, but the `@property.setter` statement somehow removes the added attribute and it cannot be seen anymore. I posted my question [here](https://stackoverflow.com/questions/60531297/how-can-the-class-instance-be-accessed-from-a-decorated-method-at-runtime) – Ecuashungo Mar 04 '20 at 17:47
151

I've used them as static variables for a function. For example, given the following C code:

int fn(int i)
{
    static f = 1;
    f += i;
    return f;
}

I can implement the function similarly in Python:

def fn(i):
    fn.f += i
    return fn.f
fn.f = 1

This would definitely fall into the "abuses" end of the spectrum.

mipadi
  • 398,885
  • 90
  • 523
  • 479
  • 3
    Interesting. Are there other ways to implement static variables in python? – Eli Bendersky Dec 03 '08 at 20:39
  • You can use a global, too, but then the value is available...well, globally. – mipadi Dec 03 '08 at 22:12
  • 159
    That's a pretty poor reason to downvote this answer, which is demonstrating an analogy between C and Python, not advocating the best possible way to write this particular function. – Robert Rossney Oct 06 '09 at 07:19
  • @mipadi attributes on global functions are also global! – allyourcode Nov 27 '12 at 07:02
  • 3
    @RobertRossney But if generators are the way to go, then this is a poor use of function attributes. If so, then this is an abuse. Not sure whether to upvote abuses though, since the question asks for those too :P – allyourcode Nov 27 '12 at 07:27
  • @mipadi interesting concept. While it's not really an analogue to C static, it can be quite useful, especially if `fn` is redefined and you expect to get new static state. Compare to using "global" dict(). – Dima Tisnek Dec 12 '12 at 12:31
  • 1
    Definitely seems like an abuse, per PEP 232, thanks @user140352, and hop's comment and [this SO answer](http://stackoverflow.com/a/593046/623735) – hobs Aug 19 '13 at 18:52
  • 3
    @hobs I don't see how it's an abuse per PEP 232. PEP 232 provides some use cases for the mechanism, but doesn't seem to recommend that the use is limited to those use cases. – Evgeni Sergeev Oct 14 '14 at 04:30
  • @EvgeniSergeev, yea, you may be right. PEP 232 disproves the null hypothesis but doesn't prove the hypothesis. But it also initially seemed like a good abuse example, IMO. But now that I research and ponder, I can't think of a better way to implement `static`, so I can see why you see it as a good usage. – hobs Oct 14 '14 at 23:00
  • adding caching functionality to allow persistent variables without dealing with classes, databases, or memcache – Jeremy Leipzig Dec 22 '14 at 16:30
  • I am not sure about the thought process of adding an attribute to maintain what C program does using `static`. Are we not getting into state issue by adding an attribute that store a value? Is there a standard reason, why language designers allowed adding attributes to `function` type objects in python? – overexchange Feb 06 '15 at 09:58
65

You can do objects the JavaScript way... It makes no sense but it works ;)

>>> def FakeObject():
...   def test():
...     print "foo"
...   FakeObject.test = test
...   return FakeObject
>>> x = FakeObject()
>>> x.test()
foo
defnull
  • 4,169
  • 3
  • 24
  • 23
17

I use them sparingly, but they can be pretty convenient:

def log(msg):
   log.logfile.write(msg)

Now I can use log throughout my module, and redirect output simply by setting log.logfile. There are lots and lots of other ways to accomplish that, but this one's lightweight and dirt simple. And while it smelled funny the first time I did it, I've come to believe that it smells better than having a global logfile variable.

Robert Rossney
  • 94,622
  • 24
  • 146
  • 218
  • 10
    re smell: This doesn't get rid of global logfile though. It just squirrels it away in another global, the log function. – allyourcode Nov 27 '12 at 07:08
  • 3
    @allyourcode: But it can help avoid name clashes if you have to have a bunch of global logfiles for different functions in the same module. – firegurafiku Aug 05 '16 at 10:21
16

Function attributes can be used to write light-weight closures that wrap code and associated data together:

#!/usr/bin/env python

SW_DELTA = 0
SW_MARK  = 1
SW_BASE  = 2

def stopwatch():
   import time

   def _sw( action = SW_DELTA ):

      if action == SW_DELTA:
         return time.time() - _sw._time

      elif action == SW_MARK:
         _sw._time = time.time()
         return _sw._time

      elif action == SW_BASE:
         return _sw._time

      else:
         raise NotImplementedError

   _sw._time = time.time() # time of creation

   return _sw

# test code
sw=stopwatch()
sw2=stopwatch()
import os
os.system("sleep 1")
print sw() # defaults to "SW_DELTA"
sw( SW_MARK )
os.system("sleep 2")
print sw()
print sw2()

1.00934004784

2.00644397736

3.01593494415

Kevin Little
  • 12,436
  • 5
  • 39
  • 47
12

I've created this helper decorator to easily set function attributes:

def with_attrs(**func_attrs):
    """Set attributes in the decorated function, at definition time.
    Only accepts keyword arguments.
    E.g.:
        @with_attrs(counter=0, something='boing')
        def count_it():
            count_it.counter += 1
        print count_it.counter
        print count_it.something
        # Out:
        # >>> 0
        # >>> 'boing'
    """
    def attr_decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            return fn(*args, **kwargs)

        for attr, value in func_attrs.iteritems():
            setattr(wrapper, attr, value)

        return wrapper

    return attr_decorator

A use case is to create a collection of factories and query the data type they can create at a function meta level.
For example (very dumb one):

@with_attrs(datatype=list)
def factory1():
    return [1, 2, 3]

@with_attrs(datatype=SomeClass)
def factory2():
    return SomeClass()

factories = [factory1, factory2]

def create(datatype):
    for f in factories:
        if f.datatype == datatype:
            return f()
    return None
DiogoNeves
  • 1,727
  • 2
  • 17
  • 36
  • How does the decorator help? Why not just set `factory1.datatype=list` right below the declaration like old-style decorators? – Ceasar Jun 04 '15 at 22:09
  • 3
    2 main differences: style, multiple attributes are easier to set. You can definitely set as an attribute but gets verbose with multiple attributes in my opinion and you also get an opportunity to extend the decorator for further processing (like having defaults being defined in one place instead of all the places that use the function or having to call an extra function after the attributes were set). There are other ways to achieve all these results, I just find this to be cleaner, but happy to change my mind ;) – DiogoNeves Jun 11 '15 at 11:32
  • Quick update: with Python 3 you need to use `items()` instead of `iteritems()`. – Scott Means May 29 '20 at 14:24
  • 1
    You don't need the extra wrapper function. You could just modify `fn` directly with setattr and return it. – Håken Lid Apr 15 '21 at 17:05
7

Sometimes I use an attribute of a function for caching already computed values. You can also have a generic decorator that generalizes this approach. Be aware of concurrency issues and side effects of such functions!

  • I like this idea! A more common trick for caching computed values is using a dict as the default value of an attribute that the caller is never intended to provide -- since Python evaluates that only once when defining the function, you can store data in there and have it stick around. While using function attributes might be less obvious, it feels significantly less hacky to me. – Soren Bjornstad Aug 09 '19 at 23:44
1

I was always of the assumption that the only reason this was possible was so there was a logical place to put a doc-string or other such stuff. I know if I used it for any production code it'd confuse most who read it.

Dale Reidy
  • 1,189
  • 9
  • 22
  • 2
    I agree with your main point about this most likely being confusing, but re docstrings: Yes, but why do functions have AD-HOC attributes? There could be a fixed set of attributes, one for holding the docstring. – allyourcode Nov 27 '12 at 07:24
  • @allyourcode Having the general case rather than specific ad-hoc cases designed into the language makes things simpler and increases compatibility with older versions of Python. (E.g., code that sets/manipulates docstrings will still work with a version of Python that doesn't do docstrings, so long as it handles the case where the attribute doesn't exist.) – cjs Dec 12 '18 at 00:32