Evan Fosmark
Evan Fosmark

Reputation: 101681

Creating a decorator in a class with access to the (current) class itself

Currently, I'm doing it in this fashion:

class Spam(object):

    decorated = None

    @classmethod
    def decorate(cls, funct):
        if cls.decorated is None:
            cls.decorated = []
        cls.decorated.append(funct)
        return funct


class Eggs(Spam):
    pass


@Eggs.decorate
def foo():
    print "spam and eggs"


print Eggs.decorated # [<function foo at 0x...>]
print Spam.decorated # None

I need to be able to do this in a subclass as shown. The problem is that I can't seem to figure out how to make the decorated field not shared between instances. Right now I have a hackish solution by initially setting it to None and then checking it when the function is decorated, but that only works one way. In other words, if I subclass Eggs and then decorate something with the Eggs.decorate function, it affects all subclasses.

I guess my question is: is it possible to have mutable class fields that don't get shared between base and sub classes?

Upvotes: 0

Views: 254

Answers (3)

Torsten Marek
Torsten Marek

Reputation: 86502

Not that I have anything against metaclasses, but you can also solve it without them:

from collections import defaultdict

class Spam(object):
    _decorated = defaultdict(list)

    @classmethod
    def decorate(cls, func):
        cls._decorated[cls].append(func)
        return func

    @classmethod
    def decorated(cls):
        return cls._decorated[cls]


class Eggs(Spam):
    pass

@Eggs.decorate
def foo():
    print "spam and eggs"

print Eggs.decorated() # [<function foo at 0x...>]
print Spam.decorated() # []

It is not possible to have properties on class objects (unless you revert to metaclasses again), therefore it is mandatory to get the list of decorated methods via a classmethod again. There is an extra layer of indirection involved compared to the metaclass solution.

Upvotes: 0

Evan Fosmark
Evan Fosmark

Reputation: 101681

I figured it out through using metaclasses. Thanks for all who posted. Here is my solution if anybody comes across a similar problem:

class SpamMeta(type):

    def __new__(cls, name, bases, dct):
        SpamType = type.__new__(cls, name, bases, dct)
        SpamType.decorated = []
        return SpamType


class Spam(object):

    __metaclass__ = SpamMeta

    @classmethod
    def decorate(cls, funct):
        cls.decorated.append(funct)
        return funct


class Eggs(Spam):
    pass


@Eggs.decorate
def foo():
    print "spam and eggs"


print Eggs.decorated # [<function foo at 0x...>]
print Spam.decorated # []

Upvotes: 1

Glenn Maynard
Glenn Maynard

Reputation: 57474

I'm fairly sure you can't. I thought about doing this with property(), but unfortunately the class of the class itself--where a property would need to go--is ClassType itself.

You can write your decorator like this, but it changes the interface a little:

class Spam(object):
    decorated = {}

    @classmethod
    def get_decorated_methods(cls):
        return cls.decorated.setdefault(cls, [])

    @classmethod
    def decorate(cls, funct):
        cls.get_decorated_methods().append(funct)
        return funct


class Eggs(Spam):
    pass


@Spam.decorate
def foo_and_spam():
    print "spam"

@Eggs.decorate
def foo_and_eggs():
    print "eggs"

print Eggs.get_decorated_methods() # [<function foo_and_eggs at 0x...>]
print Spam.get_decorated_methods() # [<function foo_and_spam at 0x...>]

Upvotes: 0

Related Questions