Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

python

setattr

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.

like image 847
Ray Salemi Avatar asked Apr 08 '18 14:04

Ray Salemi


2 Answers

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)]
like image 122
Aran-Fey Avatar answered Oct 10 '22 21:10

Aran-Fey


Your instance object has many attributes that shouldn't be reassigned, and most of them pass the callable test:

>>> [item for item in dir(my_base) if callable(getattr(my_base, item))]
['__class__', '__delattr__', '__dir__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'get_one', 'print_hey']

You should restrict most of them. You could simply check for item.startswith('__') but I'm not sure what you want to do with __repr__ and friends. Perhaps check for underbars after a whitelist?

like image 23
tdelaney Avatar answered Oct 10 '22 23:10

tdelaney