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