4

Suppose I have defined:

def to_class(cls):
  """ returns a decorator
  aimed to force the result to be of class cls. """
  def decorating_func(func):
    def wrapper(*args, **kwargs):
      return cls(func(*args, **kwargs))
    return wrapper
  return decorator(decorating_func)

I wish to use it to create decorators which turn function results to objects of a given class. However, this will not work:

class TestClass(object):
  def __init__(self, value):
    self._value = (value, value)

  def __str__(self):
    return str(self._value)

  @staticmethod
  @to_test_class
  def test_func(value):
    return value

to_test_class = to_class(TestClass)

as test_func will look for to_test_class and will not find it. On the other hand, putting the assignment to to_test_class before the class definition will fail as well, as TestClass will not be defined yet.

Trying to put @to_class(TestClass) above the definition of test_func will also fail, as the method is constructed before the class (if I am not wrong).

The only workaround I have found is to define to_test_class manually as a decorator, and not as one returned from the general "to_class" def.

It might be important to mention that this is only a basic example, but I wish to use to_class for many applications, such as modifying the returned value before 'plugging' it into the class' constructor; and I wish to use it as a decorator for other class' methods as well.

I am sure some think a "to_class" decorator is pointless; manipulations can be done within the decorated method, instead. Though, I find it convenient, and it helps me with readability.

Finally I wish to add that this interests me 20% for practical reasons and 80% for studying reasons, as I find this is something I do not fully understand about decorators in Python in general.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Bach
  • 6,145
  • 7
  • 36
  • 61

3 Answers3

4

Indeed, at class construction time, the class object itself has not yet been constructed, thus you cannot use it as the basis of a decorator.

One work-around I can think of, is to not use the staticmethod decorator. Instead, internally in your own decorator, re-use the classmethod decorator. That way you ensure that Python at the very least passes in the associated class for you:

def to_class(func):
    """ returns a decorator
    aimed to force the result to be of class cls. """
    def wrapper(cls, *args, **kwargs):
        return cls(func(*args, **kwargs))
    return classmethod(wrapper)

Then use it like this:

class TestClass(object):
    def __init__(self, value):
        self._value = (value, value)

    def __str__(self):
        return str(self._value)

    @to_class
    def test_func(value):
        return value

Demonstration:

>>> def to_class(func):
...     """ returns a decorator
...     aimed to force the result to be of class cls. """
...     def wrapper(cls, *args, **kwargs):
...         return cls(func(*args, **kwargs))
...     return classmethod(wrapper)
... 
>>> class TestClass(object):
...     def __init__(self, value):
...         self._value = (value, value)
...     def __str__(self):
...         return str(self._value)
...     @to_class
...     def test_func(value):
...         return value
... 
>>> TestClass.test_func('foo')
<__main__.TestClass object at 0x102a77210>
>>> print TestClass.test_func('foo')
('foo', 'foo')

A generic version of your decorator is not easy; the only other workaround to your conundrum is to use a metaclass hack; see another answer of mine where I describe the method in more detail.

You basically need to reach into the class-under-construction namespace, set a temporary metaclass, and then rely on there being at least one instance of the class before your decorator will work; the temporary metaclass approach hooks into the class creation mechanisms to retrieve the constructed class at a later time.

Seeing as you are using this decorator as an alternative class factory however, that is probably not going to be ideal; if someone used your decorated functions to create class instances exclusively the metaclass would be called too late.

Community
  • 1
  • 1
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Won't work for normal methods (they cannot be made to classmethods), but of course there is a similar approach using `.__class__` for these. Nice solution! – Alfe Nov 16 '12 at 11:36
  • @Alfe: 'normal' methods are already part of an instance, so there is no need to return an instance of the class. – Martijn Pieters Nov 16 '12 at 11:37
  • We're talking about wrapping their return value. 'Normal' methods don't necessarily return an instance of the class they are in. But I think just your wording confused me here. What you meant was maybe 'no need to pass a class to them' (and that's what I meant when I mentioned `.__class__`). – Alfe Nov 16 '12 at 11:42
  • @Alfe: sure, if you wanted to use this decorator on a 'normal' method you'd have to avoid the `classmethod` decorator and grab the class from the first parameter (which is an instance, personally I'd use `type(args[0])` in that case). I am 99.9% certain the OP did not have this scenario in mind though. :-) – Martijn Pieters Nov 16 '12 at 11:44
  • 1
    His definition of `to_class` is rather general though (passing an arbitrary class into it, for instance); and since this is a board not a PM session, other people could read this later, so not just the asker's intention is relevant; also what other people could make out of his question, and I at least understood him differently, so your 99.9% likelihood isn't based on hard numbers (I'm making more than 0.1% of the readers yet). ;-) – Alfe Nov 16 '12 at 11:52
  • I like that use of `type(x)` instead of `x.__class__`. `.__class__` mentions explicitly that I expect an instance and want its class, but using `type()` doesn't look as ugly with all the underscores which make it seem like digging into the dirt of the compiler internals. – Alfe Nov 16 '12 at 11:55
  • @Alfe: Fair enough point, but the OP's original question is much more about not having access to the class while it is being constructed anyway. I can provide more devious methods to work around that restriction still, but for the use-case demonstrated in the question, using a `classmethod` decorator is sufficient. – Martijn Pieters Nov 16 '12 at 11:58
  • 1
    That's true. I just think he had a general decorator in mind (maybe even using it at several places outside of classes) and ran into trouble using it in this case. After all the usecase of forcing the result to be of a specific class is in no way restricted to static methods. – Alfe Nov 16 '12 at 12:00
  • I wish to be able to use to_test_class in many places; within the definition of TestClass (where it can be defined otherwise, say, with 'to_self' decorated method), within the definition of other classes, or even on unbounded methods. – Bach Nov 16 '12 at 12:03
  • Ha! Victory of the 0.1%! Have a look at the update of my answer, but I warn you: It will still be ugly. – Alfe Nov 16 '12 at 12:05
  • @HansZauber: Updated my answer with a little more detail, you are, I am afraid, stuck with a worse work-around still in that case. – Martijn Pieters Nov 16 '12 at 12:07
3

Well, you forgot that class is the first parameter passed to method decorated with classmethod, so you can write it like this:

def to_this_class(func):
    def wrapped(cls, value):
        res = func(cls, value)
        return cls(res)
    return wrapped

class TestClass(object):
    def __init__(self, value):
        self._value = (value, value)

    def __str__(self):
        return str(self._value)

    @classmethod
    @to_this_class
    def test_func(cls, value):
        return value

x = TestClass('a')

print x.test_func('b')
Alfe
  • 56,346
  • 20
  • 107
  • 159
Bunyk
  • 7,635
  • 8
  • 47
  • 79
  • 1
    Requires adding of 'cls' though. And isn't possible in case a different class is wished (after all we could want to return an instance of a class different from the class we are currently defining). – Alfe Nov 16 '12 at 11:39
  • 1
    @Alfe, yes, this is why I called it `to_this_class`. For other classes he could use his decorator, if there are not problems with circular depencencies. – Bunyk Nov 16 '12 at 11:53
0

The problem is that a decorator gets evaluated upon defining the thing it decorates, so when defining the method test_func(), the decorator to_test_class gets called, and even if it already exists, the thing it shall work on (the class TestClass) does not exist yet (as this is created after all methods are created).

Maybe you can use a placeholder at the point where the class is used and later (after the class is created) fill in that value (the class) at the point of the placeholder.

Example:

lazyClasses = {}
def to_lazy_class(className):
  """ returns a decorator
  aimed to force the result to be of class cls. """
  def decorating_func(func):
    def wrapper(*args, **kwargs):
      return lazyClasses[className](func(*args, **kwargs))
    return wrapper
  return decorating_func 

class TestClass(object):
  def __init__(self, value):
    self._value = (value, value)

  def __str__(self):
    return str(self._value)

  @staticmethod
  @to_lazy_class('TestClass')
  def test_func(value):
    return value

lazyClasses['TestClass'] = TestClass

>>> TestClass.test_func('hallo')
<__main__.TestClass object at 0x7f76d8cba190>
Alfe
  • 56,346
  • 20
  • 107
  • 159
  • Yes. You somehow have to inject the value into the decorator later because it does not exist yet when you _call_ the decorator. Actual _use_ of the value is not made when calling the decorator but when later calling the decorated method, so injecting works lazily. But even if you could restrict your usecase to wrapping to the class you are currently defining, the problem would still be that you cannot find out the class a static method is defined in if all you've got is the static method. And that's a dumb restriction of Python :-/ – Alfe Nov 16 '12 at 12:12