OTCloud
@OTCloud
Программирование и Архитектура ПО

Поймал артефакт с аргументами объекта в фабрике. Как исправить?

python 3.9

Господа питонисты, поймал странный(?) кейс. Объясните пожалуйста причину явления, описаного в кейсе ниже. Спасибо за ранее.

Кейс:

Есть базовый класс сервис (BaseService), реализующий два метода (add(self, collection: BaseCollection) и add_one(self, entity: BaseEntity)):
class BaseEntity:
  pass

class BaseCollection:
  items = []

  def __init__(self, items: list = []):
    self.items = items

class CollectionsFactory:
  @staticmethod
  def create(name: str, items: list = []) -> BaseCollection:
    if name == Enums.CONTACT:
      return CollectionsFactory.create_contacts_collection(items)
    # ...

  @staticmethod
  def create_contacts_collection(items: list = []) -> ContactsCollection:
    return ContactsCollection(items)

class BaseService:
  def add(self, collection: BaseCollection):
    # ... какие то действия (запросы в бд/апи/и тд)
    # создание новой коллекции (для моего метода
    # add новая коллекция нужна, но не стал добавлять 
    # реализации, потому что иначе придется углубляться в 
    # иерархию других классов, это лишнее, в кейсе важен лишь 
    # факт создания еще одной коллекции внутри метода add)
    response_collection = CollectionsFactory.create(self.get_entity_name())
    # далее какая то обработка и возврат результата в response_collection

  def add_one(self, entity: BaseEntity):
    items = [entity]
    # self.get_entity_name(self, name: str) -> BaseCollection: 
    # определяется в конкретном классе, то есть он абстрактный
    collection = CollectionsFactory.create(self.get_entity_name(), items)
    return self.add(collection)


Код выше это каркас для определения конкретных реализаций, например, Users-(Entity, Collection,Service), Posts-(Entity, Collection,Service) и так далее. Далее код для конкретной реализации:

class ContactEntity(BaseEntity):
  pass

class ContactsCollection(BaseCollection):
  pass

class ContactsService(BaseService):
  def get_entity_name() -> str:
    return Enums.Contact


Теперь о самом странном артефакте, нас интересует метод BaseService.add(), в нем есть создание response_collection и аргумент collection. Почему то collection.items попадают в response_collection.items при использовании фабрики. Вот и весь артефакт, не понимаю почему так происходит... То есть если внутри метода вывести список элементов обоих коллекций (collection и response_collection):
...
def add(self, collection: BaseCollection):
    # ... какие то действия (запросы в бд/апи/и тд)
    response_collection = CollectionsFactory.create(self.get_entity_name())
    print(collection.items)
    print(response_collection.items)
    # то результат будет таким
    # два одинаковых объекта в разных инстансах коллекции ContactsCollection ... 
    # Как так?
    # [<src.models.contacts.Contact object at 0x7f09ac384a00>]
    # [<src.models.contacts.Contact object at 0x7f09ac384a00>]
...
  • Вопрос задан
  • 34 просмотра
Решения вопроса 1
@galaxy
Не очень хочется разбираться в ваших кусках кода. Скорее всего, происходит то же, что и в этом простейшем примере:
class A:
	def __init__(self, a = []):
		self.a = a

class B(A):
	pass

o1 = A()
o2 = B()

o1.a.append(17)
print(o1.a)
# [17]
print(o2.a)
# [17]


А причина проста: значение по умолчанию - Mutable объект, он создается один раз при объявлении метода. Так что все ссылки self.a для всех экземпляров, созданных без передачи параметра в конструктор, ведут на один и тот же объект список.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы