我想重写
__new__
函数,对于一个特定的类,根据传递的输入,它从一个类或另一个类返回一个对象。
我写了下面的代码——这似乎有效——但感觉就像在作弊,我想知道这是“最好的”还是“最像 python 的”方法:
class A:
def __new__(cls, x):
if x == 1:
a = 0
self = B.__new__(A, a)
self.cls = B
else:
a = 0
b = 0
self = C.__new__(A, a, b)
self.cls = C
return self
def __init__(self, x):
self.__class__ = self.cls
class B(A):
def __new__(cls, a):
self = object.__new__(cls)
self.a = a
return self
def print_this(self):
print("self.a is: ", self.a)
print("class is B")
class C(A):
def __new__(cls, a, b):
self = object.__new__(cls)
self.a = a
self.b = b
return self
def print_this(self):
print("self.a is: ", self.a)
print("self.b is: ", self.b)
print("class is C")
xxx = A(1)
print("xxx.__class__: ", xxx.__class__)
xxx.print_this()
yyy = A(2)
print("yyy.__class__: ", yyy.__class__)
yyy.print_this()
它返回以下内容,这就是我想要的
xxx.__class__: <class '__main__.B'>
self.a is: 0
class is B
yyy.__class__: <class '__main__.C'>
self.a is: 0
self.b is: 0
class is C
是的,它很脏 - 对于读者来说,
A()
实际上创建了另一个类的实例并不明显,如果有人在 B.__new__
中更改某些内容,他可能会破坏他永远不会想到的部分,并且对于没有经验的 python 来说可能很难理解程序员这是怎么回事。
你搜索的是工厂设计模式。
from enum import Enum
class ClsEnum(Enum):
B = "B"
C = "C"
class A:
pass
def cls_factory(e: ClsEnum) -> A:
if e == ClsEnum.B:
return B(0)
elif e == ClsEnum.C:
return C(0, 0)
raise TypeError
class B(A):
def __init__(self, a):
self.a = a
def print_this(self):
print("self.a is: ", self.a)
print("class is B")
class C(A):
def __init__(self, a, b):
self.a = a
self.b = b
def print_this(self):
print("self.a is: ", self.a)
print("self.b is: ", self.b)
print("class is C")
xxx = cls_factory(ClsEnum.B)
print("xxx.__class__: ", xxx.__class__)
xxx.print_this()
yyy = cls_factory(ClsEnum.C)
print("yyy.__class__: ", yyy.__class__)
yyy.print_this()
此代码提供与您的版本相同的输出。
我在处理类似情况时遇到了这个问题,并认为我会添加我的方法,因为它运作良好。
IMO 这种方法有一个有效的用例,在我的例子中是在使用身份映射时,其中应该只有一个具有给定 ID 的对象实例。由于只能有一个实例,它应该是最具体的子类,因为用户以后将无法创建它的另一个实例。
这是一个工作的、符合 mypy 的(Python 3.11)示例,它说明了我的方法。
from __future__ import annotations
from typing import Type, Optional
from abc import ABC
class A(ABC):
# return new instance of either A or appropriate subclass of A
def __new__(cls: Type[A], arg: Optional[str] = None):
print(f'A.__new__({cls}, arg={arg})')
# map arg to subclass (can be based on heuristic inspection of args)
cls_map = {
'B': B,
'C': C
}
if arg in cls_map:
# use more specific class
cls_new = cls_map[arg]
else:
# no subclass required
cls_new = cls
if cls is cls_new:
# already have correct class, create new object
return object.__new__(cls_new)
else:
# invoke correct __new__
return cls_new.__new__(cls_new, arg)
def __init__(self, arg: Optional[str] = None):
print(f'A.__init__({arg})')
# subclasses which can be returned by A()
class B(A): pass
class C(A): pass
巧妙的是 B/C 根本不需要实现 __new__ 或意识到 A.__new__ 中发生了任何异常情况。然而,他们可以实现 __new__ 并且它仍然可以正常工作,如果需要一些常见的处理(比如我的身份映射场景),他们甚至可以调用 super().__new__ 。
测试代码:
a = A()
assert type(a) is A
b = A('B')
assert type(b) is B
c = A('C')
assert type(c) is C
输出:
A.__new__(<class '__main__.A'>, arg=None)
A.__init__(None)
A.__new__(<class '__main__.A'>, arg=B)
A.__new__(<class '__main__.B'>, arg=B)
A.__init__(B)
A.__new__(<class '__main__.A'>, arg=C)
A.__new__(<class '__main__.C'>, arg=C)
A.__init__(C)
或者你可以使用工厂函数,但是你必须在任何地方调用工厂函数而不是仅仅实例化 IMO 更直观的类。基本上,工厂函数由构造函数本身包装。你返回一个比你要求的更具体的类并没有违反任何规则——它仍然会像鸭子一样嘎嘎叫,只是可能是一个更具体的嘎嘎。
就是说,一个类很少知道哪些类是它的子类,而且这种方法很少有必要。