Проблема здесь в том, что type
имеет __qualname__
в __dict__
, что является свойством (то есть дескриптором 1005 *), а не строкой:
>>> type.__qualname__
'type'
>>> vars(type)['__qualname__']
<attribute '__qualname__' of 'type' objects>
И попытка присвоить не-строку классу __qualname__
выдает исключение:
>>> class C: pass
...
>>> C.__qualname__ = 'Foo' # works
>>> C.__qualname__ = 3 # doesn't work
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only assign string to C.__qualname__, not 'int'
Вот почему необходимо удалить __qualname__
из __dict__
.
Что касается причины, по которой ваш type_copy
не может быть вызван: это потому, что type.__call__
отклоняет все, что не является подклассом type
. Это верно как для формы с тремя аргументами:
>>> type.__call__(type, 'x', (), {})
<class '__main__.x'>
>>> type.__call__(type_copy, 'x', (), {})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: descriptor '__init__' for 'type' objects doesn't apply to 'type' object
А также форма с одним аргументом, которая на самом деле работает только с type
в качестве первого аргумента:
>>> type.__call__(type, 3)
<class 'int'>
>>> type.__call__(type_copy, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: type.__new__() takes exactly 3 arguments (1 given)
Это не легко обойти. Исправить форму с тремя аргументами достаточно просто: мы делаем копию пустым подклассом type
.
>>> type_copy = type('type_copy', (type,), {})
>>> type_copy('MyClass', (), {})
<class '__main__.MyClass'>
Но форма с одним аргументом type
намного противнее, поскольку она работает только в том случае, если первый аргумент - type
. Мы можем реализовать пользовательский метод __call__
, но этот метод должен быть записан в метаклассе, что означает, что type(type_copy)
будет отличаться от type(type)
.
>>> class TypeCopyMeta(type):
... def __call__(self, *args):
... if len(args) == 1:
... return type(*args)
... return super().__call__(*args)
...
>>> type_copy = TypeCopyMeta('type_copy', (type,), {})
>>> type_copy(3) # works
<class 'int'>
>>> type_copy('MyClass', (), {}) # also works
<class '__main__.MyClass'>
>>> type(type), type(type_copy) # but they're not identical
(<class 'type'>, <class '__main__.TypeCopyMeta'>)
Есть две причины, по которым type
так сложно скопировать:
- Это реализовано в C. Вы столкнетесь с подобными проблемами, если попытаетесь скопировать другие встроенные типы, такие как
int
или str
.
Тот факт, что type
является экземпляром самого :
>>> type(type)
<class 'type'>
Это то, что обычно невозможно. Размывает грань между классом и экземпляром. Это хаотическое накопление атрибутов экземпляра и класса. Вот почему __qualname__
является строкой при доступе как type.__qualname__
, но дескриптором при обращении как vars(type)['__qualname__']
.
Как видите, невозможно сделать идеальную копию type
. Каждая реализация имеет различные компромиссы.
Самое простое решение - создать подкласс type
, который не поддерживает вызов с одним аргументом type(some_object)
:
import builtins
def copy_class(cls):
# if it's a builtin class, copy it by subclassing
if getattr(builtins, cls.__name__, None) is cls:
namespace = {}
bases = (cls,)
else:
namespace = dict(vars(cls))
bases = cls.__bases__
cls_copy = type(cls.__name__, bases, namespace)
cls_copy.__qualname__ = cls.__qualname__
return cls_copy
Сложное решение - создать собственный метакласс:
import builtins
def copy_class(cls):
if cls is type:
namespace = {}
bases = (cls,)
class metaclass(type):
def __call__(self, *args):
if len(args) == 1:
return type(*args)
return super().__call__(*args)
metaclass.__name__ = type.__name__
metaclass.__qualname__ = type.__qualname__
# if it's a builtin class, copy it by subclassing
elif getattr(builtins, cls.__name__, None) is cls:
namespace = {}
bases = (cls,)
metaclass = type
else:
namespace = dict(vars(cls))
bases = cls.__bases__
metaclass = type
cls_copy = metaclass(cls.__name__, bases, namespace)
cls_copy.__qualname__ = cls.__qualname__
return cls_copy