의존성 주입을위한 파이썬적인 방법은 무엇입니까?


84

소개

Java의 경우 Dependency Injection은 순수 OOP로 작동합니다. 즉, 구현할 인터페이스를 제공하고 프레임 워크 코드에서 정의 된 인터페이스를 구현하는 클래스의 인스턴스를 수락합니다.

이제 Python의 경우 동일한 방식으로 수행 할 수 있지만 Python의 경우에는 그 방법이 너무 많은 오버 헤드라고 생각합니다. 그렇다면 파이썬 방식으로 어떻게 구현할까요?

사용 사례

이것이 프레임 워크 코드라고 가정합니다.

class FrameworkClass():
    def __init__(self, ...):
        ...

    def do_the_job(self, ...):
        # some stuff
        # depending on some external function

기본 접근 방식

가장 순진한 (그리고 아마도 가장 좋은?) 방법은 FrameworkClass생성자에 외부 함수를 제공 한 다음 do_the_job메서드 에서 호출되도록하는 것입니다.

프레임 워크 코드 :

class FrameworkClass():
    def __init__(self, func):
        self.func = func

    def do_the_job(self, ...):
        # some stuff
        self.func(...)

클라이언트 코드 :

def my_func():
    # my implementation

framework_instance = FrameworkClass(my_func)
framework_instance.do_the_job(...)

질문

질문은 짧습니다. 이를 위해 더 일반적으로 사용되는 Pythonic 방법이 있습니까? 아니면 그러한 기능을 지원하는 라이브러리가 있습니까?

업데이트 : 구체적인 상황

토큰을 사용하여 인증을 처리하는 마이크로 웹 프레임 워크를 개발한다고 상상해보십시오. 이 프레임 워크 ID에는 토큰에서 얻은 일부를 제공하고 이에 해당하는 사용자를 가져 오는 함수가 필요 합니다 ID.

분명히 프레임 워크는 사용자 또는 기타 애플리케이션 특정 로직에 대해 전혀 알지 못하므로 클라이언트 코드는 인증이 작동하도록 프레임 워크에 사용자 getter 기능을 삽입해야합니다.


2
"구현할 인터페이스를 제공하고 프레임 워크 코드에서 정의 된 인터페이스를 구현하는 클래스의 인스턴스를 허용" 하지 않는 이유는 무엇 입니까? 파이썬에서는 EAFP 스타일 로이 작업을 수행합니다 (즉, 해당 인터페이스를 충족하고 AttributeErroror TypeError가 그렇지 않으면 발생 한다고 가정 ). 그렇지 않으면 동일합니다.
jonrsharpe

데코레이터 와 함께 absABCMeta메타 클래스 를 사용하면 쉽게 할 수 @abstractmethod있으며 수동 유효성 검사가 없습니다. 몇 가지 옵션과 제안을 받고 싶습니다. 당신이 인용 한 것이 가장 깨끗한 것이지만 나는 더 많은 오버 헤드가 있다고 생각합니다.
bagrat

그럼 어떤 질문을 하려는지 모르겠습니다.
jonrsharpe

좋습니다. 다시 말해 볼게요. 문제는 분명합니다. 문제는 파이썬 방식으로이를 수행하는 방법입니다. 옵션 1 : 당신이 인용 한 방식, 옵션 2 : 질문에서 설명한 기본 접근 방식 . 그래서 질문은, 그것을 수행하는 다른 파이썬적인 방법이 있습니까?
bagrat 2015-07-28

답변:


66

참조 레이몬드 Hettinger - 슈퍼 슈퍼 고려! -파이 콘 2015-DI 대신 수퍼 및 다중 상속을 사용하는 방법에 대한 논의에 대한 . 전체 동영상을 볼 시간이 없다면 15 분으로 이동하세요 (하지만 모두 시청하는 것이 좋습니다).

다음은이 비디오에 설명 된 내용을 예제에 적용하는 방법의 예입니다.

프레임 워크 코드 :

class TokenInterface():
    def getUserFromToken(self, token):
        raise NotImplementedError

class FrameworkClass(TokenInterface):
    def do_the_job(self, ...):
        # some stuff
        self.user = super().getUserFromToken(...)

클라이언트 코드 :

class SQLUserFromToken(TokenInterface):
    def getUserFromToken(self, token):      
        # load the user from the database
        return user

class ClientFrameworkClass(FrameworkClass, SQLUserFromToken):
    pass

framework_instance = ClientFrameworkClass()
framework_instance.do_the_job(...)

이것은 Python MRO가 getUserFromToken 클라이언트 메서드가 호출되도록 보장하기 때문에 작동합니다 (super ()가 사용되는 경우). Python 2.x를 사용하는 경우 코드를 변경해야합니다.

여기에 추가 된 한 가지 이점은 클라이언트가 구현을 제공하지 않으면 예외가 발생한다는 것입니다.

물론 이것은 실제로 의존성 주입이 아니며 다중 상속과 믹스 인이지만 문제를 해결하는 Python 방식입니다.


10
이 답변으로 간주 super():
바그 라트

2
Raymond는 그것을 CI라고 불렀고 나는 그것이 순수한 믹스 인이라고 생각했습니다. 그러나 Python mixin과 CI에서 사실상 동일 할 수 있습니까? 유일한 차이점은 유죄 수준입니다. Mixin은 클래스 수준에 종속성을 주입하고 CI는 인스턴스에 종속성을 주입합니다.
nad2000

1
생성자 수준 주입은 OP가 설명하는 방식과 같이 어쨌든 Python에서 수행하기가 매우 쉽다고 생각합니다. 이 비단뱀 방식은 매우 흥미로워 보입니다. 단순한 생성자 주입 IMO보다 약간 더 많은 배선이 필요합니다.
stucash

6
매우 우아하다고 생각하지만이 접근 방식에는 두 가지 문제가 있습니다. 1. 클래스에 서버 항목을 주입해야 할 때 어떤 일이 발생합니까? 2. 상속은 "is a"/ 전문화 감각으로 가장 자주 사용됩니다. DI에 사용하는 것은 그 아이디어를 무시합니다 (예를 들어 발표자에게 서비스를 주입하려는 경우).
AljoSt

18

프로젝트에서 의존성 주입을하는 방법은 inject lib 입니다. 문서 확인 . DI에 사용하는 것이 좋습니다. 하나의 기능만으로는 의미가 없지만 여러 데이터 소스 등을 관리해야 할 때 많은 의미가 있습니다.

귀하의 예를 따르면 다음과 유사 할 수 있습니다.

# framework.py
class FrameworkClass():
    def __init__(self, func):
        self.func = func

    def do_the_job(self):
        # some stuff
        self.func()

사용자 정의 기능 :

# my_stuff.py
def my_func():
    print('aww yiss')

애플리케이션의 어딘가에 정의 된 모든 종속성을 추적하는 부트 스트랩 파일을 만들려고합니다.

# bootstrap.py
import inject
from .my_stuff import my_func

def configure_injection(binder):
    binder.bind(FrameworkClass, FrameworkClass(my_func))

inject.configure(configure_injection)

그런 다음 다음과 같이 코드를 사용할 수 있습니다.

# some_module.py (has to be loaded with bootstrap.py already loaded somewhere in your app)
import inject
from .framework import FrameworkClass

framework_instance = inject.instance(FrameworkClass)
framework_instance.do_the_job()

파이썬은 인터페이스 나 타입 힌팅과 같은 멋진 것들을 가지고 있지 않기 때문에 이것이 얻을 수있는 것만 큼 비단뱀 적입니다 (모듈에는 매개 변수 등으로 주입 할 데코레이터와 같은 파이썬 단맛이 있습니다-문서 확인).

따라서 귀하의 질문에 직접 답변하는 것은 매우 어려울 것입니다. 진정한 질문은 파이썬이 DI를 기본적으로 지원합니까? 그리고 그 대답은 슬프게도 아니오입니다.


대답 해 주셔서 감사합니다. 꽤 흥미로워 보입니다. 데코레이터 부분을 확인하겠습니다. 그 동안 더 많은 답변을 기다리겠습니다.
bagrat 2015-08-05

'inject'라이브러리에 대한 링크를 주셔서 감사합니다. 이것은 내가 DI로 채우고 싶었던 틈새를 채우기 위해 지금까지 찾은 가장 가까운 것입니다. 그리고 보너스는 실제로 유지되고 있습니다!
Andy Mortimer 19 년

13

얼마 전에 나는 그것을 Pythonic- Dependency Injector 로 만들고자하는 야망을 가지고 의존성 주입 마이크로 프레임 워크를 작성했습니다 . 사용하는 경우 코드가 다음과 같이 보일 수 있습니다.

"""Example of dependency injection in Python."""

import logging
import sqlite3

import boto.s3.connection

import example.main
import example.services

import dependency_injector.containers as containers
import dependency_injector.providers as providers


class Platform(containers.DeclarativeContainer):
    """IoC container of platform service providers."""

    logger = providers.Singleton(logging.Logger, name='example')

    database = providers.Singleton(sqlite3.connect, ':memory:')

    s3 = providers.Singleton(boto.s3.connection.S3Connection,
                             aws_access_key_id='KEY',
                             aws_secret_access_key='SECRET')


class Services(containers.DeclarativeContainer):
    """IoC container of business service providers."""

    users = providers.Factory(example.services.UsersService,
                              logger=Platform.logger,
                              db=Platform.database)

    auth = providers.Factory(example.services.AuthService,
                             logger=Platform.logger,
                             db=Platform.database,
                             token_ttl=3600)

    photos = providers.Factory(example.services.PhotosService,
                               logger=Platform.logger,
                               db=Platform.database,
                               s3=Platform.s3)


class Application(containers.DeclarativeContainer):
    """IoC container of application component providers."""

    main = providers.Callable(example.main.main,
                              users_service=Services.users,
                              auth_service=Services.auth,
                              photos_service=Services.photos)

다음은이 예제에 대한보다 광범위한 설명에 대한 링크입니다 -http : //python-dependency-injector.ets-labs.org/examples/services_miniapp.html

도움이되기를 바랍니다. 자세한 내용은 다음을 방문하십시오.


@Roman Mogylatov 감사합니다. 구성 파일에서와 같이 런타임에 이러한 컨테이너를 구성 / 조정하는 방법을 알고 싶습니다. 이러한 종속성은 주어진 컨테이너 ( PlatformServices)에 하드 코딩 된 것 같습니다 . 주입 가능한 라이브러리 클래스의 모든 조합에 대해 새 컨테이너를 만드는 솔루션이 있습니까?
Bill DeRose

2
안녕하세요 @BillDeRose. 내 대답이 너무 긴 것으로 간주되어 너무 긴 것으로 간주되었지만 github 문제를 만들고 거기에 대답을 게시 했습니다 -github.com/ets-labs/python-dependency-injector/issues/197 :) 도움이 되기를 바랍니다 . 감사합니다, 로마
로마 Mogylatov

2

DI와 아마도 AOP는 일반적인 Python 개발자의 선호도 때문에 일반적으로 Pythonic으로 간주되지 않는다고 생각합니다.

사실 100 줄 미만의 기본 DI 프레임 워크를 구현할 수 있습니다. 메타 클래스와 클래스 데코레이터를 사용하여 .

덜 침습적 인 솔루션의 경우 이러한 구성을 사용하여 사용자 정의 구현을 일반 프레임 워크에 플러그인 할 수 있습니다.


2

Google의 오픈 소스 파이썬 종속성 주입기 ​​인 Pinject도 있습니다.

다음은 예입니다.

>>> class OuterClass(object):
...     def __init__(self, inner_class):
...         self.inner_class = inner_class
...
>>> class InnerClass(object):
...     def __init__(self):
...         self.forty_two = 42
...
>>> obj_graph = pinject.new_object_graph()
>>> outer_class = obj_graph.provide(OuterClass)
>>> print outer_class.inner_class.forty_two
42

그리고 여기에 소스 코드가 있습니다


2

종속성 주입은 Python이 직접 지원하는 간단한 기술입니다. 추가 라이브러리가 필요하지 않습니다. 유형 힌트 사용 하면 명확성과 가독성을 높일 수 있습니다.

프레임 워크 코드 :

class UserStore():
    """
    The base class for accessing a user's information.
    The client must extend this class and implement its methods.
    """
    def get_name(self, token):
        raise NotImplementedError

class WebFramework():
    def __init__(self, user_store: UserStore):
        self.user_store = user_store

    def greet_user(self, token):
        user_name = self.user_store.get_name(token)
        print(f'Good day to you, {user_name}!')

클라이언트 코드 :

class AlwaysMaryUser(UserStore):
    def get_name(self, token):      
        return 'Mary'

class SQLUserStore(UserStore):
    def __init__(self, db_params):
        self.db_params = db_params

    def get_name(self, token):
        # TODO: Implement the database lookup
        raise NotImplementedError

client = WebFramework(AlwaysMaryUser())
client.greet_user('user_token')

UserStore클래스 타입 힌트는 의존성 주입을 구현하는 데 필요하지 않습니다. 주요 목적은 클라이언트 개발자에게 지침을 제공하는 것입니다. UserStore클래스와 이에 대한 모든 참조를 제거해도 코드는 계속 작동합니다.


1

종속성 주입을 수행하는 매우 쉽고 Pythonic 방법은 importlib입니다.

작은 유틸리티 함수를 정의 할 수 있습니다.

def inject_method_from_module(modulename, methodname):
    """
    injects dynamically a method in a module
    """
    mod = importlib.import_module(modulename)
    return getattr(mod, methodname, None)

그런 다음 사용할 수 있습니다.

myfunction = inject_method_from_module("mypackage.mymodule", "myfunction")
myfunction("a")

mypackage / mymodule.py에서 myfunction을 정의합니다.

def myfunction(s):
    print("myfunction in mypackage.mymodule called with parameter:", s)

물론 MyClass iso 클래스를 사용할 수도 있습니다. myfunction 함수. settings.py 파일에서 methodname의 값을 정의하면 설정 파일의 값에 따라 다른 버전의 methodname을로드 할 수 있습니다. Django는 이러한 체계를 사용하여 데이터베이스 연결을 정의합니다.


1

Python OOP 구현으로 인해 IoC 및 종속성 주입은 Python 세계에서 표준 관행이 아닙니다. 그러나 접근 방식은 Python에서도 유망 해 보입니다.

  • 의존성을 인수로 사용하는 것은 비 파이썬 접근 방식입니다. Python은 아름답고 우아한 OOP 모델을 갖춘 OOP 언어로, 종속성을 유지하는보다 직접적인 방법을 제공합니다.
  • 인터페이스 유형을 모방하기 위해 추상 메서드로 가득 찬 클래스를 정의하는 것도 이상합니다.
  • 래퍼에 대한 거대한 해결 방법은 코드 오버 헤드를 발생시킵니다.
  • 또한 작은 패턴 만 필요한 경우 라이브러리를 사용하는 것을 좋아하지 않습니다.

그래서 내 해결책 은 다음과 같습니다.

# Framework internal
def MetaIoC(name, bases, namespace):
    cls = type("IoC{}".format(name), tuple(), namespace)
    return type(name, bases + (cls,), {})


# Entities level                                        
class Entity:
    def _lower_level_meth(self):
        raise NotImplementedError

    @property
    def entity_prop(self):
        return super(Entity, self)._lower_level_meth()


# Adapters level
class ImplementedEntity(Entity, metaclass=MetaIoC):          
    __private = 'private attribute value'                    

    def __init__(self, pub_attr):                            
        self.pub_attr = pub_attr                             

    def _lower_level_meth(self):                             
        print('{}\n{}'.format(self.pub_attr, self.__private))


# Infrastructure level                                       
if __name__ == '__main__':                                   
    ENTITY = ImplementedEntity('public attribute value')     
    ENTITY.entity_prop         

편집하다:

패턴에주의하십시오. 실제 프로젝트에서 사용했는데 그다지 좋은 방법이 아님을 보여주었습니다. 패턴에 대한 내 경험에 대한 Medium 게시물.


물론 IOC와 DI가 일반적으로 사용되며 일반적으로 사용 되지 않는 것은 DI 프레임 워크 입니다.
juanpa.arrivillaga

0

Python에서 DI 프레임 워크 중 일부를 사용해 본 후 .NET Core와 같은 다른 영역에서 얼마나 간단한 지 비교할 때 사용하기가 다소 어색하다는 것을 알았습니다. 이것은 대부분 코드를 어지럽히고 단순히 프로젝트에 추가하거나 제거하기 어렵게 만드는 데코레이터와 같은 것을 통해 결합하거나 변수 이름을 기반으로 결합하기 때문입니다.

저는 최근에 Simple-Injection이라는 주입을 수행하기 위해 대신 타이핑 주석을 사용하는 종속성 주입 프레임 워크를 연구하고 있습니다. 아래는 간단한 예입니다.

from simple_injection import ServiceCollection


class Dependency:
    def hello(self):
        print("Hello from Dependency!")

class Service:
    def __init__(self, dependency: Dependency):
        self._dependency = dependency

    def hello(self):
        self._dependency.hello()

collection = ServiceCollection()
collection.add_transient(Dependency)
collection.add_transient(Service)

collection.resolve(Service).hello()
# Outputs: Hello from Dependency!

이 라이브러리는 서비스 수명을 지원하고 서비스를 구현에 바인딩합니다.

이 라이브러리의 목표 중 하나는 기존 애플리케이션에 쉽게 추가하고 커밋하기 전에 원하는 방식을 확인하는 것입니다. 애플리케이션에 적절한 타이핑이 있어야하기 때문에 다음 위치에서 종속성 그래프를 작성하면됩니다. 진입 점을 찾아 실행하십시오.

도움이 되었기를 바랍니다. 자세한 내용은

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.