Найти - Пользователи
Полная версия: Один объект, который ведет себя как два разных объекта
Начало » Python для экспертов » Один объект, который ведет себя как два разных объекта
1 2
Master_Sergius
Здравствуйте, мне понравилось обсуждение моего предыдущего дурацкого вопроса http://python.su/forum/topic/40304/, и я решил замутить еще одно

Начнем с сути проблемы: есть апишка (всё та же, но не суть), все ответы которой представляются в сдк как объекты/модели (можно сказать, аналог ORM):

  
class User:
    first_name = None
    last_name = None
    position = None
 
users = group_and_users_client.list_users()
 
# сырой ответ от API: [{"userId": "xxx", "firstName": "John", "lastName": "Connor", "position": "Tester"}]
# в переменной users будет список объектов типа User такого вида:
 
for user in users:
     print(user.first_name, user.last_name, user.position)

Есть Enum Positions:

  
from aenum import Enum, extend_enum
 
 
class Position(
    str,
    Enum
):
    DEV = "DEV"
    TESTER = "TESTER"
    MANAGER = "MANAGER"
 
    @classmethod
    def _missing_(cls, value):
        value_upper_case = value.upper()
        try:
            return getattr(cls, value_upper_case)
        except:
            extend_enum(Position, value_upper_case, value_upper_case)
            return getattr(cls, value_upper_case)

То есть, если нет позиции в списке, то список расширяется спокойно. Даже не спрашивайте, почему так )
Фокус в том, что есть наследование от класса User:
   
class Dev(User):
   position = 'DEV'
 
class Tester(User):
   position = 'TESTER'
 
class MANAGER(User):
   position = 'MANAGER'

И вот, если есть позиция в Enum, то возвращается соответствующая модель, а если позиции нет в списке, то возвращается базовый класс User, где position = Position(“any_position”)
Итого, получается, что в тех известных моделях, position - строчка (str), а в базовой модели - Position.

Выходит наша sdk возвращает несовместимые типы позиций:

  
for user in users:
   print(user.position, type(user.position))
  
# выхлоп (AQA - Automation QA):
DEV <class 'str'>
TESTER <class 'str'>
Position.AQA <aenum 'Position'>

И вот задача, надо сделать типы совестимыми, но, блин, без breaking change!!!
То есть, выходит, что вдруг кто-то решал для себя проблему таким образом:
   
if isinstance(user.position, str):
   print(user.position)
else:
   print(user.position.value)
 
# или наоборот
if isinstance(user.position, Position):
   print(user.position.value)
else:
   print(user.position)

то, надо сделать так, чтобы и оно работало и просто все position были строчками!!!

Пока что мне удалось придумать лишь половину решения:
 class PositionWrapper(str):
    def __new__(cls, position):
        if isinstance(position, Enum):
            return str.__new__(cls, position.value)
        return str.__new__(cls, position)
 
    def __init__(self, position):
        self.position = position
 
    def __str__(self):
        return self.value
 
    @property
    def value(self):
        if isinstance(self.position, Enum):
            return self.position.value
        return self.position 

А вот наследоваться от Position никак нельзя:
  File "/Users/bserhii/Projects/work/okta/venv/lib/python3.8/site-packages/aenum/__init__.py", line 1638, in __prepare__
    member_type, first_enum = metacls._get_mixins_(bases)
  File "/Users/bserhii/Projects/work/okta/venv/lib/python3.8/site-packages/aenum/__init__.py", line 2263, in _get_mixins_
    raise TypeError("cannot extend enumerations via subclassing")
TypeError: cannot extend enumerations via subclassing

В общем, что делать?
AD0DE412
кхм кхм эээ ок enum неизменяемый но вам нужно сделать его ну или что бы это походило на “изменяемый” enum так?
и второе без изменений в существующем коде (новый добавить можно)
Master_Sergius
Мне нужно сделать, чтобы:
1)
Вместо такого:

  
for user in users:
   print(user.position)
   
# выхлоп (AQA - Automation QA):
DEV <class 'str'>
TESTER <class 'str'>
Position.AQA <aenum 'Position'>

было что-то вроде такого:
  
for user in users:
   print(user.position)
   
# выхлоп (AQA - Automation QA):
DEV <class 'str'>
TESTER <class 'str'>
AQA <class 'str'>

2) не внести “breaking change”, то есть не поломать то, что могло б работать для других юзеров, то есть, если вдруг они обходили проблему через проверку инстансов, чтоб привести все к одному виду.
AD0DE412
John Crawford 13 Мар 2019 в 20:00 ?
зы хотя скоре всего нет
наверное вы уже прошерстили че как и где в этих интернетах
Master_Sergius
Не уверен, что это поможет, разве что переписать по-другому класс Position и тогда плясать оттуда.
Даже не знаю, как без “breaking change” чтоб для “старых” пользователей нашей SDK работало это:

   
for user in users:
   if isinstance(user.position, str):
      print(user.position)
   else:
      print(user.position.value)
  
# или наоборот
for user in users:
   if isinstance(user.position, Position):
      print(user.position.value)
   else:
      print(user.position)

А для “новых” пользователей SDK работало это:

  
for user in users:
   print(user.position)

И всюду чтоб одинаковый тип… бред… но насяльнике хочет, чтоб без “breaking change”
AD0DE412
ну там вроде уникальность теряется
вернее ... объекты эээ исходные и то что получилось не true
Rodegast
> В общем, что делать?

Нормально проектировать программу, а не заниматься ерундой. У тебя position должен быть перечислением. Множественное наследование из класса Position нужно удалить и всё отрефакторить.
AD0DE412
Rodegast ну так там у нее/него там ситуация
doza_and
AD0DE412
ну так там у нее/него там ситуация
Ну так а некоторые высочайшим указом повелевают числу пи стать равным трем. Посылать надо “насяльнике ” вот и все.

Master_Sergius
но насяльнике хочет, чтоб без “breaking change”
Master_Sergius
что вдруг кто-то решал для себя проблему таким образом….
странное у вас понимание “breaking change”. “breaking change” Это когда нарушаются соглашения декларируемые в документации к продукту. Для нормальных продуктов это означает что новая версия продукта перестала проходить тесты.

Ваша задача поменять класс на строку и прогнать тесты. А если кто-то там, например в китае, поковырялся в носу и написал код полагаясь на интроспекцию свою мудрость и еще незнамо что, так это не ваша проблема. Китайцев много, а караван идет.
Rodegast
> ну так там у нее/него там ситуация

А почему у него такая ситуация? Да потому что “наша sdk возвращает несовместимые типы позиций”. В место того что бы исправить ошибку и возвращать данные одного типа они предложили её костылём закрыть, а теперь при помощи другого костыля они этот костыль чинят.
С таким подходом “ситуации” для них уже должны быть нормой.
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB