Functools - 파이썬에서 고차 함수의 힘

소스 노드 : 1865357

이 기사는 데이터 과학 Blogathon

개요

Python 표준 라이브러리에는 코드를 더 깔끔하고 단순하게 유지하는 데 도움이 되는 많은 훌륭한 모듈이 있으며 functools는 확실히 중 하나

캐싱

functools 모듈의 가장 간단하면서도 강력한 기능 중 일부부터 시작해 보겠습니다. 캐싱 기능(및 데코레이터)부터 시작해 보겠습니다. lru_cache,cachecached_property. 그 중 첫 번째 – lru_cache 함수 실행의 마지막 결과에 대한 캐시를 제공합니다. 즉, 작업 결과를 기억합니다.

from functools import lru_cache
import requests @lru_cache(maxsize=32)
def get_with_cache(url): try: r = requests.get(url) return r.text except: return "Not Found" for url in ["https://google.com/", "https://reddit.com/", "https://google.com/", "https://google.com/"]: get_with_cache(url) print(get_with_cache.cache_info())
# CacheInfo(hits=2, misses=4, maxsize=32, currsize=4)
print(get_with_cache.cache_parameters())
# {'maxsize': 32, 'typed': False}

이 예에서는 데코레이터를 사용하여 GET 요청을 수행하고 결과(최대 32개 결과)를 캐시합니다. @lru_cache. 캐싱이 실제로 작동하는지 확인하려면 메소드를 사용하여 함수 캐시 정보를 확인할 수 있습니다. cache_info캐시 적중 횟수와 적중 횟수를 표시합니다. 데코레이터는 메서드도 제공합니다. clear_cachecache_parameters캐시된 결과 및 테스트 매개변수를 각각 취소합니다.

보다 세부적인 캐싱이 필요한 경우 선택적 인수 typed=true를 포함할 수 있습니다. 이를 통해 다양한 유형의 인수를 별도로 캐시할 수 있습니다.

functools에서 캐싱을 위한 또 다른 데코레이터는 간단히 말해서 cache. 간단한 포장이에요 lru_cachemax_size 인수를 생략하고 이를 줄이며 이전 값을 제거하지 않습니다.

캐싱에 사용할 수 있는 또 다른 데코레이터는 다음과 같습니다. cached_property. 이름에서 알 수 있듯이 클래스 속성의 결과를 캐시하는 데 사용됩니다. 이 메커니즘은 계산 비용이 많이 들지만 동일하게 유지되는 속성이 있는 경우 매우 유용합니다.

from functools import cached_property class Page: @cached_property def render(self, value): # Do something with supplied value... # Long computation that renders HTML page... return html

이 간단한 예가 나와 있습니다. 예를 들어, 사용자에게 계속해서 표시해야 하는 렌더링된 HTML 페이지를 캐시하기 위해 캐시된 속성을 사용할 수 있습니다. 특정 데이터베이스 쿼리나 긴 수학 계산에도 동일한 작업을 수행할 수 있습니다.

또 다른 아름다움 cached_property조회 시에만 실행되므로 속성 값을 변경할 수 있다는 것입니다. 속성을 변경한 후에는 이전에 캐시된 값이 변경되지 않고 대신 새 값이 계산되어 캐시됩니다. 캐시를 지울 수도 있으며, 속성을 제거하기만 하면 됩니다.

위의 모든 데코레이터에 대한 경고로 이 섹션을 마무리하고 싶습니다. 함수에 부작용이 있거나 호출될 때마다 변경 가능한 객체를 생성하는 경우에는 사용하지 마세요. 이러한 함수는 분명히 캐시하려는 함수가 아니기 때문입니다. .

비교 및 주문

아마도 여러분은 Python에서 다음과 같은 비교 연산자를 구현할 수 있다는 것을 이미 알고 있을 것입니다. <, >=or ==lt, gtor eq. 그러나 각각의 사항을 실현하는 것은 상당히 실망스러울 수 있습니다. eq, lt, le, gtor ge. 다행히 functools에는 데코레이터가 있습니다. @total_ordering우리가 구현해야 할 것은 다음과 같기 때문에 도움이 될 수 있습니다. eq나머지 메서드 중 하나를 사용하고 나머지 데코레이터는 자동으로 생성됩니다.

from functools import total_ordering @total_ordering
class Number: def __init__(self, value): self.value = value def __lt__(self, other): return self.value Number(3))
# True
print(Number(1) = Number(15))
# True
print(Number(10) <= Number(2))
# False

이런 식으로 우리는 eq와 lt만 가지고 있음에도 불구하고 모든 확장된 비교 연산을 구현할 수 있습니다. 가장 확실한 이점은 편리성입니다. 즉, 이러한 추가 매직 메서드를 모두 작성할 필요가 없다는 것입니다. 하지만 코드 양을 줄이고 가독성을 높이는 것이 더 중요할 것입니다.

과부하

우리는 아마도 Python에는 오버로딩이 없다는 것을 배웠을 것입니다. 그러나 실제로 functools의 두 가지 함수, 즉 단일 디스패치 및/또는 단일 디스패치 방법을 사용하여 이를 구현하는 쉬운 방법이 있습니다. 이러한 함수는 Python과 같은 동적으로 유형이 지정된 프로그래밍 언어가 런타임에 유형을 구별할 수 있도록 하는 다중 디스패치 알고리즘을 구현하는 데 도움이 됩니다.

일부의

우리 모두는 다양한 외부 라이브러리나 프레임워크를 사용하여 작업하며, 그 중 다수는 비동기 작업이나 이벤트 수신과 같은 콜백 전달이 필요한 기능과 인터페이스를 제공합니다. 새로운 것은 아니지만 콜백과 함께 몇 가지 인수도 전달해야 한다면 어떻게 될까요? 이것은 편리한 functools가 제공되는 곳입니다..partial. 사용할 수 있습니다 partial단순화된 함수 시그니처를 사용하여 새 개체를 생성하여 함수에 대한 인수 중 일부(또는 전체)를 고정합니다. 혼란스러운? 몇 가지 실제 사례를 살펴보겠습니다.

def output_result(result, log=None): if log is not None: log.debug(f"Result is: {result}") def concat(a, b): return a + b import logging
from multiprocessing import Pool
from functools import partial logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("default") p = Pool()
p.apply_async(concat, ("Hello ", "World"), callback=partial(output_result, log=logger))
p.close()
p.join()

위의 코드는 사용 방법을 보여줍니다. partial함수를 전달하려면( output_result)와 인수( log=logger)를 콜백으로 사용합니다. 이 경우 함수의 결과를 비동기적으로 계산하는 multiprocessing.apply_async를 사용합니다( concat) 콜백 결과를 반환합니다. 하지만, apply_async항상 결과를 첫 번째 인수로 전달하며, log=logger의 경우처럼 추가 인수를 포함하려면 부분을 사용해야 합니다.

우리는 상당히 고급 사용 사례를 고려했으며, 더 간단한 예는 stdout 대신 stderr로 작성하는 함수의 일반적인 생성입니다.

import sys
from functools import partial print_stderr = partial(print, file=sys.stderr)
print_stderr("This goes to standard error output")

이 간단한 트릭을 사용하여 항상 통과하는 새로운 호출 가능한 함수를 만들었습니다. file=sys.stderr출력할 명명된 인수로 사용하면 코드를 단순화할 수 있고 매번 명명된 인수의 값을 지정할 필요가 없습니다.

그리고 마지막 좋은 예입니다. 우리는 사용할 수 있습니다 partial잘 알려지지 않은 기능과 함께 iter호출 가능한 객체를 전달하여 반복자를 생성하고 sentinelin iter, 다음과 같이 적용할 수 있습니다.

from functools import partial RECORD_SIZE = 64 # Read binary file...
with open("file.data", "rb") as file: records = iter(partial(file.read, RECORD_SIZE), b'') for r in records: # Do something with the record...

일반적으로 파일을 읽을 때 줄을 반복하고 싶지만 이진 데이터의 경우 고정된 크기의 레코드를 반복해야 할 수도 있습니다. 다음을 사용하여 호출 가능한 객체를 생성하면 됩니다. partial지정된 데이터 청크를 읽고 전달합니다. iter반복자를 생성합니다. 그런 다음 이 반복자는 파일 끝에 도달할 때까지 읽기 함수를 호출하며 항상 지정된 청크 크기( RECORD_SIZE). 마지막으로 파일의 끝에 도달하면 값이 sentinel(비”) 반환되고 반복이 중지됩니다.

장식

우리는 이전 섹션에서 일부 데코레이터에 대해 이미 이야기했지만 더 많은 데코레이터를 생성하기 위한 데코레이터는 다루지 않았습니다. 그러한 데코레이터 중 하나가 functools입니다..wraps. 왜 필요한지 이해하기 위해 예를 살펴보겠습니다.

def decorator(func): def actual_func(*args, **kwargs): """Inner function within decorator, which does the actual work""" print(f"Before Calling {func.__name__}") func(*args, **kwargs) print(f"After Calling {func.__name__}") return actual_func @decorator
def greet(name): """Says hello to somebody""" print(f"Hello, {name}!") greet("Martin")
# Before Calling greet
# Hello, Martin!
# After Calling greet

이 예에서는 간단한 데코레이터를 구현하는 방법을 보여줍니다. 특정 작업을 수행하는 함수를 래핑합니다( actual_func) 외부 데코레이터를 사용하면 데코레이터 자체가 되며, 예를 들어 greet. 함수를 호출하면, greet 두 가지 모두에서 메시지가 인쇄되는 것을 볼 수 있습니다. actual_func그리고 그 자체로. 괜찮아 보이는데요, 그렇죠? 하지만 이렇게 하면 어떻게 될까요?

print(greet.__name__)
# actual_func
print(greet.__doc__)
# Inner function within decorator, which does the actual work

데코레이팅된 함수의 이름과 문서를 호출하면 데코레이터 함수의 값으로 대체되었음을 알 수 있습니다. 일부 데코레이터를 사용할 때 모든 함수 이름과 문서를 다시 작성할 수 없기 때문에 이는 좋지 않습니다. 이 문제는 어떻게 해결될 수 있나요? 물론 functools를 사용하면.wraps:

from functools import wraps def decorator(func): @wraps(func) def actual_func(*args, **kwargs): """Inner function within decorator, which does the actual work""" print(f"Before Calling {func.__name__}") func(*args, **kwargs) print(f"After Calling {func.__name__}") return actual_func @decorator
def greet(name): """Says hello to somebody""" print(f"Hello, {name}!") print(greet.__name__)
# greet
print(greet.__doc__)
# Says hello to somebody

함수의 유일한 목적 wraps덮어쓰기를 방지하기 위해 이름, 문서, 인수 목록 등을 복사하는 것입니다. 고려해 보면 wraps이는 데코레이터이기도 하므로, real_func에 간단히 추가하면 문제가 해결됩니다!

줄입니다

마지막으로 functools 모듈에서 이 감소가 있습니다. 아마도 다른 언어에서는 다음과 같이 알 수도 있습니다. fold(하스켈). 이 함수는 반복 가능한 항목을 가져와 모든 값을 하나로 접습니다(추가). 이를 위한 많은 응용 프로그램이 있습니다. 예를 들면 다음과 같습니다.

from functools import reduce
import operator def product(iterable): return reduce(operator.mul, iterable, 1) def factorial(n): return reduce(operator.mul, range(1, n)) def sum(numbers): # Use `sum` function from standard library instead return reduce(operator.add, numbers, 1) def reverse(iterable): return reduce(lambda x, y: y+x, iterable) print(product([1, 2, 3]))
# 6
print(factorial(5))
# 24
print(sum([2, 6, 8, 3]))
# 20
print(reverse("hello"))
# olleh

코드에서 볼 수 있듯이, reduce 코드를 한 줄로 단순화하거나 압축할 수 있습니다. 그렇지 않으면 훨씬 길어질 것입니다. 즉, 코드를 축소하여 "더 똑똑하게" 만들기 위해 이 함수를 남용하는 것은 일반적으로 나쁜 생각입니다. 왜냐하면 코드가 빨리 무섭고 읽을 수 없게 되기 때문입니다. 이러한 이유로 제 생각에는 이 제품을 아껴서 사용해야 한다고 생각합니다.

그리고 그걸 기억한다면 reduce 종종 모든 것을 한 줄로 줄여서 다음과 완벽하게 결합할 수 있습니다. partial:

product = partial(reduce, operator.mul) print(product([1, 2, 3]))
# 6

마지막으로, 최종 "접힌" 결과 이상이 필요한 경우 다음을 사용할 수 있습니다. accumulate– 또 다른 훌륭한 모듈에서 itertools. 최대값을 계산하려면 다음과 같이 사용할 수 있습니다.

from itertools import accumulate data = [3, 4, 1, 3, 5, 6, 9, 0, 1] print(list(accumulate(data, max)))
# [3, 4, 4, 4, 5, 6, 9, 9, 9]

결론

functools에서 볼 수 있듯이, 여러분의 삶을 더 쉽게 만들어 줄 수 있는 유용한 기능과 데코레이터가 많이 있지만 이는 빙산의 일각에 불과합니다. 처음에 말했듯이 Python 표준 라이브러리에는 더 나은 코드를 작성하는 데 도움이 되는 많은 함수가 있으므로 여기서 다룬 함수 외에도 다음과 같은 다른 모듈에 주의할 수 있습니다. operatoror itertool. 문의사항이 있으면 댓글 상자를 누르시면 됩니다. 귀하의 의견을 해결하기 위해 최선을 다하고 원하는 결과를 제공할 수 있기를 바랍니다. LinkedIn에서도 저에게 연락하실 수 있습니다:-

https://www.linkedin.com/in/shivani-sharma-aba6141b6/

이 기사에 표시된 미디어는 Analytics Vidhya의 소유가 아니며 작성자의 재량에 따라 사용됩니다.

출처: https://www.analyticsvidhya.com/blog/2021/08/functools-the-power-of-higher-order-functions-in-python/

타임 스탬프 :

더보기 분석 Vidhya