Automating the application of decorator to class methods by decorating a class.

Automating the application of decorator to class methods by decorating a class.

#Python #DesignPatterns

Difficulty level: Advanced.

Intended Audience: Python programmers developing frameworks and have good knowledge of decorator design pattern in Python.

Source Code: The full source is provided at the end of article for reference.

Background: A participant asked an interesting question while I was covering the topic of class decorators at CoreCode Programming Academy. I will characterize the problem as follows:

Problem:

Requirement: A function decorating another function to tinker with the decorated function's parameters has been implemented. We have to apply that function decorator to all methods of a class whose names do not start and end with two underscores (in other words we must apply a decorator to all non-special methods of a class). Can we automate this process instead of manually applying the decorator to each method?

Solution:

We must apply the decorator to the class, which is the target for automating the decoration of its methods. And the decorator is implemented as a class in itself.

Referring to our example:

a) The 'method_dec' is a function decorator which tinkers with the parameters of the decorated function object.

b) The 'Test' class contains two methods viz. 'get' and 'set' which are not special method names. Therefore, we have to apply the 'method_dec' on them. It can be done manually by modifying the source code a class.

c) In order to automate it we have implemented a class decorate named 'cls_dec' which will decorate class 'Test' and apply the 'method_dec' on it.

Algorithm:

Step 1:

Application of the class decorator 'cls_dec' will hook the creation of the class Test. The body of Test class will be executed, and class namespace (dictionary containing variables defined in class along with their objects) will be encapsulated in newly allocated object of class 'type'. In normal course, the 'type' object will bind itself with class name Test and the statement will finish. Because of decorator, the 'type' object, will be sent as actual parameter to the class of cls_dec.__init__, instead of getting bound with class name Test.


Step 2:

Initiate a for loop to go through all variables name : object pairs defined within the class. This is achieved through iterating over cls.__dict__.items().

For each attribute in class, we do the following:

a) Check whether the current attribute name is data or function. If its data then go to the next iteration

b) if its a function but its name starts starts and ends with two underscores. If it is then also go to the next iteration.

c) If the current attribute is a function and its not a special method name then we apply the 'method_dec' decorator to it. But we must apply the decorator manually and not by \@ syntax. The following code shows, how to apply decorator manually.

@method_dec 
def function_name(parameter list):
    body 

== 

function_name = method_dec(function_name)         

We use

setattr(self.cls, cls_attr, method_dec(cls_val))        

to achieve the same effect. Because cls.__dict__ cannot be modified manually.

Step 3:

Because the class Test is decorated by the class cls_dec, the Test variable will be an object of the class cls_dec but the client will continue to think of Test as class. And the client is going to try to instantiate what is a 'class' to it by putting call operator around it. Therefore, we must an object of the 'cls_dec', a callable object. We do so by implementing __call__() method in cls_dec. The cls_dec constructor saves the 'type' object of decorated class. In __call__(), we simply instantiate the object and return it to the client. Note that *args, **kwargs make the formal parameter list of call method compatible with initialization list of any class.

SOURCE CODE FOR REFERENCE

"""
Requirement: A function decorator is implemented to tinkter with 
decorated function's parameters and this decorator must be applied 
to all non-special methods (methods whose name do not start and end 
with '__') of a given class. 

Can we automate application of the decorator instead of individually 
applying them to class methods? 
"""

def method_dec(F): 
    def inner_function(*args, **kwargs): 
        print(f"COMMON TINKERING CODE for call to:{F.__name__}")
        return F(*args, **kwargs)
    return inner_function

class cls_dec: 
    def __init__(self, cls): 
        self.cls = cls 
        for (cls_attr, cls_val) in cls.__dict__.items(): 
            if (
                type(cls_val) == type(lambda:None) and 
                not cls_attr.startswith('__') and 
                not cls_attr.endswith('__')
            ): 
                setattr(self.cls, cls_attr, method_dec(cls_val))
              
    def __call__(self, *args, **kwargs): 
        return self.cls(*args, **kwargs)

@cls_dec 
class Test: 
    x = 10 
    y = 20 
    def __init__(self, init_n): 
        self.n = init_n 

    def __str__(self): 
        return str(self.n)
    
    def __repr__(self): 
        return str(self.n)

    def get(self): 
        return self.n 
    
    def set(self, n): 
        self.n = n 

t = Test(10)
print(t.get())
t.set(20)
print(t.get())
        

Wow, your article on automating application of decorators to class methods is super detailed and insightful! Consider diving more into Python's asynchronous features next, they're pretty powerful too. What area of software development excites you the most for your future career?

回复

要查看或添加评论,请登录

Yogeshwar Shukla的更多文章

  • How to design a data structure neatly using Python OOP features?

    How to design a data structure neatly using Python OOP features?

    Difficulty Level: Upper Intermediate. Audience: Anyone who knows one OOP language and has implemented a linked list.

  • Reduce in C++.

    Reduce in C++.

    [Difficulty Level: Intermediate, Intended audience: must have basic familiarity with C++ lambdas, STL and template…

社区洞察

其他会员也浏览了