Python 把自己放在别的地方有多危险?

Python 把自己放在别的地方有多危险?,python,python-2.x,Python,Python 2.x,假设我有一个类,它有许多子类 我可以实例化这个类。然后,我可以将它的\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu属性设置为一个子类。我已经在一个活动对象上有效地将类类型更改为其子类的类型。我可以对它调用调用这些方法的子类版本的方法 那么,这样做有多危险?这看起来很奇怪,但做这样的事是不对的吗?尽管能够在运行时更改类型,但这是语言中应该完全避免的特性吗?为什么 (取决于回答,我将发布一个更具体的问题,关于我想做什么,以及是否有

假设我有一个类,它有许多子类

我可以实例化这个类。然后,我可以将它的
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
属性设置为一个子类。我已经在一个活动对象上有效地将类类型更改为其子类的类型。我可以对它调用调用这些方法的子类版本的方法

那么,这样做有多危险?这看起来很奇怪,但做这样的事是不对的吗?尽管能够在运行时更改类型,但这是语言中应该完全避免的特性吗?为什么

(取决于回答,我将发布一个更具体的问题,关于我想做什么,以及是否有更好的选择)。

它的“危险”程度主要取决于子类在初始化对象时会做什么。它完全有可能无法正确初始化,因为只运行了基类的
\uuuu init\uuuu()
,并且由于(比如)未初始化的实例属性,某些操作稍后会失败


即使没有这一点,对于大多数用例来说,这似乎是一个糟糕的实践。首先,简单地实例化所需的类更容易。

以下是我能想到的使这一过程变得危险的事情列表,按从最坏到最坏的大致顺序排列:

  • 阅读或调试代码的人可能会感到困惑
  • 您将无法获得正确的
    \uuuu init\uuuu
    方法,因此可能无法正确初始化所有实例变量(甚至根本无法初始化)
  • 2.x和3.x之间的差异非常显著,移植起来可能会很痛苦
  • 有一些带有类方法、手工编码描述符、方法解析顺序挂钩等的边缘情况,它们在经典类和新样式类(以及2.x和3.x)之间是不同的
  • 如果使用
    \uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
    ,则所有类必须具有。(如果您有兼容但不同的插槽,它一开始可能会工作,但会做一些可怕的事情……)
  • 新样式类中的特殊方法定义可能不会更改。(事实上,这在所有当前Python实现的实践中都是可行的,但它并没有文档记录,所以…)
  • 如果你使用
    \uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
    ,事情将不会按照你天真的
  • 如果这些类有不同的元类,事情会变得更加混乱
同时,在许多您认为必要的情况下,有更好的选择:

  • 使用工厂动态创建相应类的实例,而不是创建基实例,然后将其转换为派生实例
  • 使用
    \uuuu new\uuuu
    或其他机制来钩住构造
  • 重新设计事物,使您拥有一个具有某些数据驱动行为的单一类,而不是滥用继承

作为上一种最常见的特殊情况,只需将所有“变量方法”放入类中,这些类的实例作为“父”的数据成员保存,而不是放入子类中。只需执行
self.member=OtherSubclass(self)
即可,而不必更改
self.\uuuu类\uuuuuu=OtherSubclass(self)
。如果你真的需要方法来神奇地改变,那么自动转发(例如,通过
\uu getattr\uuu
)是一种比动态改变类更常见、更具Python风格的习惯用法。

在任意类上,这是极不可能起作用的,即使它起作用,也是非常脆弱的。这与从一个类的方法中提取底层函数对象,并在不是原始类实例的对象上调用它们基本相同。这是否可行取决于内部实现细节,这是一种非常紧密的耦合形式


这就是说,在一组专门设计用于此方式的类中更改对象的
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
,这是非常好的。我已经意识到你可以这么做很长一段时间了,但我还没有发现这种技术的用途,在这种情况下,一个更好的解决方案不会同时出现在我的脑海中。所以,如果你认为你有一个用例,就去做吧。只需在评论/文档中清楚说明发生了什么。特别地,这意味着所涉及的所有类的实现必须尊重它们的不变量/假设/等,而不是能够孤立地考虑每个类,所以您希望确保任何参与相关代码的人都知道这一点。p> 这里有一个例子,说明了一种不用更改
\uuuu类
就可以做同样的事情的方法。在对问题的评论中引用@unutbu:

假设你在为细胞自动机建模。假设每个细胞可能处于5个阶段中的一个阶段。您可以定义5个类Stage1、Stage2等。假设每个Stage类有多个方法

如果您允许更改
\uuuuuuuuuuuuuuuuuuuuuuuu类
,您可以立即为单元格提供新阶段的所有方法(相同的名称,但不同的行为)

改变当前阶段也是一样的,但这是一件非常正常的事情,不会让任何人感到困惑

另外,它允许您不更改某些不希望更改的特殊方法,只需在
单元格中重写它们即可

此外,它还适用于数据成员、类方法、静态方法等,这是每个中间Python程序员已经理解的

如果您拒绝更改
\uuuuu class\uuuuuuu
,那么您可能必须包含一个stage属性,并使用大量If语句,或者重新分配大量指向不同stage函数的属性

是的,我使用了stage属性,但这并不是缺点,它是跟踪当前stage的明显可见方式,更好地进行调试和可读性

除了
class Stage1(object):
  …

class Stage2(object):
  …

…

class Cell(object):
  def __init__(self):
    self.current_stage = Stage1()
  def goToStage2(self):
    self.current_stage = Stage2()
  def __getattr__(self, attr):
    return getattr(self.current_stage, attr)
class Stage(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

class Stage1(Stage):
    def step(self):
        if ...:
            self.__class__ = Stage2

class Stage2(Stage):
    def step(self):
        if ...:
            self.__class__ = Stage3

cells = [Stage1(x,y) for x in range(rows) for y in range(cols)]

def step(cells):
    for cell in cells:
        cell.step()
    yield cells
class Stage1(object):
    def step(self, cell):
        ...
        if ...:
            cell.goToStage2()

class Stage2(object):
    def step(self, cell):
        ...
        if ...:        
            cell.goToStage3()

class Cell(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.current_stage = Stage1()
    def goToStage2(self):
        self.current_stage = Stage2()
    def __getattr__(self, attr):
        return getattr(self.current_stage, attr)

cells = [Cell(x,y) for x in range(rows) for y in range(cols)]

def step(cells):
    for cell in cells:
        cell.step(cell)
    yield cells
class ChildDispatcher:
    _subclasses = dict()
    def __new__(cls, *args, dispatch_arg, **kwargs):
        # dispatch to a registered child class
        subcls = cls.getsubcls(dispatch_arg)
        return super(ChildDispatcher, subcls).__new__(subcls)
    def __init_subclass__(subcls, **kwargs):
        super(ChildDispatcher, subcls).__init_subclass__(**kwargs)
        # add __new__ contructor to child class based on default first dispatch argument
        def __new__(cls, *args, dispatch_arg = subcls.__qualname__, **kwargs):
            return super(ChildDispatcher,cls).__new__(cls, *args, **kwargs)
        subcls.__new__ = __new__
        ChildDispatcher.register_subclass(subcls)

    @classmethod
    def getsubcls(cls, key):
        name = cls.__qualname__
        if cls is not ChildDispatcher:
            raise AttributeError(f"type object {name!r} has no attribute 'getsubcls'")
        try:
            return ChildDispatcher._subclasses[key]
        except KeyError:
            raise KeyError(f"No child class key {key!r} in the "
                           f"{cls.__qualname__} subclasses registry")

    @classmethod
    def register_subclass(cls, subcls):
        name = subcls.__qualname__
        if cls is not ChildDispatcher:
            raise AttributeError(f"type object {name!r} has no attribute "
                                 f"'register_subclass'")
        if name not in ChildDispatcher._subclasses:
            ChildDispatcher._subclasses[name] = subcls
        else:
            raise KeyError(f"{name} subclass already exists")

class Child(ChildDispatcher): pass

c1 = ChildDispatcher(dispatch_arg = "Child")
assert isinstance(c1, Child)
c2 = Child()
assert isinstance(c2, Child)