Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to properly copy a class using type

Tags:

According to this answer, a class object cls can be replicated with

cls_copy = type('cls_copy', cls.__bases__, dict(cls.__dict__))

This works perfectly for most normal cases. It does not work when the metaclass of cls is not type. My initial naive fix was to do

cls_copy = type(cls)('cls_copy', cls.__bases__, dict(cls.__dict__))

However, this is simply pointless. There is no way to know what a metaclass does, as this answer to a related question points out, how it transforms the input dictionary, what additional keywords it requires, etc.

The original use of type is almost good enough with a couple of minor exceptions:

  1. The __dict__ created by metaclasses that do not end up calling type.__new__ may be of a different type than the usual proxy object.
  2. Classes extending the copy will not have the correct metaclass, which may cause unexpected behavior.
  3. Any properties or other data descriptors defined in the original metaclass will no longer be available on the class object.

I am willing to ignore item #1. It is a corner case that I am willing to document away should I find a viable solution to the other items. Items #2 and #3 can be solved if it were possible to change the metaclass of the copy. I tried (again, naively)

cls_copy = type('cls_copy', cls.__bases__, dict(cls.__dict__),
                metaclass=type(cls))

This just raised a TypeError, as could be expected:

TypeError: __init_subclass__() takes no keyword arguments

This makes sense in light of the docs:

Like its identity, an object’s type is also unchangeable. 1

However, the footnote states that

It is possible in some cases to change an object’s type, under certain controlled conditions. It generally isn’t a good idea though, since it can lead to some very strange behaviour if it is handled incorrectly.

What are the conditions under which it is possible to change an object's type, specifically that of a class? Is this one of those cases, and if so, how?

Note

I am aware that copy.deepcopy and inheritance are viable alternatives here. For the purpose of this question, I wish to ignore those alternatives and stick with using type-related techniques.

like image 666
Mad Physicist Avatar asked Mar 07 '18 16:03

Mad Physicist


1 Answers

You could use type.__new__(type(cls), cls.__name__, cls.__bases__, dict(cls.__dict__)). This uses the normal type creation process, but creates an instance of type(cls) instead of type.

As for the __metaclass__ issue, I think that is because __metaclass__ is usually what is called, so type can't use it.

like image 110
internet_user Avatar answered Nov 28 '22 06:11

internet_user