파이썬 장식기의 전생
선사
먼저 간단한 예를 보면 실제적으로 매우 복잡할 수 있다.
def today():
print('2018-05-25')
함수의 실행 로그를 기록할 수 있는 새로운 요구 사항이 생겨 코드에 로그 코드를 추가합니다.
def today():
print('2018-05-25')
logging.info('today is running...')
만약 함수 yesterday (),tomorrow () 도 비슷한 수요가 있다면 어떻게 합니까?Yesterday 함수에 logging을 하나 더 쓸까요?이렇게 하면 대량의 동일한 코드를 만들 수 있다. 중복 코드를 줄이기 위해 우리는 이렇게 새로운 함수를 정의할 수 있다. 로그를 전문적으로 처리하고 로그를 처리한 후에 진정한 업무 코드를 집행한다.
def logging_tool(func):
logging.info('%s is running...' % func.__name__)
func()
def today():
print('2018-05-25')
logging_tool(today)
이렇게 하면 논리적으로 문제가 없다. 기능은 실현되었다. 그러나 우리가 호출할 때 진정한 업무 논리 today 함수를 호출하지 않고logging 로 바뀌었다.tool 함수, 이것은 원래의 코드 구조를 파괴했다. 로그 기능을 지원하기 위해 원래의 코드를 대폭 수정해야 한다. 그러면 더 좋은 방법이 없을까?당연히 있지, 답은 장식기야.
2. 천지개벽
간단한 장식기
def logging_tool(func):
def wrapper(*arg, **kwargs):
logging.info('%s is running...' % func.__name__)
func() # today , func() today()
return wrapper
def today():
print('2018-05-25')
today = logging_tool(today) # logging_tool(today) wrapper, today=wrapper
today() # today() wrapper()
지금까지도 장식기의 원리였습니다!!!
3. 파이토닉 세계의 초심자
# 문법 사탕이 파이톤에 접촉한 지 한동안 되면 @ 기호에 낯설지 않을 것이다. 그래 @ 기호는 장식기의 문법 사탕이다. 함수가 정의되기 시작한 곳에 두면 마지막 단계에서 다시 값을 부여하는 조작을 생략할 수 있다.
def logging_tool(func):
def wrapper(*arg, **kwargs):
logging.info('%s is running...' % func.__name__)
func() # today , func() today()
return wrapper
@logging_tool
def today():
print('2018-05-25')
today()
@이 있으면 우리는 today = logging 을 생략할 수 있다tool(today) 이 구절은 today()를 직접 호출하면 원하는 결과를 얻을 수 있습니다.today () 함수를 수정할 필요가 없습니다. 정의된 곳에 장식기를 추가하면 호출할 때 예전과 같습니다.만약 우리가 다른 유사한 함수가 있다면, 함수를 반복해서 수정하거나 새로운 봉인을 추가하지 않고, 장식기를 계속 호출해서 수식할 수 있다.이렇게 하면 프로그램의 중복 이용성을 향상시키고 프로그램의 가독성을 증가시킨다.
장식기가 파이톤에서 사용하는 것이 이렇게 편리한 이유는 파이톤 함수가 일반적인 대상처럼 매개 변수로 다른 함수에 전달되고 다른 변수에 값을 부여받을 수 있으며 되돌아오는 값으로 다른 함수에 정의될 수 있기 때문이다.
장식기는 본질적으로 Python 함수나 클래스로 다른 함수나 클래스가 코드 수정이 필요 없는 전제에서 추가 기능을 추가할 수 있고 장식기의 반환값도 함수/클래스 대상이다.이것은 자주 절면 수요가 있는 장면, 예를 들어 로그 삽입, 성능 테스트, 사무 처리, 캐시, 권한 검사 등 장면에 사용되는데 장식기는 이런 문제를 해결하는 절호의 디자인이다.장식기가 있으면 우리는 함수 기능 자체와 무관한 대량의 코드를 추출하여 장식기에 다시 사용할 수 있다.간단하게 말하자면 장식기의 역할은 이미 존재하는 대상에게 추가 기능을 추가하는 것이다.
4. 다원화 백가쟁명
1. 파라미터가 있는 장식기
장식기의 문법은 @decorator (condition) 와 같은 다른 인자를 호출할 때 제공할 수 있도록 합니다.장식기의 작성과 사용에 더욱 큰 유연성을 제공하였다.예를 들어, 우리는 장식기에서 로그의 등급을 지정할 수 있다. 왜냐하면 서로 다른 업무 함수는 서로 다른 로그 등급을 필요로 할 수 있기 때문이다.
def logging_tool(level):
def decorator(func):
def wrapper(*arg, **kwargs):
if level == 'error':
logging.error('%s is running...' % func.__name__)
elif level == 'warn':
logging.warn('%s is running...' % func.__name__)
else:
logging.info('%s is running...' % func.__name__)
func()
return wrapper
return decorator
@logging_tool(level='warn')
def today(name='devin'):
print('Hello, %s! Today is 208-05-25' % name)
today()
2. 장식기가 파라미터가 있거나 없는 것을 동시에 지원하도록 한다
def new_logging_tool(obj):
if isinstanc(obj, str): # , str
def decorator(func):
@functools.wraps(func)
def wrapper(*arg, **kwargs):
if obj == 'error':
logging.error('%s is running...' % func.__name__)
elif obj == 'warn':
logging.warn('%s is running...' % func.__name__)
else:
logging.info('%s is running...' % func.__name__)
func()
return wrapper
return decorator
else: # , ,
@functools.wraps(obj)
def wrapper(*args, **kwargs):
logging.info('%s is running...' % obj.__name__)
obj()
return wrapper
@new_logging_tool
def yesterday():
print('2018-05-24')
yesterday()
@new_logging_tool('warn')
def today(name='devin'):
print('Hello, %s! Today is 208-05-25' % name)
today()
위에서 보듯이 매개 변수는 두 가지 유형이 있는데 하나는 문자열이고 다른 하나는 호출 가능한 함수 유형이다.따라서 파라미터 유형에 대한 판단을 통해 파라미터가 있는 것과 없는 두 가지 상황을 지원할 수 있다.
3. 클래스 장식기
장식기는 함수일 뿐만 아니라 클래스일 수도 있다. 함수 장식기에 비해 클래스 장식기는 유연성이 크고 내부 집합이 높으며 봉인성 등 장점이 있다.클래스 장식기를 사용하는 것은 주로 클래스의call 방법에 의존합니다. @ 형식으로 장식기를 함수에 추가할 때 이 방법을 사용합니다.
(1) 예1, 장식된 함수에 파라미터가 없음
class Foo(object):
def __init__(self, func):
self._func = func #
def __call__(self):
print ('class decorator runing')
self._func() #
print ('class decorator ending')
@Foo
def bar(): #
print ('bar')
bar()
(2) 예2. 장식된 함수대 파라미터
class Counter:
def __init__(self, func):
self.func = func
self.count = 0 #
def __call__(self, *args, **kwargs):
self.count += 1
return self.func(*args, **kwargs)
@Counter
def today(name='devin'):
print('Hello, %s! Today is 208-05-25' % name) #
for i in range(10):
today()
print(today.count) # 10
(3) 예시 3. 초기화 함수에 의존하지 않고call 함수를 단독으로 사용하여 실현(클래스 장식기의 유연성이 크고 내부 집합이 높으며 봉인성이 높은 특징을 나타낸다) 일부 중요한 함수가 실행될 때 로그를 한 파일에 인쇄하고 사용자에게 알림을 보내는 것을 실현한다.
class LogTool(object):
def __init__(self, logfile='out.log'):
self.logfile = logfile #
def __call__(self, func): # __call__
@functools.wraps(func)
def wrapper(*args, **kwargs):
log_string = func.__name__ + " was called"
print(log_string) #
with open(self.logfile, 'a') as fw:
fw.write(log_string + '
') #
self.notify() #
return func(*args, **kwargs)
return wrapper
#
def notify(self):
pass
@LogTool() # __call__ , ,
def bill_func():
pass
LogTool에 하위 클래스를 만들어 이메일 기능을 추가하는 추가 확장:
class EmailTool(LogTool):
"""
LogTool , email , email
"""
def __init__(self, email='[email protected]', *args, **kwargs):
self.email = email
super(EmailTool, self).__init__(*args, **kwargs)
# , email self.email
def notify(self):
pass
@EmailTool()
def bill_func():
pass
4, 장식 함수 -> 장식 클래스
(1) 함수 차원의 장식기는 흔히 볼 수 있는데 하나의 함수를 입력으로 하고 새로운 함수를 되돌려준다.(2) 클래스 차원의 장식도 사실 유사하다. 이미 하나의 클래스를 입력으로 하고 새로운 클래스로 되돌아간다.
예를 들어 기존 클래스에 길이 속성과 Getter,setter 방법을 추가합니다
def Length(cls):
class NewClass(cls):
@property
def length(self):
if hasattr(self, '__len__'):
self._length = len(self)
return self._length
@length.setter
def length(self, value):
self._length = value
return NewClass
@Length
class Tool(object):
pass
t = Tool()
t.length = 10
print(t.length) # 10
5. 상고신기
1、@property -> getter/setter 방법
예: Student에 score 속성을 추가하는 Getter, setter 방법
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer')
if value < 0 or value > 100:
raise ValueError('score must between 0~100!')
self._score = value
s = Student()
s.core = 60
print('s.score = ', s.score)
s.score = 999 # ValueError: score must between 0~100!
2、@classmethod、@staticmethod
(1) @classmethod 클래스 방법: 예비 구조기를 정의합니다. 첫 번째 파라미터는 클래스 자체입니다. (파라미터 이름은 제한되지 않으며 일반적으로cls를 사용합니다) (2) @staticmethod 정적 방법: 클래스와 밀접한 관계를 가진 함수
간단한 원리 예:
class A(object):
@classmethod
def method(cls):
pass
... 과 같다
class A(object):
def method(cls):
pass
method = classmethod(method)
3、@functools.wraps
장식기는 코드를 크게 복용했지만 단점이 하나 있다. 끼워 넣은 함수 대상인 wrapper가 원함수가 아니기 때문에 원함수의 원 정보를 잃어버린다. 예를 들어 함수의docstring,name, 파라미터 목록 등 정보를 잃어버린다.하지만 방법은 항상 어려움보다 많습니다. @functools를 통과할 수 있습니다.랩스는 원 함수의 원 정보를 장식기 안의func 함수에 복사하여 장식기 안의func와 원 함수에 같은 원 정보를 가지게 한다.
def timethis(func):
"""
Decorator that reports the execution time.
"""
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(func.__name__, time.time() - start)
return result
return wrapper
@timethis
def countdown(n: int):
"""Counts down"""
while n > 0:
n -= 1
countdown(10000000) # 1.3556335
print(countdown.__name__, ' doc: ', countdown.__doc__, ' annotations: ', countdown.__annotations__)
@functools.wraps는 속성 wrapped를 통해 장식된 함수에 직접 접근할 수 있고 장식된 함수로 하여금 밑바닥의 매개 변수 서명 정보를 정확하게 노출시킬 수 있다
countdown.__wrapped__(1000) #
print(inspect.signature(countdown)) #
4、Easter egg
(1) 매개 변수를 받아들이는 포장기를 정의한다
@decorator(x, y, z)
def func(a, b):
pass
... 과 같다
func = decorator(x, y, z)(func)
즉: decorator (x, y,z) 의 반환 결과는 호출 가능한 대상이어야 하며, 함수를 매개 변수로 받아들여 포장해야 한다.
(2) 하나의 함수는 여러 개의 장식기를 동시에 정의할 수 있다. 예를 들어 다음과 같다.
@a
@b
@c
def f():
pass
... 과 같다
f = a(b(c(f)))
즉, 그것의 집행 순서는 안에서 밖으로 가장 안쪽을 가장 먼저 호출하고, 마지막으로 가장 바깥쪽의 장식기를 호출하는 것이다.
마지막
파이썬 장식기에 대해 위에서 열거한 예시를 제외하고는 신기한 용법이 많다. 또한 장식기는 파이썬 장식기의 빙산의 일각일 뿐이다. 여기는 벽돌을 던져 옥을 끌어올리는 데만 있고 더 많은 해커 용법, 소년, 마음껏 즐겁게 탐색하자...(더 많은 하이라이트, 주목: DevinBlog)
참고: 파이썬 장식기 이해하기 이번 편만 봐도 파이썬 진급
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.