简体   繁体   中英

'classmethod' object is not callable inside a metaclass's method in Python 3

I am trying to implement a metaclass that initializes class variables when a first its instance is being created. I want to keep a new magic method __load__ that should be called as a classmethod (like __new__ ). So I implemented it like this:

class StaticLoad(type):
    __loaded_classes = set()

    def __call__(cls, *args, **kwargs):
        if cls not in cls.__loaded_classes:
            if hasattr(cls, '__load__'):
                cls.__load__()
            cls.__loaded_classes.add(cls)
        return super().__call__(*args, **kwargs)


class BaseClass(metaclass=StaticLoad):
    s = 0


class MyClass(BaseClass):
    @classmethod
    def __load__(cls):
        print("Loading", cls.__name__, "...")
        cls.s += 1


obj1 = MyClass()
obj2 = MyClass()
print(MyClass.s)

It works fine and gives the correct result:

Loading MyClass ...
1

Now I want to implement the method __load__ as a classmethod by default like __new__ (without the need to type @classmethod above each time). I tried this:

class StaticLoad(type):
    __loaded_classes = set()

    def __call__(cls, *args, **kwargs):
        if cls not in cls.__loaded_classes:
            if hasattr(cls, '__load__'):
                # I try to apply classmethod routine to make
                # cls.__load__ a classmethod
                classmethod(cls.__load__)()
            cls.__loaded_classes.add(cls)
        return super().__call__(*args, **kwargs)


class BaseClass(metaclass=StaticLoad):
    s = 0


class MyClass(BaseClass):
    # @classmethod line was deleted
    def __load__(cls):
        print("Loading", cls.__name__, "...")
        cls.s += 1


obj1 = MyClass()
obj2 = MyClass()
print(MyClass.s)

I got the error:

Traceback (most recent call last):
  File "example.py", line 22, in <module>
    obj1 = MyClass()
  File "example.py", line 7, in __call__
    classmethod(cls.__load__)()
TypeError: 'classmethod' object is not callable

It looks like classmethod routine is correctly available only inside a class definition.

How should I improve my metaclass to make it work fine? I would like to keep the content of classes BaseClass and MyClass as I wrote above, placing all magic into StaticLoad .

With the help of @AnttiHaapala the solution is simple. Instead of calling

classmethod(cls.__load__)()

I had to call

cls.__load__(cls)

If you want to perform transforms on the certain methods and attributes of a class creation, you do that on the metaclass' __new__ function.

Since yu already have a metaclass, all you have to do is to implement its __new__ method to convert any __load__ methods in a classmethod:

class StaticLoad(type):
    __loaded_classes = set()

    def __new__(metacls, name, bases, namespace):
        if "__load__" in namespace and not isinstance(namespace["__load__"], classmethod):
            namespace["__load__"] = classmethod(namespace["load"])
        return super().__new__(metacls, name, bases, namespace)

    def __call__(cls, *args, **kwargs):
        if cls not in cls.__class__.__loaded_classes:
            if hasattr(cls, '__load__'):
                cls.__load__()
            type(cls).__loaded_classes.add(cls)
        return super().__call__(*args, **kwargs)

(the other change I made was to make explict that "__loaded_classes" should be accessed on the metaclass, not on the class itself).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM