Уведомления

Группа в Telegram: @pythonsu

#1 Авг. 31, 2015 14:30:29

4kpt_III
Зарегистрирован: 2014-12-22
Сообщения: 999
Репутация: +  39  -
Профиль   Отправить e-mail  

Dependency injection in python

FishHook

Напиши Андрею Светлову. Они точно где-то используют. У меня таких задач нету

P.S. Или кинь мне свою почту в личку, я дам тебе ссылку на чатик, где он часто появляется.

Отредактировано 4kpt_III (Авг. 31, 2015 14:30:43)

Офлайн

#2 Авг. 31, 2015 14:32:52

FishHook
От:
Зарегистрирован: 2011-01-08
Сообщения: 8312
Репутация: +  568  -
Профиль   Отправить e-mail  

Dependency injection in python

Да вопрос то чисто ради срача



Офлайн

#3 Авг. 31, 2015 14:36:14

4kpt_III
Зарегистрирован: 2014-12-22
Сообщения: 999
Репутация: +  39  -
Профиль   Отправить e-mail  

Dependency injection in python

FishHook

Жаль не смогу помочь. Поучаствовать в сраче я всегда “за”, ты же знаешь

P.S. Я, к слову, догадываюсь зачем оно надо. Если будет время - кинь скайп в личку. Заодно и лясы поточим.

Отредактировано 4kpt_III (Авг. 31, 2015 14:45:30)

Офлайн

#4 Авг. 31, 2015 14:57:29

FishHook
От:
Зарегистрирован: 2011-01-08
Сообщения: 8312
Репутация: +  568  -
Профиль   Отправить e-mail  

Dependency injection in python

отправил



Офлайн

#5 Сен. 10, 2015 10:34:26

rmk
Зарегистрирован: 2015-09-10
Сообщения: 8
Репутация: +  1  -
Профиль   Отправить e-mail  

Dependency injection in python

Привет всем!

Попробую немного разложить по полочкам данную тему, по крайней мере исходя из своей точки зрения.

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 очень приятно рефакторить.

Подводя итог,

“Инверсия управления” - “не бери то, что тебе нужно здесь и сейчас, бери это из третьей точки (или выставляй требования и жди, что тебе его сами дадут - внедрение зависимостей)”


Офлайн

#6 Сен. 10, 2015 10:37:55

rmk
Зарегистрирован: 2015-09-10
Сообщения: 8
Репутация: +  1  -
Профиль   Отправить e-mail  

Dependency injection in python

Кстати, вот моя либа для Dependency Injection - http://python-dependency-injector.ets-labs.org/

Делалась для себя, юзается мной и моими товарищами по оружию на своих и коммерческих проектов. Обычно с фласком (но это на самом деле не так уж важно).

Возможно, кому-то пригодиться.

Отредактировано rmk (Янв. 26, 2017 15:06:23)

Офлайн

#7 Сен. 10, 2015 10:47:58

FishHook
От:
Зарегистрирован: 2011-01-08
Сообщения: 8312
Репутация: +  568  -
Профиль   Отправить e-mail  

Dependency injection in python

rmk
Всё это хорошо, но нет примера. Покажите минимальный пример, наглядно демонстрирующий, как конкретно работает DI в питоне.
Понимаете, нам деревенским программистам теория в большей степени до лампочки, нам практика важнее.
Вы говорите, родитель не должен знать о потомках. Замечательно, my_class.__subclasses__() даст мне всех потомков, которых я запросто отфильтрую по какому-либо атрибуту. То есть питон сам по себе реализует это абстрагирование без лишних костылей.
Итак, давайте пример.



Офлайн

#8 Сен. 10, 2015 11:05:01

rmk
Зарегистрирован: 2015-09-10
Сообщения: 8
Репутация: +  1  -
Профиль   Отправить e-mail  

Dependency injection in python

Родители 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

Подробнее тут: http://python-dependency-injector.ets-labs.org/en/stable/providers/overriding.html

Если убрать все лишнее, то суть этого примера такая:

extended_users_service = providers.Factory(ExtendedUserService,
user_cls=ExtendedUser)
users_service.override(extended_users_service)


Updated: Как-то жестко все поехало… Убрал картинку…

Отредактировано rmk (Ноя. 23, 2016 12:58:02)

Офлайн

#9 Сен. 10, 2015 14:46:03

PooH
От:
Зарегистрирован: 2006-12-05
Сообщения: 1948
Репутация: +  72  -
Профиль   Отправить e-mail  

Dependency injection in python

FishHook
Итак, давайте пример.
Я попробую. В ZCA, ну и в последнем из крупных проектов использующих его - pyramid, вся конфигурация сделана, в принципе, через него. Все регистрируется в реестре, а компоненты запрашивают нужные зависимости из него. Вот, например, кусок кода пирамиды, в котором для запроса ищется фабрика, которая создаст ответ
    def response(self):
        registry = self.registry
        response_factory = registry.queryUtility(IResponseFactory,  default=Response)
        return response_factory()
Всегда можно зарегать свою фабрику вместо дефолтной. И так почти в любом месте.
В теле такая приятная гибкость образовалась.(С)Падал прошлогодний снег



Вот здесь один из первых отарков съел лаборанта. Это был такой умный отарк, что понимал даже теорию относительности. Он разговаривал с лаборантом, а потом бросился на него и загрыз…

Офлайн

#10 Сен. 10, 2015 23:27:47

4kpt_III
Зарегистрирован: 2014-12-22
Сообщения: 999
Репутация: +  39  -
Профиль   Отправить e-mail  

Dependency injection in python

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

Офлайн

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version