Trying to program the general to_class decorator

Suppose I 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 want to use it to create decorators that turn the results of a function into objects of this 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) 

since test_func will look for to_test_class and will not find it. On the other hand, placing the assignment to_test_class before the class definition also fails, because TestClass will not be defined yet.

An attempt to put @to_class (TestClass) above the definition of test_func will also fail, as the method is built in front of the class (if I'm not mistaken).

The only workaround I found was to manually define to_test_class as a decorator, and not as returning from the general "to_class" parameter.

It may be important to note that this is just a basic example, but I want to use to_class for many applications, such as changing the return value before "connecting" it to the class constructor; and I want to use it as a decorator for other class methods.

I'm sure some people think that the to_class decorator is pointless; Instead, manipulations can be performed as part of the decorated method. Although, it is convenient for me, and it helps me with readability.

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

+4
source share
3 answers

Indeed, during the construction of the class, the class object itself is not yet built, so you cannot use it as the basis of the decorator.

One way I can think of is to not use the staticmethod decorator. Instead, internally in your own decorator, reuse the classmethod decorator. This way you guarantee that Python will at least pass 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 as follows:

 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') 

The general version of your decorator is not simple; the only way around your puzzle is to use metaclassing; see my other answer , where I describe this method in more detail.

You basically need to go to the class namespace under construction, set up a temporary metaclass, and then rely on having at least one instance of the class before your decorator works; a temporary metaclass approach captures the mechanisms of creating a class to obtain the constructed class later.

Seeing that you are using this decorator as an alternative factory class, however this probably will not be ideal; if someone used your decorated functions to create instances of a class, then only the metaclass will be called too late.

+4
source

Well, you forgot that the class is the first parameter passed to the method decorated with the 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') 
+3
source

The problem is that the decorator is evaluated when defining the item that it decorates, so the test_func() decorator is called when the test_func() method is to_test_class , and even if it already exists, the item it will work on (class TestClass ) does not exist (since this is created after all methods are created).

Perhaps you can use the placeholder in the place where the class is used, and later (after creating the class) fill in this value (class) in the place 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> 
0
source

Source: https://habr.com/ru/post/1446435/


All Articles