问题描述
我正在尝试为python
类添加灵活性,以便它注意到 init 参数之一何时已经是该类的实例。
如果您不介意,请跳过“初始情况”,我是如何来到这里的。
初始情况
我有这门课:
class Pet:
def __init__(self, animal):
self._animal = animal
@property
def present(self):
return "This pet is a " + self._animal
...
并且有许多函数接受此类的实例作为参数( def f(pet, ...)
)。
一切都按预期进行。
然后我想为这些函数的使用增加一些灵活性:如果调用者传递一个Pet
实例,一切都会像以前一样继续工作。
在所有其他情况下,会创建一个Pet
实例。
实现这一目标的一种方法是这样的:
def f(pet_or_animal, ...):
if isinstance(pet_or_animal, Pet): #Pet instance was passed
pet = pet_or_animal
else: #animal string was passed
pet = Pet(pet_or_animal)
...
这也按预期工作,但这些行在每个函数中都会重复。 不干,不好。
目标
所以,我想从每个函数中提取if
/ else
,并将其集成到 Pet 类本身中。
我尝试将其__init__
方法更改为
class PetA: #I've changed the name to facilitate discussion here.
def __init__(self, pet_or_animal):
if isinstance(pet_or_animal, PetA):
self = pet_or_animal
else:
self._animal = pet_or_animal
...
并开始每个功能
def f(pet_or_animal, ...):
pet = PetA(pet_or_animal)
...
但是,这是行不通的。 如果传递了 Pet 实例,则一切正常,但如果调用了字符串,则无法正确创建 Pet 实例。
当前(丑陋)解决方案
什么是工作,是一类方法添加到类,如下所示:
class PetB: #I've changed the name to facilitate discussion here.
@classmethod
def init(cls, pet_or_animal):
if isinstance(pet_or_animal, PetB):
return pet_or_animal
else:
return cls(pet_or_animal)
def __init__(self, animal):
self._animal = animal
...
并将功能更改为
def f(pet_or_animal, ...):
pet = PetB.init(pet_or_animal) #ugly
...
问题
-
有谁知道,如何更改
PetA
类,使其具有预期的行为? 可以肯定的是,这里是快速测试:
pb1 = PetB.init('dog')
pb2 = PetB.init(pb1) #correctly initialized; points to same instance as pb1 (as desired)
pa1 = PetA('cat')
pa2 = PetA(pa1) #incorrectly initialized; pa1 != pa2
更一般地说,这是增加这种灵活性的正确方法吗? 我考虑的另一个选择是编写一个单独的函数来进行检查,但这也很丑陋,而且是另一件需要跟踪的事情。 我宁愿保持一切整洁并包裹在课程本身中。
最后一句话:我意识到有些人可能会发现添加的类方法 (
petB
) 是一种更优雅的解决方案。 我更喜欢添加到__init__
方法 (petA
) 的原因是,在我的实际使用中,我已经允许许多不同类型的初始化参数。 因此,已经有一个if
/elif
/elif
/... 语句列表,用于检查创建者使用了哪些可能性。 我想通过另一种情况扩展它,即,如果传递了一个初始化的实例。
非常感谢
1楼
我相信您当前的“丑陋”解决方案实际上是正确的方法。
这会尽可能地提高灵活性,因为它很乱。 即使 python 允许任意类型和值浮动,您的用户和您自己都会感谢您将其限制在最外层。
我会认为它(不需要以这种方式实现)
class Pet:
@classmethod
def from_animal(cls, ...):
...
@classmethod
def from_pet(cls, ...):
...
@classmethod
def auto(cls, ...):
if is_pet(...):
return cls.from_pet(...)
def __init__(cls, internal_rep):
...
等等。
如果你不知道你的函数是在接受一个对象还是一个初始化程序,那么这是一种代码异味。 看看您是否可以使用用户输入尽可能预先处理并标准化所有内容。
2楼
您可以改用函数来获得您想要的相同行为:
def make_pet_if_required(pet_or_animal):
if isinstance(pet_or_animal, PetA):
return pet_or_animal
else:
return Pet(pet_or_animal)
进而:
def f(pet_or_animal, ...):
pet = make_pet_if_required(pet_or_animal)
...
要获得更多“美感”,您可以尝试将该函数调用转换为装饰器。