Форум сайта python.su
FishHook
Напиши Андрею Светлову. Они точно где-то используют. У меня таких задач нету
P.S. Или кинь мне свою почту в личку, я дам тебе ссылку на чатик, где он часто появляется.
Отредактировано 4kpt_III (Авг. 31, 2015 14:30:43)
Офлайн
Да вопрос то чисто ради срача
Офлайн
FishHook
Жаль не смогу помочь. Поучаствовать в сраче я всегда “за”, ты же знаешь
P.S. Я, к слову, догадываюсь зачем оно надо. Если будет время - кинь скайп в личку. Заодно и лясы поточим.
Отредактировано 4kpt_III (Авг. 31, 2015 14:45:30)
Офлайн
отправил
Офлайн
Привет всем!
Попробую немного разложить по полочкам данную тему, по крайней мере исходя из своей точки зрения.
1) “Зачем в питоне DI?”. Думаю, что это не совсем правильный вопрос. DI - один из способов реализации такой штуки как IoC (Inversion of Control, инверсия управления). Так что давайте начнем с инверсии управления. Инверсия управления - это принцип, который говорит нам, что “Модули верхнего уровня не должны зависеть от модулей нижнего уровня. И те, и другие должны зависеть от абстракции.” и “Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.” (https://ru.wikipedia.org/wiki/%D0%98%D0%BD%D0%B2%D0%B5%D1%80%D1%81%D0%B8%D1%8F_%D1%83%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F). Для того чтобы работать по принципам инверсии управления можно пользоваться следующими шаблонами: factory, service locator, dependency injection. Стоит отдельно остановиться на разнице между сервис локатором и внедрением зависимостей. Если в двух словах, то сервис локатор - это единая точка доступа к любому объекту, которая завязывается во всей программе, в то время как, при использовании внедрения зависимостей конкретные компоненты не “знают” откуда приходят их зависимости, они просто “вбрасываются” “кем-то” (dependency injector'ом) - все связи остаются на самом верхнем уровне архитектуры приложения. Собственно, главная цель инверсии управления - уменьшить связанность конкретных компонентов, дать разработчику контроль на структурой объектов приложения и их зависимостями друг от друга. Отвечая на конкретный вопрос “Зачем в питоне DI?” я бы сказал, что DI существует вне языков программирования, и применим в питоне как и в любом другом языке. Немного другой вопрос, как и где его применять. Я бы сказал, что DI максимально эффективен на больших проектах, на маленьких проектах - тоже прикольно, но вроде все и так понятно - проект то маленький.
2) “Зачем в питоне отдельная либа для реализации DI?”. Питон - очень крутой язык И, я согласен, что сделать DI на своем проекте достаточно просто. Другое дело - стандартизация. 5 разных грамотных разработчиков напишут разные реализации и будут страдать, если придется разбираться в реализации коллеги. Они напишут хорошо, все принципы будут соблюдены, но по разному. Поэтому отвечая на вопрос “нужна ли отдельная либа для DI?” мой ответ - скорее да, чем нет. Это введет некоторую стандартизацию и хоть и не принципиально, но упростит жизнь разработчику.
3) “Как и где применять DI?”. Во-первых, лучше всего это работает на средних / крупных проектах. Альтернативная формулировка - “на проектах, которыми сложно управлять”. Во-вторых, на проектах, которые начинают писаться. DI не очень хорошо внедряется в крупные, зрелые проекты - для этого лучше подходит service locator. В-третьих, DI всегда добавляет в проект кода - хоть с либой, хоть без, стоит понимать, что этот код добавляется, чтобы облегчить жизнь в будущем, когда нужно будет разбираться со связями, а не сейчас, когда нужно по-быстрее закрыть задачу.
4) “Преимущества”. Я бы выделил такие - “контроль над приложением (лучшее понимание связей)”, “улучшенная тестируемость”, “улучшенная гибкость” и “потенциальная возможность переиспользования уже написанного кода”. Приложения написанные с использованием DI очень приятно рефакторить.
Подводя итог,
“Инверсия управления” - “не бери то, что тебе нужно здесь и сейчас, бери это из третьей точки (или выставляй требования и жди, что тебе его сами дадут - внедрение зависимостей)”
Офлайн
Кстати, вот моя либа для Dependency Injection - http://python-dependency-injector.ets-labs.org/
Делалась для себя, юзается мной и моими товарищами по оружию на своих и коммерческих проектов. Обычно с фласком (но это на самом деле не так уж важно).
Возможно, кому-то пригодиться.
Отредактировано rmk (Янв. 26, 2017 15:06:23)
Офлайн
rmk
Всё это хорошо, но нет примера. Покажите минимальный пример, наглядно демонстрирующий, как конкретно работает DI в питоне.
Понимаете, нам деревенским программистам теория в большей степени до лампочки, нам практика важнее.
Вы говорите, родитель не должен знать о потомках. Замечательно, my_class.__subclasses__() даст мне всех потомков, которых я запросто отфильтрую по какому-либо атрибуту. То есть питон сам по себе реализует это абстрагирование без лишних костылей.
Итак, давайте пример.
Офлайн
Родители User and UserService не знают о потомках ExtendedUser and ExtendedUserService:
"""Overriding user's model example.""" from dependency_injector import providers class User(object): """Example class User.""" def __init__(self, id, password): """Initializer.""" self.id = id self.password = password super(User, self).__init__() class UserService(object): """Example class UserService.""" def __init__(self, user_cls): """Initializer.""" self.user_cls = user_cls super(UserService, self).__init__() def get_by_id(self, id): """Find user by his id and return user model.""" return self.user_cls(id=id, password='secret' + str(id)) # Users factory and UserService provider: users_service = providers.Factory(UserService, user_cls=User) # Getting several users and making some asserts: user1 = users_service().get_by_id(1) user2 = users_service().get_by_id(2) assert isinstance(user1, User) assert user1.id == 1 assert user1.password == 'secret1' assert isinstance(user2, User) assert user2.id == 2 assert user2.password == 'secret2' assert user1 is not user2 # Extending user model and user service for adding custom attributes without # making any changes to client's code. class ExtendedUser(User): """Example class ExtendedUser.""" def __init__(self, id, password, first_name=None, last_name=None, gender=None): """Initializer.""" self.first_name = first_name self.last_name = last_name self.gender = gender super(ExtendedUser, self).__init__(id, password) class ExtendedUserService(UserService): """Example class ExtendedUserService.""" def get_by_id(self, id): """Find user by his id and return user model.""" user = super(ExtendedUserService, self).get_by_id(id) user.first_name = 'John' + str(id) user.last_name = 'Smith' + str(id) user.gender = 'male' return user # Overriding users_service provider: extended_users_service = providers.Factory(ExtendedUserService, user_cls=ExtendedUser) users_service.override(extended_users_service) # Getting few other users users and making some asserts: user3 = users_service().get_by_id(3) user4 = users_service().get_by_id(4) assert isinstance(user3, ExtendedUser) assert user3.id == 3 assert user3.password == 'secret3' assert user3.first_name == 'John3' assert user3.last_name == 'Smith3' assert isinstance(user4, ExtendedUser) assert user4.id == 4 assert user4.password == 'secret4' assert user4.first_name == 'John4' assert user4.last_name == 'Smith4' assert user3 is not user4
extended_users_service = providers.Factory(ExtendedUserService,
user_cls=ExtendedUser)
users_service.override(extended_users_service)
Отредактировано rmk (Ноя. 23, 2016 12:58:02)
Офлайн
FishHookЯ попробую. В ZCA, ну и в последнем из крупных проектов использующих его - pyramid, вся конфигурация сделана, в принципе, через него. Все регистрируется в реестре, а компоненты запрашивают нужные зависимости из него. Вот, например, кусок кода пирамиды, в котором для запроса ищется фабрика, которая создаст ответ
Итак, давайте пример.
def response(self): registry = self.registry response_factory = registry.queryUtility(IResponseFactory, default=Response) return response_factory()
Офлайн
rmk
Спасибо за разъяснения и пример, но все же вопрос, для полного понимания. Чем такой вариант хуже?
class User(object): """Example class User.""" def __init__(self, id, password): """Initializer.""" self.id = id self.password = password super(User, self).__init__() class UserService(object): """Example class UserService.""" user_cls = None def __init__(self): """Initializer.""" super(UserService, self).__init__() def get_by_id(self, id): """Find user by his id and return user model.""" return self.user_cls(id=id, password='secret' + str(id)) # Users factory and UserService provider: users_service = UserService users_service.user_cls = User # Getting several users and making some asserts: user1 = users_service().get_by_id(1) user2 = users_service().get_by_id(2) assert isinstance(user1, User) assert user1.id == 1 assert user1.password == 'secret1' assert isinstance(user2, User) assert user2.id == 2 assert user2.password == 'secret2' assert user1 is not user2 class ExtendedUser(User): """Example class ExtendedUser.""" def __init__(self, id, password, first_name=None, last_name=None, gender=None): """Initializer.""" self.first_name = first_name self.last_name = last_name self.gender = gender super(ExtendedUser, self).__init__(id, password) class ExtendedUserService(UserService): """Example class ExtendedUserService.""" def get_by_id(self, id): """Find user by his id and return user model.""" user = super(ExtendedUserService, self).get_by_id(id) user.first_name = 'John' + str(id) user.last_name = 'Smith' + str(id) user.gender = 'male' return user # Overriding users_service provider: users_service = ExtendedUserService users_service.user_cls = ExtendedUser # Getting few other users users and making some asserts: user3 = users_service().get_by_id(3) user4 = users_service().get_by_id(4) assert isinstance(user3, ExtendedUser) assert user3.id == 3 assert user3.password == 'secret3' assert user3.first_name == 'John3' assert user3.last_name == 'Smith3' assert isinstance(user4, ExtendedUser) assert user4.id == 4 assert user4.password == 'secret4' assert user4.first_name == 'John4' assert user4.last_name == 'Smith4' assert user3 is not user4
Офлайн