我有一个动物园,里面有动物,用物体来表示。历史上,只有Animal
类存在,动物对象是用例如x = Animal('Bello')
,以及使用isinstance(x, Animal)
完成的类型检查。
最近,区分物种变得很重要。Animal
已经成为ABC,所有动物对象现在都是它的子类的示例,例如Dog
和Cat
。
这个改变允许我直接从一个子类创建一个动物对象,例如。在下面的代码中使用dog1 = Dog('Bello')
。这很便宜,我可以使用它,只要我知道我在处理什么样的动物。isinstance(dog1, Animal)
的类型检查仍然像以前一样工作。
然而,为了可用性和向后兼容性,我还希望能够调用dog2 = Animal('Bello')
,让 it(从输入值)确定物种,并返回一个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)
进行类型检查,因此破坏了向后兼容性。
4条答案
按热度按时间6qftjkof1#
在收到评论中的更多信息和问题更新后,更新了答案:
原文答案如下:
我通常会提倡使用类方法作为替代构造函数,而不是编写执行重要工作的
__new__
或__init__
。在这种情况下,它将是一个静态方法。例如:
然而,在这个特定的例子中,我们有一个来自Python标准库的例子:pathlib。如果示例化一个
Path
,它实际上将返回一个WindowsPath
或PosixPath
,具体取决于您的平台,这两个都是Path
的子类。这是如何做到的:(其中
cls._from_parts
调用object.__new__
。)在您的情况下,这将是类似于:
注意,在这种情况下,不应该定义
__init__
,因为它将使用 original 参数调用,而不是修改后的参数。2lpgd9682#
这是一个有趣的问题,所以我想出了一个可能对你有用的答案。
这里的想法是覆盖
__call__
来解析name
参数,以获得类和动物名称,然后使用动物名称分派到正确的类。输出:
注意:这对我的喜欢来说一般是太多的魔法了,但这是一种做你想做的事情的方法。
jw5wzhpr3#
我可以通过检查示例是否已经通过
Dog.__init__
方法来创建所需的功能。为此,我在此方法的代码中添加了3行,如下所示。由于需要将这些行添加到每个子类中,因此创建一个装饰器可能是有意义的,如下所示:
如果有一个好的理由,为什么这是一个坏的做法,并且存在更好的解决方案,我真的很想听听。
nr7wwzry4#
最后,我使用了一个类装饰器:
IMO,这是最干净和最少侵入性的解决方案。