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)