views:

138

answers:

3

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?

A: 

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...>]
Glenn Maynard
I got it to work perfectly with using metaclasses. I posted the answer if you're curious as to how. Thanks for investigating, Glenn.
Evan Fosmark
+1  A: 

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 # []
Evan Fosmark
A: 

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.

Torsten Marek
Yes, this seems to be similar to how Glenn did it. Even though it works for this example, it still doesn't solve the issue at hand being that I didn't want class fields shared between parent and child.
Evan Fosmark