TypeError: __class__ assignment only supported for

2019-05-06 18:48发布

问题:

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.

回答1:

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)]


回答2:

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?



回答3:

Turns out that __class__ is callable and that there are many other callables as well.

I just want the functions so the following does the job:

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