Rabih Kodeih
Rabih Kodeih

Reputation: 9521

Discover decorated class instance methods in python

I have a python class, for example:

class Book(models.Model):
    enabled     = models.BooleanField(default=False)
    full_title  = models.CharField(max_length=256)
    alias       = models.CharField(max_length=64)
    author      = models.CharField(max_length=64)
    status      = models.CharField(max_length=64)
    @serializable
    def pretty_status(self):
        return [b for a, b in BOOK_STATUS_CHOICES if a == self.status][0]

The method pretty_status is decorated with @serializable.

What is the simplest and most efficient way to discover the methods in a class that have a certain decoration ? (in the above example giving: pretty_status).

Edit: Please also note that the decorator in question is custom/modifiable.

Upvotes: 3

Views: 162

Answers (3)

Martijn Pieters
Martijn Pieters

Reputation: 1123420

Generally speaking, you can't. A decorator is just syntactic sugar for applying a callable. In your case the decorator syntax translates to:

def pretty_status(self):
    return [b for a, b in BOOK_STATUS_CHOICES if a == self.status][0]
pretty_status = serializable(pretty_status)

That is, pretty_status is replaced by whatever serializable() returns. What it returns could be anything.

Now, if what serializable returns has itself been decorated with functools.wraps() and you are using Python 3.2 or newer, then you can see if there is a .__wrapped__ attribute on the new .pretty_status method; it's a reference to the original wrapped function.

On earlier versions of Python, you can easily do this yourself too:

def serializable(func):
    def wrapper(*args, **kw):
        # ...

    wrapper.__wrapped__ = func

    return wrapper

You can add any number of attributes to that wrapper function, including custom attributes of your own choosing:

def serializable(func):
    def wrapper(*args, **kw):
        # ...

    wrapper._serializable = True

    return wrapper

and then test for that attribute:

if getattr(method, '_serializable', False):
    print "Method decorated with the @serializable decorator"

One last thing you can do is test for that wrapper function; it'll have a .__name__ attribute that you can test against. That name might not be unique, but it is a start.

In the above sample decorator, the wrapper function is called wrapper, so pretty_status.__name__ == 'wrapper' will be True.

Upvotes: 2

unutbu
unutbu

Reputation: 880289

If you have no control over what the decorator does, then in general, you can not identify decorated methods.

However, since you can modify serializable, then you could add an attribute to the wrapped function which you could later use to identify serialized methods:

import inspect
def serializable(func):
    def wrapper(self):
        pass
    wrapper.serialized = True
    return wrapper

class Book:
    @serializable
    def pretty_status(self):
        pass
    def foo(self):
        pass


for name, member in inspect.getmembers(Book, inspect.ismethod):
    if getattr(member, 'serialized', False):
        print(name, member)

yields

('pretty_status', <unbound method Book.wrapper>)

Upvotes: 2

oleg
oleg

Reputation: 4182

You can't discover them directly but You can mark decorated methods with some flag.

import functools
def serializable(func):
    functools.wraps(func)
    def wrapper(*args, **kw):
        # ...

    wrapper._serializable = True
    return wrapper

And then You can make metaclass for example analyse presence or absence of _serializable attribute.

Or You can collect all wrapped methodsin decorator

import functools
DECORATED = {}
def serializable(func):
    functools.wraps(func)
    def wrapper(*args, **kw):
        # ...

    DECORATED[func.__name__] = wrapper
    return wrapper

Upvotes: 0

Related Questions