当前位置: 代码迷 >> python >> 如果类实例作为初始化参数传递,请不要中断
  详细解决方案

如果类实例作为初始化参数传递,请不要中断

热度:36   发布时间:2023-07-16 10:51:57.0

我正在尝试为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 /... 语句列表,用于检查创建者使用了哪些可能性。 我想通过另一种情况扩展它,即,如果传递了一个初始化的实例。

非常感谢

我相信您当前的“丑陋”解决方案实际上是正确的方法。

这会尽可能地提高灵活性,因为它很乱。 即使 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):
           ...

等等。

如果你不知道你的函数是在接受一个对象还是一个初始化程序,那么这是一种代码异味。 看看您是否可以使用用户输入尽可能预先处理并标准化所有内容。

您可以改用函数来获得您想要的相同行为:

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)
    ...

要获得更多“美感”,您可以尝试将该函数调用转换为装饰器。

  相关解决方案