Python 애플리케이션 변경 사항을 인식하는 구성 클래스

15196 단어 pythonprogramming

최소한의 접근



내 응용 프로그램에 구성을 위한 중앙 집중식 위치가 필요한 경우 일반적으로 dict() 개체를 포함하는 전용 파일로 시작합니다.

# file: config.py
config = dict()


응용 프로그램 전체에서 사용하는 방법은 정말 간단합니다. 가져오기 및 사용:

# file: app.py
from config import config

config['db'] = 'uat01'
config.update(host='localhost', username='user', 
              password='secret pass')


이러한 config는 멋진 속성을 가지고 있습니다. 구현 트릭이 없는 싱글톤입니다.

그러나 dict 로 요소에 액세스하는 [] 구문을 사용하는 것이 특히 편리하지 않습니다. 빠른 수정은 다음 중 하나입니다.
  • 1. __*attr__ 메서드에 대한 별칭을 만듭니다.

  • # file: config.py
    class Config(dict):
        __getattr__ = dict.get
        __setattr__ = dict.__setitem__
        __delattr__ = dict.__delitem__
    
    config = Config()
    


  • 2. SimpleNamespace에서 types를 사용하지만 update() 동등한 방법이 없습니다.

  • 상황이 바뀔 때



    첫 번째 선택을 고수하고 다음 시나리오를 살펴보겠습니다.

    # file: app.py
    from config import config
    from types import Optional
    
    def fibonacci(
        count: int,
        first: Optional[int] = None,
        second: Optional[int] = None
    ) -> int:
        a, b = first or config.first, second or config.second
        for _ in range(count):
            a, b = b, a + b
        return a
    
    # file: main.py
    from app import fibonacci
    from config import config
    
    if __name__ == '__main__':
        config.update(first=1, second=1)
        print(f'{fibonacci(6)=}')
    
        config.update(first=3, second=5)
        print(f'{fibonacci(6)=}')
    


    결과는 fibonacci 함수에 대한 두 번의 호출 간에 변경되지 않습니다.

    fibonacci(6)=8
    fibonacci(6)=8
    


    변경 사항을 알리십시오.




    # file: config.py
    import importlib
    import inspect
    
    class Config:
        _data = dict()
        _referrers = set()
    
        def __getattr__(self, name):
            caller = inspect.currentframe().f_back
            module_name = caller.f_globals['__name__']
    
            if '__main__' not in module_name:
                self._referrers.add(module_name)
    
            try:
                return self._data[name]
            except KeyError:
                raise AttributeError('no attr %s', name)
    
        def __setattr__(self, name, value):
            self._data[name] = value
    
        def _reload_modules(self):
            for module_name in self._referrers.copy():
                module = importlib.import_module(module_name)
                importlib.reload(module)
    
        def __repr__(self):
            return 'Config(%r)' % self._data
    
        def update(self, reload=True, **kwargs):
            for k, v in kwargs.items()
                self.__setattr__(k, v)
    
            if reload:
                self._reload_modules()
    
    config = Config()
    
    # set defaults
    config.update(first=0, second=1)
    


    이제 main.py를 실행하면 다음 결과가 생성됩니다.

    fibonacci(6)=8
    fibonacci(6)=55
    


    무슨 일이야?



    좋습니다. 새로 정의된Config 클래스는 약간 길고 약간의 설명이 필요합니다.

    코드를 살펴보겠습니다.

    import importlib
    import inspect
    


    이 두 가지 가져오기는 잠시 후에 유용하게 사용할 수 있습니다.

    class Config:
        _data = dict()
        _referrers = set()
    

    Config 클래스는 두 가지 요소를 캡슐화합니다. 1. _data , 실제 키-값 컨테이너 및 2. _referrers , config를 가져오고 해당 항목에 액세스하는 다른 모듈 집합입니다.

        def __getattr__(self, name):
            caller = inspect.currentframe().f_back
            module_name = caller.f_globals['__name__']
    
            if '__main__' not in module_name:
                self._referrers.add(module_name)
    


    사용자 정의__getattr__는 도트 액세스 구문을 활성화합니다. 또한 config를 참조하는 모듈을 녹음하는 순간입니다. config가 업데이트되면 해당 모듈에 변경 사항이 있고 다시 로드할 시간임을 알릴 수 있습니다.

    나머지는 매우 간단합니다. 안에:

        def _reload_modules(self):
            for module_name in self._referrers.copy():
                module = importlib.import_module(module_name)
                importlib.reload(module)
    


    관련 모듈을 반복하고 다시 로드합니다.

    마지막으로 파일 끝에서 코드의 다른 부분에서 사용되는 항목의 기본값을 설정해야 합니다. 다른 옵션은 구성 파일을 로드하고 구문 분석하는 것입니다.

    또한 설계상 update에 대한 호출만 참조된 모듈을 새로 고치도록 결정했습니다. __setattr__로 하나씩 설정하면 이 동작이 발생하지 않습니다.

    구성에 대한 귀하의 접근 방식은 무엇입니까?

    좋은 웹페이지 즐겨찾기