我正在尝试编写从类创建类而不修改原始类的函数。
简单解决方案(基于this answer)
def class_operator(cls):
namespace = dict(vars(cls))
... # modifying namespace
return type(cls.__qualname__, cls.__bases__, namespace)
除了type
本身之外,工作正常:
>>> class_operator(type)
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: type __qualname__ must be a str, not getset_descriptor
经过测试 Python 3.2 - Python 3.6 。
(我知道在当前版本中修改namespace
对象中的可变属性会改变原始类,但事实并非如此)
即使我们从__qualname__
删除namespace
参数,如果有
def class_operator(cls):
namespace = dict(vars(cls))
namespace.pop('__qualname__', None)
return type(cls.__qualname__, cls.__bases__, namespace)
结果对象的行为与原始type
>>> type_copy = class_operator(type)
>>> type_copy is type
False
>>> type_copy('')
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: descriptor '__init__' for 'type' objects doesn't apply to 'type' object
>>> type_copy('empty', (), {})
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: descriptor '__init__' for 'type' objects doesn't apply to 'type' object
有人可以解释 Python 内部中的哪种机制可以防止复制type
类(以及许多其他内置类)。
答案 0 :(得分:1)
这里的问题是type
__qualname__
中有一个__dict__
,它是一个属性(即descriptor)而不是字符串:
>>> 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
的子类的内容。对于3参数形式都是如此:
>>> 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)
这不容易规避。修复3参数形式很简单:我们使副本成为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
如此难以复制有两个原因:
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