WSGI 도구 라이브러리인 Werkzeug는 몇 가지 고려 사항으로 인해 Python에 내장된 ThreadLocal 클래스를 직접 사용하지 않고 일련의 Local 클래스를 자체적으로 구현합니다. 이를 기반으로 구현된 LocalStack, LocalManager 및 LocalProxy는 물론 간단한 Local도 포함됩니다. 다음으로 이러한 클래스가 어떻게 사용되는지, 해당 클래스의 원래 의도와 구체적인 구현 기술을 살펴보겠습니다.
Local 클래스 설계
Werkzeug의 디자이너는 Python과 함께 제공되는 ThreadLocal이 요구 사항을 충족할 수 없다고 생각하는데, 그 이유는 주로 다음 두 가지 이유 때문입니다.
Werkzeug는 주로 다음을 사용합니다. "ThreadLocal" 동시성 요구 사항을 충족하기 위해 Python 자체 ThreadLocal은 스레드 기반 동시성만 구현할 수 있습니다. Python에는 일반 코루틴(greenlet)과 같은 다른 동시성 메서드가 많이 있으므로 코루틴을 지원할 수 있는 Local 객체를 구현해야 합니다.
WSGI는 요청을 처리하기 위해 매번 새 스레드가 생성된다는 것을 보장하지 않습니다. 즉, 스레드를 재사용할 수 있습니다(요청을 처리하기 위해 스레드 풀을 유지 관리할 수 있음). 이러한 방식으로 werkzeug가 Python 자체 ThreadLocal을 사용하는 경우 "unclean(이전에 처리된 요청과 관련된 데이터 저장)" 스레드가 새 요청을 처리하는 데 사용됩니다.
이 두 가지 문제를 해결하기 위해 werkzeug에서는 Local 클래스를 구현했습니다. 로컬 객체는 스레드와 코루틴 간에 데이터를 격리할 수 있습니다. 또한 특정 스레드나 코루틴에서 데이터 정리를 지원합니다(요청을 처리한 후 해당 데이터를 정리한 후 다음 요청 도착을 기다릴 수 있음).
구체적으로 구현하는 방법은 무엇입니까? 아이디어는 실제로 매우 간단합니다. "Python의 ThreadLocal 변수에 대한 심층적 이해(1부)" 기사 마지막 부분에서 전역 변수를 생성하는 방법에 대해 언급했습니다. 식별자가 키 역할을 하고, 해당 스레드(또는 코루틴)의 로컬 데이터가 값 역할을 합니다. 여기서 werkzeug는 위의 아이디어에 따라 구현되었지만 Python의 일부 흑마술을 사용하여 최종적으로 사용자에게 명확하고 간단한 인터페이스를 제공합니다.
구체적인 구현
Local 클래스의 구현은 werkzeug.local에 있으며, 분석에는 8a84b62 버전의 코드를 사용했습니다. 처음 두 기사에서 ThreadLocal에 대한 이해를 통해 우리는 이미 Local 객체의 특성과 사용법을 알고 있었습니다. 따라서 여기서는 더 이상 Local 개체를 사용하는 예제를 제공하지 않고 코드를 직접 살펴보겠습니다.
class Local(object): __slots__ = ('__storage__', '__ident_func__') def __init__(self): object.__setattr__(self, '__storage__', {}) object.__setattr__(self, '__ident_func__', get_ident) ...
Local 객체가 많을 수 있으므로 Local 객체가 차지하는 공간을 절약하기 위해 여기서는 __slots__를 사용하여 Local이 가질 수 있는 속성을 하드 코딩합니다.
__storage__: 값은 실제 데이터를 저장하는 데 사용되는 사전이며 비어 있도록 초기화됩니다.
__ident_func__: 값은 현재 스레드 또는 코루틴의 식별자를 찾는 데 사용되는 함수입니다.
Local 객체의 실제 데이터는 __storage__에 저장되므로 Local 속성에 대한 작업은 실제로 __storage__에 대한 작업입니다. 속성을 얻기 위해 여기서 매직 메소드 __getattr__을 사용하여 __storage__ 및 __ident_func__ 이외의 속성 획득을 가로채고 이를 __storage__에 저장된 현재 스레드 또는 코루틴의 데이터로 전달합니다. 속성 값의 set 또는 del은 각각 __setattr__ 및 __setattr__을 사용하여 구현됩니다(이러한 매직 메서드에 대한 소개는 속성 제어 참조). 키 코드는 다음과 같습니다.
def __getattr__(self, name): try: return self.__storage__[self.__ident_func__()][name] except KeyError: raise AttributeError(name) def __setattr__(self, name, value): ident = self.__ident_func__() storage = self.__storage__ try: storage[ident][name] = value except KeyError: storage[ident] = {name: value} def __delattr__(self, name): try: del self.__storage__[self.__ident_func__()][name] except KeyError: raise AttributeError(name)
ID가 1, 2, ..., N인 N개의 스레드 또는 코루틴이 있다고 가정합니다. 각각은 Local 개체를 사용하여 자체 로컬 데이터 중 일부를 저장합니다. Local 객체의 내용은 아래와 같습니다:
또한 Local 클래스는 현재 스레드나 코루틴에 의해 저장된 데이터를 해제하기 위한 __release_local__ 메서드도 제공합니다.
로컬 확장 인터페이스
Werkzeug는 Local을 기반으로 LocalStack과 LocalManager를 구현하여 보다 친숙한 인터페이스 지원을 제공합니다.
LocalStack
LocalStack은 Local을 캡슐화하여 스레드(또는 코루틴) 독립적인 스택 구조를 구현합니다. 주석에 구체적인 사용 방법이 있습니다
ls = LocalStack() ls.push(12) print ls.top # 12 print ls._local.__storage__ # {140735190843392: {'stack': [12]}}
LocalStack의 구현은 매우 흥미롭습니다. Local 객체를 자체 속성인 _local로 사용한 다음 인터페이스 push, pop 및 top 메소드를 정의하여 해당 스택 작업을 수행합니다. _local.__storage__._local.__ident_func__() 목록은 여기서 스택 구조를 시뮬레이션하는 데 사용됩니다. 인터페이스 push, pop 및 top에서는 이 목록을 조작하여 스택의 작동을 시뮬레이션합니다. 인터페이스 함수 내에서 이 목록을 얻을 때 위의 굵은 글씨처럼 복잡할 필요는 없습니다. _local의 getattr() 메서드를 직접 사용하는 것이 바로 Can입니다. push 함수를 예로 들면 구현은 다음과 같습니다.
def push(self, obj): """Pushes a new item to the stack""" rv = getattr(self._local, 'stack', None) if rv is None: self._local.stack = rv = [] rv.append(obj) return rv
pop과 top의 구현은 일반 스택과 비슷하며 둘 다 list stack = getattr(self._local에 해당합니다. , '스택', 없음)이 작동합니다. 또한 LocalStack을 사용하면 __ident_func__를 사용자 정의할 수 있습니다. 여기서는 내장 함수 속성을 사용하여 설명자를 생성하고 __ident_func__의 가져오기 및 설정 작업을 캡슐화하고 속성 값 __ident_func__을 인터페이스로 제공합니다. :
def _get__ident_func__(self): return self._local.__ident_func__ def _set__ident_func__(self, value): object.__setattr__(self._local, '__ident_func__', value) __ident_func__ = property(_get__ident_func__, _set__ident_func__) del _get__ident_func__, _set__ident_func__
LocalManager
Local과 LocalStack은 스레드나 코루틴에 독립적인 단일 개체입니다. 여러 Local 또는 LocalStack 개체를 구성하려면 스레드나 코루틴 독립 컨테이너가 필요한 경우가 많습니다. 우리는 목록을 사용하여 여러 int 또는 문자열 유형을 구성합니다.
Werkzeug实现了LocalManager,它通过一个list类型的属性locals来存储所管理的Local或者LocalStack对象,还提供cleanup方法来释放所有的Local对象。Werkzeug中LocalManager最主要的接口就是装饰器方法make_middleware,代码如下:
def make_middleware(self, app): """Wrap a WSGI application so that cleaning up happens after request end. """ def application(environ, start_response): return ClosingIterator(app(environ, start_response), self.cleanup) return application
这个装饰器注册了回调函数cleanup,当一个线程(或者协程)处理完请求之后,就会调用cleanup清理它所管理的Local或者LocalStack 对象(ClosingIterator 的实现在 werkzeug.wsgi中)。下面是一个使用 LocalManager 的简单例子:
from werkzeug.local import Local, LocalManager local = Local() local_2 = Local() local_manager = LocalManager([local, local2]) def application(environ, start_response): local.request = request = Request(environ) ... # application 处理完毕后,会自动清理local_manager 的内容
通过LocalManager的make_middleware我们可以在某个线程(协程)处理完一个请求后,清空所有的Local或者LocalStack对象,这样这个线程又可以处理另一个请求了。至此,文章开始时提到的第二个问题就可以解决了。Werkzeug.local 里面还实现了一个 LocalProxy 用来作为Local对象的代理,也非常值得去学习。