Search code examples
pythonsetattr

TypeError: __class__ assignment only supported for heap types or ModuleType subclasses


I'm trying to copy functions from an arbitrary 'base' class into my new object. However, I'm getting the following error with this sample code.

class my_base:
    def print_hey():
        print("HEY")

    def get_one():
        print(1)

class my_ext:
    def __init__(self, base):
        methods = [method for method in dir(base) if callable(getattr(base, method))]
        for method in methods:
            setattr(self, method, getattr(base, method))


me = my_ext(my_base)
me.get_one()

The above gets this error on the call to setattr.

 TypeError: __class__ assignment only supported for heap types or ModuleType subclasses

The statement works if I type it into the prompt after defining the above.


Solution

  • The problem here is that all objects in python have a __class__ attribute that stores the type of the object:

    >>> my_base.__class__
    <class 'type'>
    >>> type(my_base)
    <class 'type'>
    

    Since calling a class is how you create an instance of that class, they're considered callables and pass the callable test:

    >>> callable(my_base)
    True
    >>> my_base()
    <__main__.my_base object at 0x7f2ea5304208>
    

    And when your code tries to assign something to the __class__ attribute the TypeError you've observed is thrown:

    >>> object().__class__ = int
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: __class__ assignment only supported for heap types or ModuleType subclasses
    

    So you need to be more specific about which attributes should be copied.

    You could filter out attributes with double underscores:

    methods = [method for method in dir(base) if not method.startswith('__')
                                                 and callable(getattr(base, method))]
    

    Or you could filter out classes:

    methods = [method for method in dir(base) if callable(getattr(base, method)) and
                                         not isinstance(getattr(base, method), type)]
    

    Or you could only allow functions by comparing to types.FunctionType:

    methods = [method for method in dir(base) if callable(getattr(base, method)) and
                               isinstance(getattr(base, method), types.FunctionType)]