Не нарушайте, если экземпляр класса передается в качестве аргумента инициализации - PullRequest
0 голосов
/ 21 марта 2020

Я пытаюсь добавить гибкость классу 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)
    ...

Это также работает, как и ожидалось, но эти строки повторяются в каждой функции. Не DRY, не хорошо.

Цель

Итак, я хотел бы извлечь 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
  • В целом, это правильный путь к go о добавлении этой гибкости? Другим вариантом, который я рассмотрел, было написание отдельной функции, чтобы просто выполнять проверку, но это тоже довольно уродливо и еще одна вещь, которую нужно отслеживать. Я бы предпочел держать все аккуратно и завернутым в самом классе.

  • И последнее замечание: я понимаю, что некоторые люди могут найти добавленный метод класса (petB) более элегантным решение. Причина, по которой я предпочитаю добавить метод __init__ (petA), заключается в том, что в моем реальном использовании я уже допускаю множество различных типов аргументов инициализации. Итак, уже есть список операторов if / elif / elif / ..., которые проверяют, какая из возможностей используется создателем. Я хотел бы расширить это еще на один случай, а именно, если передан инициализированный экземпляр.

Большое спасибо

Ответы [ 2 ]

1 голос
/ 21 марта 2020

Я полагаю, что ваше текущее "уродливое" решение на самом деле является правильным подходом.

Это повышает гибкость, насколько это возможно, так как это грязно. Даже при том, что 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):
           ...

et c.

Запах кода, если вы не знаете, принимает ли ваша функция объект или инициализатор. Посмотрите, сможете ли вы выполнить обработку как можно более заранее с помощью пользовательского ввода и стандартизировать все остальное.

0 голосов
/ 21 марта 2020

Вместо этого вы можете использовать функцию, чтобы получить то же поведение, что и вы:

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

Для большей «красоты» вы можете попробовать повернуть вызов этой функции. в декоратор.

...