我有一个动物园,里面的动物用对象表示。历史上,只有 Animal
类存在,例如通过 x = Animal('Bello')
创建动物对象,并使用 isinstance(x, Animal)
进行类型检查。
最近,区分物种变得很重要。 Animal
已成为 ABC,并且所有动物对象现在都是其子类(例如 Dog
和 Cat
)的实例。
这个改变允许我直接从其中一个子类创建动物对象,例如在下面的代码中使用 dog1 = Dog('Bello')
。这很便宜,只要我知道我正在处理什么类型的动物。类型检查 isinstance(dog1, Animal)
仍然像以前一样工作。
然而,为了可用性和向后兼容性,我还想能够调用 dog2 = Animal('Bello')
,让 它(来自输入值)确定物种,并返回 Dog
实例 - 即使这会计算更多。
我需要第二种方法的帮助。
这是我的代码:
class Animal:
def __new__(cls, name):
if cls is not Animal: # avoiding recursion
return super().__new__(cls)
# Return one of the subclasses
if name.lower() in ['bello', 'fido', 'bandit']: # expensive tests
name = name.title() # expensive data correction
return Dog(name)
elif name.lower() in ['tiger', 'milo', 'felix']:
# ...
name = property(lambda self: self._name)
present = lambda self: print(f"{self.name}, a {self.__class__.__name__}")
# ... and (many) other methods that must be inherited
class Dog(Animal):
def __init__(self, name):
self._name = f"Mr. {name}" # cheap data correction
# ... and (few) other dog-specific methods
class Cat(Animal):
def __init__(self, name):
self._name = f"Dutchess {name}" # cheap data correction
# ... and (few) other cat-specific methods
dog1 = Dog("Bello")
dog1.present() # as expected, prints 'Mr. Bello, a Dog'.
dog2 = Animal("BELLO")
dog2.present() # unexpectedly, prints 'Mr. BELLO, a Dog'. Should be same.
备注:
在我的用例中,第二种创建方法是最重要的。
我想要实现的是调用
Animal
后返回一个子类实例,例如Dog
,并用操纵后的参数(如本例中的name
)进行初始化。因此,我正在寻找一种方式来保留上述代码的基本结构,其中可以调用父类,但始终返回一个子类实例。
当然,这只是一个虚构的示例;)
非常感谢,如果需要更多信息,请告诉我。
次优解决方案
工厂函数
def create_animal(name) -> Animal:
# Return one of the subclasses
if name.lower() in ['bello', 'fido', 'bandit']:
name = name.title()
return Dog(name)
elif name.lower() in ['tiger', 'milo', 'felix']:
# ...
class Animal:
name = property(lambda self: self._name)
present = lambda self: print(f"{self.name}, a {self.__class__.__name__}")
# ... and (many) other methods that must be inherited
class Dog(Animal):
# ...
这会破坏后向兼容性,不再允许使用Animal()
来创建动物。类型检查仍然是可能的。
我更喜欢能够以完全相同的方式调用特定物种的Dog()
或使用更通用的Animal()
的对称性,但在此处不存在这种情况。
工厂函数,备选方案
与以前相同,但将Animal
类的名称更改为AnimalBase
,将create_animal
函数的名称更改为Animal
。
这解决了先前的问题,但会破坏后向兼容性,不再允许使用isinstance(dog1, Animal)
进行类型检查。
__new__
来实现这个,但我强烈建议使用单独的工厂方法来减少循环依赖。 - mousetail__new__
的问题在于,由Dog
继承的Animal.__new__
仍然是创建Dog
新实例的方法。现在你有一个方法尝试着做两件完全不同的事情,取决于它的cls
参数的值。 - chepner