如何克隆一个Python类对象?(不是实例,而是类本身)

huangapple go评论54阅读模式
英文:

How to clone a python class object? (not the instance but the class itself)

问题

假设你有以下的代码:

class A:
    pass

NewA = ... # 复制 A
NewA.__init__ = decorator(A.__init__) # 但不要改变 A 的 init 函数,只改变 NewA 的

我正在寻找一种方法来改变克隆类中的一些属性/方法,而其余部分我希望它们与基类对象类似(最好甚至通过 MappingProxyType,这样当 A 变化时,NewA 未改变的逻辑也会反映出这些变化)。

我遇到了这个古老的帖子,其中提出了一些建议,但并不能完全满足我的要求:

  1. 重用继承 class NewA(A): pass,这并不完全符合我所期望的结果。
  2. 使用 type 动态生成一个新的类,并且以某种方式考虑可能发生的各种情况(具有可变属性/描述符/对全局变量的调用……)。
  3. 使用 copy.deepcopy 是完全错误的(因为类对象的内部数据表示是 MappingProxyType,我们无法复制/深度复制)。

是否有一种方法可以实现这一点,而不是手动处理每个边缘情况,特别是考虑到我们打算复制的基类可能是任何东西(具有元类和自定义 init_subclass 父类,并且包含可变属性和其他内容,还可能包含 __slots__)?

英文:

Imagine you have the following code:

class A:
    pass

NewA = ... # copy A
NewA.__init__ = decorator(A.__init__) # but don't change A's init function, just NewA's

I am looking for a way to change some of the attributes/methods in the cloned class and the rest I want them to be similar to the base class object (preferebly even through MappingProxyType so that when A changes the unchanged logic of NewA reflects the changes as well).

I came across this ancient thread, where there some suggetions which don't fully work:

  1. Repurposing inheritance class NewA(A): pass which doesn't exactly result in what I am looking for
  2. Dynamically generating a new class using type and somehow having an eye out for the tons of cases that might happen (having mutable attributes/descriptors/calls to globals ...)
  3. Using copy.deepcopy which is totally wrong (since class object's internal data representation is a MappingProxyType which we cannot copy/deepcopy)

Is there a way to still achive this without manually handling every corner case, especially considering the fact that the base class we intend to copy could be anything (with metaclasses and custom init_subclass parents, and a mixture of attributes mutable and what not, and potentially with __slots__)?

答案1

得分: 1

以下是翻译好的代码部分:

这里是一个谦卑的尝试帮助你入门我已经尝试过它对于具有插槽的类似乎有效不过我对它的这一方面不太确定

import types
import copy

def clone_class(klass):

    def _exec_body(ns):
        # 不要添加插槽描述符,否则会失败!
        ns_no_slots = {
            k:v for k,v in vars(klass).items()
            if not isinstance(v, types.MemberDescriptorType)
        }
        ns |= copy.deepcopy(ns_no_slots)
        return ns

    return types.new_class(
        name=klass.__name__,
        bases=klass.__bases__,
        kwds={"metaclass": type(klass)},
        exec_body=_exec_body,
    )

现在这似乎适用于具有`__slots__`的类唯一可能会使事情变得复杂的是如果元类具有插槽必须为空)。但那将真的很奇怪

这是一个测试脚本

import types
import copy

def clone_class(klass):
    def _exec_body(ns):
        ns_no_slots = {
            k:v for k,v in vars(klass).items()
            if not isinstance(v, types.MemberDescriptorType)
        }
        ns |= copy.deepcopy(ns_no_slots)
        return ns
    return types.new_class(
        name=klass.__name__,
        bases=klass.__bases__,
        kwds={"metaclass": type(klass)},
        exec_body=_exec_body,
    )

class Meta(type):
    def meta_magic(cls):
        print("magical!")

class Foo(metaclass=Meta):
    __slots__ = ('x','y')
    @property
    def size(self):
        return 42

class Bar(Foo):
    state = []
    __slots__ = ('z',)
    def __init__(self, x=1, y=2, z=3):
        self.x = x
        self.y = y
        self.z = z
    @property
    def closed(self):
        return False

BarClone = clone_class(Bar)

bar = BarClone()
BarClone.state.append('foo')
print(BarClone.state, Bar.state)
BarClone.meta_magic()

这将打印

['foo'] []
magical!

希望这有所帮助。如果您需要进一步的翻译或有其他问题,请告诉我。

英文:

Here is a humble attempt to get you started. I've tested it out with a class with slots and it seems to work. I am not very sure about that aspect of it though.

import types
import copy
def clone_class(klass):
def _exec_body(ns):
# don't add in slots descriptors, it will fail!
ns_no_slots = {
k:v for k,v in vars(klass).items()
if not isinstance(v, types.MemberDescriptorType)
}
ns |= copy.deepcopy(ns_no_slots)
return ns
return types.new_class(
name=klass.__name__,
bases=klass.__bases__,
kwds={"metaclass": type(klass)},
exec_body=_exec_body,
)

Now, this seems to work with classes that have __slots__. The one thing that might trip things up is if the metaclass has slots (which must be empty). But that would be really weird.

Here is a test script:

import types
import copy
def clone_class(klass):
def _exec_body(ns):
ns_no_slots = {
k:v for k,v in vars(klass).items()
if not isinstance(v, types.MemberDescriptorType)
}
ns |= copy.deepcopy(ns_no_slots)
return ns
return types.new_class(
name=klass.__name__,
bases=klass.__bases__,
kwds={"metaclass": type(klass)},
exec_body=_exec_body,
)
class Meta(type):
def meta_magic(cls):
print("magical!")
class Foo(metaclass=Meta):
__slots__ = ('x','y')
@property
def size(self):
return 42
class Bar(Foo):
state = []
__slots__ = ('z',)
def __init__(self, x=1, y=2, z=3):
self.x = x
self.y = y
self.z = z
@property
def closed(self):
return False
BarClone = clone_class(Bar)
bar = BarClone()
BarClone.state.append('foo')
print(BarClone.state, Bar.state)
BarClone.meta_magic()

This prints:

['foo'] []
magical!

huangapple
  • 本文由 发表于 2023年2月16日 17:22:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/75470120.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定