Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Decorating class methods by overriding __new__ doesn't work?

I want to decorate all the methods of my class. I have written a sample small decorator for illustration purpose here.

Decorator:

def debug(func):
    msg = func.__name__

    @wraps(func)
    def wrapper(*args, **kwargs):
        print(msg)
        return func(*args, **kwargs)
    return wrapper 

def debugmethods(cls):
    for key, val in vars(cls).items():
        if callable(val):
            setattr(cls, key, debug(val))
    return cls

Now I want to decorate all the methods of my class. One simple way is to use @debugmethods annotation on top of my class but I am trying to understand two other different approaches for doing so.

a) Overriding __new__

class Spam:
    def __new__(cls, *args, **kwargs):
        clsobj = super().__new__(cls)
        clsobj = debugmethods(clsobj)
        return clsobj

    def __init__(self):
        pass

    def foo(self):
        pass

    def bar(self):
        pass

spam = Spam()
spam.foo()

b) Writing metaclass

class debugmeta(type):
    def __new__(cls, clsname, bases, clsdict):
        clsobj = super().__new__(cls, clsname, bases, clsdict)
        clsobj = debugmethods(clsobj)
        return clsobj

class Spam(metaclass = debugmeta):     
    def foo(self):
        pass

    def bar(self):
        pass

spam = Spam()
spam.foo()

I am not sure

  1. Why " a) overriding __new__ " doesn't work ?
  2. Why signature of method __new__ is different in metaclass?

Can someone help me understand what am I missing here.

like image 346
Lokesh Agrawal Avatar asked Feb 04 '19 17:02

Lokesh Agrawal


People also ask

What are the methods of decorating class?

To decorate a method in a class, first use the '@' symbol followed by the name of the decorator function. A decorator is simply a function that takes a function as an argument and returns yet another function. Here, when we decorate, multiply_together with integer_check, the integer function gets called.

Can decorator be used in a class?

In Python, decorators can be either functions or classes. In both cases, decorating adds functionality to existing functions.

Can you decorate a class Python?

A class decorator is one that decorates a python class object. It works just the same as in the case of a function, except that the decorated object is a class. You can guess that the decorator you are implementing should return a reference to a higher-order class, then.

Which of the following decorator is used to declare a class method?

In Python, the @classmethod decorator is used to declare a method in the class as a class method that can be called using ClassName. MethodName() . The class method can also be called using an object of the class.

How to decorate a method in a class in Java?

To decorate a method in a class, first use the ‘@’ symbol followed by the name of the decorator function. A decorator is simply a function that takes a function as an argument and returns yet another function.

How to implement decorators in Java?

Decorators can be implemented in a number of different ways. One useful use-case for decorators involves using them with methods defined in a class. Decorating methods in the classes we create can extend the functionality of the defined method. For example, we could implement a data integrity check, or write the output of the method call to a file.

How to decorate methods in a class with Python?

Decorating Methods defined in a Class With Python 1 Decorating Methods. Decorators provide a convenient and elegant way to add functionality to functions and methods in Pyt... 2 Using the Same Decorator for Multiple Methods. To increase both the usability and the utility of decorators, we can... 3 Summary. More ...


1 Answers

You appear to be confused between __new__ and metaclasses. __new__ is called to create a new object (an instance from a class, a class from a metaclass), it is not a 'class created' hook.

The normal pattern is:

  • Foo(...) is translated to type(Foo).__call__(Foo, ...), see special method lookups for why that is. The type() of a class is it's metaclass.
  • The standard type.__call__ implementation used when Foo is a custom Python class will call __new__ to create a new instance, then call the __init__ method on that instance if the result is indeed an instance of the Foo class:

    def __call__(cls, *args, **kwargs):  # Foo(...) -> cls=Foo
        instance = cls.__new__(cls, *args, **kwargs)  # so Foo.__new__(Foo, ...)
        if isinstance(instance, cls):
            instance.__init__(*args, **kwargs)        # Foo.__init__(instance, ...)
        return instance
    

    So Foo.__new__ is not called when the Foo class itself is created, only when instances of Foo are created.

You don't usually need to use __new__ in classes, because __init__ suffices to initialise the attributes of instances. But for immutable types, like int or tuple, you can only use __new__ to prepare the new instance state, as you can't alter the attributes of an immutable object once it is created. __new__ is also helpful when you want change what kinds of instances ClassObj() produce (such as creating singletons or producing specialised subclasses instead).

The same __call__ -> __new__ and maybe __init__ process applies to metaclasses. A class Foo: ... statement is implemented by calling the metaclass to create a class object, passing in 3 arguments: the class name, the class bases, and the class body, as a dictionary usually. With class Spam(metaclass = debugmeta): ..., that means debugmeta('Spam', (), {...}) is called, which means debugmeta.__new__(debugmeta, 'Spam', (), {...}) is called.

Your first attempt a, setting Spam.__new__ doesn't work, because you are not creating a class object there. Instead, super().__new__(cls) creates an empty Spam() instance with no attributes, so vars() returns an empty dictionary and debugmethods() ends up doing nothing.

If you want to hook into class creation, then you want a metaclass.

like image 135
Martijn Pieters Avatar answered Sep 30 '22 14:09

Martijn Pieters