Reputation: 101681
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
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
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
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