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

소개

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 기능을 삽입해야합니다.



답변

참조 레이몬드 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 방식입니다.


답변

프로젝트에서 의존성 주입을하는 방법은 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를 기본적으로 지원합니까? 그리고 그 대답은 슬프게도 아니오입니다.


답변

얼마 전에 나는 그것을 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

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


답변

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

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

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


답변

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

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


답변

종속성 주입은 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클래스와 이에 대한 모든 참조를 제거해도 코드는 계속 작동합니다.


답변

종속성 주입을 수행하는 매우 쉽고 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는 이러한 체계를 사용하여 데이터베이스 연결을 정의합니다.