Python의 커링과 그것이 짜증나는 이유
13815 단어 pythonfunctional
커링이란?
Curring은 개발자가 함수에 인수를 부분적으로 전달할 수 있도록 하는 기능적 기술(또는 기능)입니다. 예를 살펴보겠습니다.
예시
일부 인코딩을 사용하여
decode
를 str
로 변환하는 함수bytes
가 있다고 상상해 보십시오.Yep, I know that there is built-in
str.decode
method, but for simplicity and some non-imaginaryadd
example let's look at this one
def decode(encoding: str, s: str) -> bytes:
return s.decode(encoding)
이 함수는 항상
decode("UTF-8", some_str)
처럼 실행됩니다. 대부분 하나의 인코딩(특히 "UTF-8")을 사용하므로 이"UTF-8"
인수는 어디에나 있을 것입니다. 어느 날 그것을 리팩토링하고 다른 함수를 작성하기로 결정할 때마다 작성하는 데 지쳤습니다.def decode_utf_8(s: str) -> bytes:
return decode("UTF-8", s)
실제로는 지름길일 뿐이지만 무의식적으로 커링을 사용했습니다(그러나 실제 함수형 언어가 사용하는 방식은 아님)! Curring은 왼쪽 인수를 전달할 때 대기하는 다른 함수를 얻기 위해 일부 인수를 함수에 전달하는 것입니다.
따라서 함수에 3개의 인수가 있고 2개만 전달하면 예외를 발생시키는 대신 실제로 하나의 왼쪽 인수만 허용하는 함수가 생성됩니다.
파이썬이 커링을 지원한다면?
Python이 기본적으로 이 기능을 지원한다면 다음과 같이 작성할 수 있습니다.
# this does not work!!!
decode_utf_8 = decode("UTF-8") # type: Callable[[str], bytes]
나에게는 멋져 보이지만 솔직히 말해서 이전 버전과의 호환성 문제로 인해 이것이 실제로 Python3.X(언젠가는 Python4에서 가능할 수도 있지만 누가 알겠습니까?)에서 현실이 될 가능성은 없습니다.
그러나 현재 Python에서 함수를 커링하는 몇 가지 합법적인 방법이 있습니다.
옵션 1: functools.partial
functools.partial
를 사용하면 다음과 같이 decode_utf_8
를 작성할 수 있습니다.from functools import partial
decode_utf_8 = partial(decode, "UTF-8")
decode_utf_8("hello") # b"hello"
옵션 2: toolz.curry(더 나은 것)
또 다른 옵션은 훌륭한 패키지에서 제공됩니다
toolz
.from toolz import curry
@curry
def decode(encoding: str, s: str) -> bytes:
return s.decode(encoding)
decode_utf_8 = decode("UTF-8")
이 옵션은 커링이 하나의 데코레이터로 기본적으로 지원되는 것처럼 보이기 때문에 훨씬 더 좋아 보입니다.
근데 왜 짜증나?
Python은 동적으로 입력되는 언어이며
partial
또는 @curry
를 사용한 후에는 실제로 코드 편집기에 대한 유형 힌트를 얻을 수 없습니다. 유형 힌트를 작성하고 mypy
와 같은 정적 유형 검사기를 사용하여 코드를 깨끗하게 유지하고 유형을 입력하려는 모든 시도는 헛된 것입니다. 예를 들어 VS 코드는 실제 decode_utf_8
대신 partial
유형이 curry
또는 Callable[[str], bytes]
임을 알려줍니다.이것은 실제로 함수를 실행하기 위해 전달해야 하는 인수를 스스로 기억해야 하기 때문에 사용하기 어렵습니다. 또한 선택적 인수로 수행할 작업이 항상 명확한 것은 아닙니다. 임의의 인수가 있는 함수도 당연히 커링할 수 없습니다.
해킹이 있습니까?
글쎄, 나는 하나를 찾고 있었고 다음과 같은 간단한 해결 방법을 만들었습니다.
from typing import (
Callable,
Concatenate,
ParamSpec,
TypeVar,
)
P = ParamSpec("P")
A1 = TypeVar("A1")
AResult = TypeVar("AResult")
def hof1(func: Callable[Concatenate[A1, P], AResult]):
"""Separate first argument from other."""
def _wrapper(arg_1: A1) -> Callable[P, AResult]:
def _func(*args: P.args, **kwargs: P.kwargs) -> AResult:
return func(arg_1, *args, **kwargs)
return _func
return _wrapper
# also hof2 and hof3 decorator for separating first 2 and 3
# arguments correspondingly
@hof1
def decode(encoding: str, s: str) -> bytes:
return s.decode(encoding)
decode_utf_8 = decode("UTF-8")
# but you can't do this:
decode("UTF-8", "hello") # raises error
이 데코레이터를 사용하면 먼저 전달할 인수와 실제로 함수를 실행하기 위해 전달할 인수를 미리 알아야 합니다. 이것은 심각한 제한이지만 쉬운 구문보다 더 중요하다고 생각하는 유형 힌트를 저장하는 데 도움이 됩니다.
더 광범위한 예:
from toolz import pipe
@hof1
def encode(encoding: str, b: bytes) -> str:
return b.encode(encoding)
@hof1
def split(sep: str, s: str) -> Iterable[str]:
return s.split(sep)
@hof1
def cmap(
mapper: Callable[[X], Y], iterable: Iterable[X]
) -> Iterable[Y]:
return map(mapper, iterable)
query = pipe(
b"name=John&age=32",
encode("UTF-8"),
split("&"),
cmap(split("=")),
dict,
)
print(query) # {"name": "John", "age": "32"}
# same without hof1
query = pipe(
b"name=John&age=32",
lambda b: b.encode("UTF-8"),
lambda s: s.split("&"),
lambda ss: map(lambda s: s.split("="), ss),
dict,
)
커링 및 그러한 해결 방법에 대해 어떻게 생각하십니까? 의견에 생각과 질문을 공유하십시오!
Reference
이 문제에 관하여(Python의 커링과 그것이 짜증나는 이유), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/katunilya/curring-in-python-an-why-it-sucks-1p80텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)