파이썬 __call__ 특수 메소드 실제 예제


159

__call__클래스의 인스턴스가 호출되면 클래스의 메서드가 트리거 된다는 것을 알고 있습니다. 그러나이 특별한 방법을 언제 사용할 수 있는지 전혀 알지 못합니다. 왜냐하면 단순히 새로운 방법을 만들고 동일한 작업을 수행 할 수 있기 때문입니다.__call__ 할 수 있으며 인스턴스를 호출하는 대신 메소드를 호출 할 수 있기 때문입니다.

누군가 가이 특별한 방법을 실제로 사용한다면 나에게 정말로 감사하겠습니다.



8
_call_ 의 기능은 C ++에서 오버로드 된 () 연산자 와 같습니다 . 클래스 외부에서 새 메소드를 작성하는 경우 클래스의 내부 데이터에 액세스 할 수 없습니다.
andy

2
가장 일반적으로 사용되는 __call__것은 일반보기로 숨겨져 있습니다. 그것은 당신이 클래스를 인스턴스화하는 방법입니다 : x = Foo()is x = type(Foo).__call__(Foo), __call__은 메타 클래스에 의해 정의됩니다 Foo.
chepner

답변:


88

Django 양식 모듈은 __call__메소드를 잘 사용 하여 양식 유효성 검사를위한 일관된 API를 구현합니다. Django에서 양식에 대한 자체 유효성 검사기를 함수로 작성할 수 있습니다.

def custom_validator(value):
    #your validation logic

Django에는 이메일 유효성 검사기, URL 유효성 검사기 등과 같은 기본 내장 유효성 검사기가 있으며 RegEx 유효성 검사기의 범위에 속합니다. 이를 깨끗하게 구현하기 위해 Django는 함수 대신 호출 가능한 클래스를 사용합니다. RegexValidator에서 기본 정규식 유효성 검사 논리를 구현 한 다음 다른 유효성 검사를 위해 이러한 클래스를 확장합니다.

class RegexValidator(object):
    def __call__(self, value):
        # validation logic

class URLValidator(RegexValidator):
    def __call__(self, value):
        super(URLValidator, self).__call__(value)
        #additional logic

class EmailValidator(RegexValidator):
    # some logic

이제 사용자 정의 함수와 내장 EmailValidator를 동일한 구문으로 호출 할 수 있습니다.

for v in [custom_validator, EmailValidator()]:
    v(value) # <-----

보시다시피, 장고 에서의이 구현은 다른 사람들이 아래 답변에서 설명한 것과 유사합니다. 다른 방법으로 구현할 수 있습니까? 당신은 할 수 있지만 IMHO는 장고와 같은 큰 프레임 워크에서 읽거나 쉽게 확장 할 수 없습니다.


5
따라서 올바르게 사용하면 코드를 더 읽기 쉽게 만들 수 있습니다. 잘못된 장소에서 사용하면 코드를 읽을 수 없게됩니다.
mohi666

15
이것은 그것이 어떻게 사용될 수 있는지에 대한 예이지만 내 의견으로는 좋은 것은 아닙니다. 이 경우 호출 가능한 인스턴스를 갖는 것에는 이점이 없습니다. .validate ()와 같은 메소드를 가진 인터페이스 / 추상 클래스를 갖는 것이 좋습니다. 그것은 더 명백한 것과 똑같습니다. __call__의 실제 값은 호출 가능이 예상되는 장소에서 인스턴스를 사용할 수 있습니다. 예를 들어 데코레이터를 만들 때 __call__을 가장 자주 사용합니다.
다니엘

120

이 예제는 기본적으로 값을 테이블에 저장하는 memoization을 사용하므로 (이 경우 사전) 나중에 다시 계산하는 대신 값을 찾을 수 있습니다.

여기서는 정적 변수를 포함하는 계승 함수 (Python에서는 불가능) 대신 __call__계승을 계산 하는 메소드 와 함께 간단한 클래스를 사용합니다 ( 호출 가능한 객체를 통해 ).

class Factorial:
    def __init__(self):
        self.cache = {}
    def __call__(self, n):
        if n not in self.cache:
            if n == 0:
                self.cache[n] = 1
            else:
                self.cache[n] = n * self.__call__(n-1)
        return self.cache[n]

fact = Factorial()

이제 fact다른 모든 함수와 마찬가지로 호출 가능한 객체가 있습니다. 예를 들어

for i in xrange(10):                                                             
    print("{}! = {}".format(i, fact(i)))

# output
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880

그리고 그것은 또한 상태입니다.


2
함수가 본질적으로 인덱스 fact이기 때문에 인덱싱 가능한 객체를 원합니다 __call__. 또한 dict 대신 목록을 사용하지만 그건 나뿐입니다.
Chris Lutz

4
@delnan-거의 모든 것이 몇 가지 뚜렷한 방법으로 수행 될 수 있습니다. 더 읽기 쉬운 것은 독자에 달려 있습니다.
Chris Lutz

1
@Chris Lutz : 그런 종류의 변화를 자유롭게 생각할 수 있습니다. 일반적으로 메모 의 경우 목록을 채우는 순서를 보장 할 수 없으므로 사전이 잘 작동합니다. 이 경우 목록이 작동 할 수 있지만 더 빠르거나 단순하지는 않습니다.
S.Lott

8
@delnan : 이것은 가장 짧은 것이 아닙니다. 코드 골프에서이기는 사람은 없습니다. 그것은 보여주고 __call__, 단순하고 더 이상 아무것도 아닙니다.
S.Lott

3
그러나 입증 된 기술이 작업에 이상적이지 않을 때 예를 망칠 수 있습니까? (그리고 나는 "이것에 대한 선을 절약하자"-짧은 것이 아니라, "이 똑같이 분명한 방식으로 작성하고 상용구 코드를 저장한다"-짧은 것에 대해 이야기하고 있었다. 가능한 한 가장 짧은 코드를 작성하려고하는 미친 사람 중 한 사람인 나는 독자를 위해 아무것도 추가하지 않는 상용구 코드를 피하고 싶을 뿐이다.)

40

사용하기 쉽고 (특정 인수가 필요한 호출 가능한 객체가 있음) API를 만들 수 있기 때문에 유용하며 객체 지향 사례를 사용할 수 있기 때문에 구현하기 쉽습니다.

다음은 어제 내가 작성한 코드로 hashlib.foo문자열 대신 전체 파일을 해시 하는 메소드 버전을 만듭니다 .

# filehash.py
import hashlib


class Hasher(object):
    """
    A wrapper around the hashlib hash algorithms that allows an entire file to
    be hashed in a chunked manner.
    """
    def __init__(self, algorithm):
        self.algorithm = algorithm

    def __call__(self, file):
        hash = self.algorithm()
        with open(file, 'rb') as f:
            for chunk in iter(lambda: f.read(4096), ''):
                hash.update(chunk)
        return hash.hexdigest()


md5    = Hasher(hashlib.md5)
sha1   = Hasher(hashlib.sha1)
sha224 = Hasher(hashlib.sha224)
sha256 = Hasher(hashlib.sha256)
sha384 = Hasher(hashlib.sha384)
sha512 = Hasher(hashlib.sha512)

이 구현을 통해 함수와 비슷한 방식으로 함수를 사용할 수 hashlib.foo있습니다.

from filehash import sha1
print sha1('somefile.txt')

물론 다른 방법으로 구현할 수도 있었지만이 경우에는 간단한 접근 방식처럼 보였습니다.


7
다시, 클로저는이 예제를 망칩니다. pastebin.com/961vU0ay 는 라인의 80 %이며 명확합니다.

8
나는 그것이 누군가 (예를 들어, Java 만 사용했던 사람)에게 항상 분명 하다고 확신하지는 않습니다 . 중첩 함수와 변수 조회 / 범위가 혼동 될 수 있습니다. 내 요점은 __call__OO 기술을 사용하여 문제를 해결할 수있는 도구 를 제공 한다고 생각 합니다.
bradley.ayers

4
둘 다 동등한 기능을 제공 할 때 "Y를 통해 X를 사용하는 이유"라는 질문은 매우 주관적이라고 생각합니다. 어떤 사람들에게는 OO 접근법이 이해하기 쉽고 다른 사람들에게는 폐쇄 접근법이 있습니다. 당신이 상황을 가지고하지 않는 한, 다른 한 사용하는 더 강력한 인수가 없습니다 했다 사용 isinstance또는 유사한 무언가.
bradley.ayers

2
@delnan 클로저 예제는 코드가 적지 만 주장하기가 더 어렵다는 것이 분명합니다.
Dennis

8
__call__클로저 대신 메소드를 사용하는 위치의 예 는 피클 링을 사용하여 프로세스간에 정보를 전달하는 멀티 프로세싱 모듈을 처리 할 때입니다. 클로저를 피클 링 할 수는 없지만 클래스의 인스턴스를 피클 링 할 수 있습니다.
존 피터 톰슨 Garcés

21

__call__파이썬에서 데코레이터 클래스를 구현하는데도 사용됩니다. 이 경우 데코레이터가있는 메소드가 호출 될 때 클래스의 인스턴스가 호출됩니다.

class EnterExitParam(object):

    def __init__(self, p1):
        self.p1 = p1

    def __call__(self, f):
        def new_f():
            print("Entering", f.__name__)
            print("p1=", self.p1)
            f()
            print("Leaving", f.__name__)
        return new_f


@EnterExitParam("foo bar")
def hello():
    print("Hello")


if __name__ == "__main__":
    hello()

9

예, 객체를 다루고 있다는 것을 알면 명시 적 메서드 호출을 사용하는 것이 가능합니다. 그러나 때로는 호출 가능한 객체를 기대하는 코드 (일반적으로 기능)를 처리하지만 __call__인스턴스 데이터 및 더 호출 가능한 반복 작업 등을 위임하는 더 많은 메소드를 사용하여 더 복잡한 객체를 작성할 수 있습니다.

또한 때로는 복잡한 작업 (전용 클래스를 작성하는 것이 합리적)에 대한 객체와 간단한 작업 (기능에 이미 존재하거나 더 쉽게 기능으로 작성되는)에 대한 객체를 모두 사용하고 있습니다. 공통 인터페이스를 가지려면 해당 인터페이스를 예상 인터페이스로 래핑하는 작은 클래스를 작성하거나 함수를 유지하고 더 복잡한 객체를 호출 가능하게 만들어야합니다. 실을 예로 들어 봅시다. Thread표준 라이브러리 모듈threading객체 는 호출 가능한 target인수 를 인수 로 원합니다 (예 : 새 스레드에서 수행 할 작업). 호출 가능한 객체를 사용하면 함수로 제한되지 않고 다른 스레드에서 작업을 가져 와서 순차적으로 실행하는 비교적 복잡한 작업자와 같은 다른 객체도 전달할 수 있습니다.

class Worker(object):
    def __init__(self, *args, **kwargs):
        self.queue = queue.Queue()
        self.args = args
        self.kwargs = kwargs

    def add_task(self, task):
        self.queue.put(task)

    def __call__(self):
        while True:
            next_action = self.queue.get()
            success = next_action(*self.args, **self.kwargs)
            if not success:
               self.add_task(next_action)

이것은 내 머리 꼭대기의 예일 뿐이지 만 클래스를 보증하기에 충분히 복잡하다고 생각합니다. 함수로만이 작업을 수행하는 것은 어렵습니다. 적어도 두 개의 함수를 반환해야하며 천천히 복잡해집니다. 하나는 이름을 __call__다른 것으로하고, 바인딩 된 메서드를 통과,하지만 약간 덜 분명 스레드를 생성하는 코드를 만들고, 값을 추가하지 않습니다.


3
"duck typing"( en.wikipedia.org/wiki/Duck_typing#In_Python ) 문구를 사용하는 것이 유용 할 것입니다.이 방법으로 더 복잡한 클래스 객체를 사용하여 함수를 모방 할 수 있습니다 .
Andrew Jaffe

2
관련 예제로, __call__함수 대신 클래스 인스턴스를 WSGI 응용 프로그램으로 사용하는 것을 보았습니다 . 다음은 "Pylons에 대한 명확한 안내서"의 예 입니다. 클래스 인스턴스 사용
Josh Rosen

5

클래스 기반 데코레이터 __call__는 래핑 된 함수를 참조하는 데 사용 됩니다. 예 :

class Deco(object):
    def __init__(self,f):
        self.f = f
    def __call__(self, *args, **kwargs):
        print args
        print kwargs
        self.f(*args, **kwargs)

Artima.com 의 다양한 옵션에 대한 좋은 설명이 있습니다.


나는 클래스 데코레이터를 거의 보지 못합니다. 메서드와 함께 작동하려면 명백하지 않은 상용구 코드가 필요하기 때문입니다.

4

IMHO __call__메소드와 클로저는 파이썬에서 STRATEGY 디자인 패턴을 만드는 자연스러운 방법을 제공합니다. 우리는 알고리즘 군을 정의하고, 각각을 캡슐화하고, 상호 교환 가능하게 만들고 결국 공통 단계 세트를 실행하고 파일의 해시를 계산할 수 있습니다.


4

나는 내가 아름답다고 생각하는 __call__()콘서트 의 사용법을 우연히 발견했다 __getattr__(). 객체 내에서 여러 레벨의 JSON / HTTP / (however_serialized) API를 숨길 수 있습니다.

__getattr__()부분 반복적 한번에 하나 개 이상의 속성에 채우고, 동일한 클래스의 인스턴스를 리턴 변형을 담당한다. 그런 다음 모든 정보가 소진 된 후 __call__()전달한 인수를 그대로 사용합니다.

이 모델을 사용하면 예를 들어와 같은 api.v2.volumes.ssd.update(size=20)PUT 요청으로 전화를 걸 수 있습니다 https://some.tld/api/v2/volumes/ssd/update.

특정 코드는 OpenStack의 특정 볼륨 백엔드에 대한 블록 스토리지 드라이버입니다. https://github.com/openstack/cinder/blob/master/cinder/volume/drivers/nexenta/jsonrpc.py

편집 : 마스터 개정을 가리 키도록 링크가 업데이트되었습니다.


좋습니다. 한 번은 속성 액세스를 사용하여 임의의 XML 트리를 통과하기 위해 동일한 메커니즘을 사용했습니다.
Petri

1

메소드를 지정 __metaclass__하고 __call__메소드를 대체하고 지정된 메타 클래스의 __new__메소드가 클래스의 인스턴스를 리턴하도록하십시오. 메소드와 함께 "함수"가 있습니다.


1

__call__메소드를 사용하여 다른 클래스 메소드를 정적 메소드로 사용할 수 있습니다 .

    class _Callable:
        def __init__(self, anycallable):
            self.__call__ = anycallable

    class Model:

        def get_instance(conn, table_name):

            """ do something"""

        get_instance = _Callable(get_instance)

    provs_fac = Model.get_instance(connection, "users")             

0

일반적인 예는 __call__in입니다 functools.partial. 여기에 간단한 버전이 있습니다 (Python> = 3.5).

class partial:
    """New function with partial application of the given arguments and keywords."""

    def __new__(cls, func, *args, **kwargs):
        if not callable(func):
            raise TypeError("the first argument must be callable")
        self = super().__new__(cls)

        self.func = func
        self.args = args
        self.kwargs = kwargs
        return self

    def __call__(self, *args, **kwargs):
        return self.func(*self.args, *args, **self.kwargs, **kwargs)

용법:

def add(x, y):
    return x + y

inc = partial(add, y=1)
print(inc(41))  # 42

0

함수 호출 연산자

class Foo:
    def __call__(self, a, b, c):
        # do something

x = Foo()
x(1, 2, 3)

__call__의 방법 재정의 / 동일한 객체를 다시 초기화 할 수있다. 또한 객체에 인수를 전달하여 클래스의 인스턴스 / 객체를 함수로 쉽게 사용할 수 있습니다.


언제 유용할까요? 푸 (1, 2, 3)가 더 명확 해 보입니다.
Yaroslav Nikitenko

0

내가 호출 객체를 사용하기에 좋은 장소를 찾을 정의하는 사람들은 __call__()같은 파이썬에서 함수형 프로그래밍 기능을 사용하여 때, map(), filter(), reduce().

일반 함수 또는 람다 함수보다 호출 가능한 객체를 사용하는 가장 좋은 시간은 논리가 복잡하고 일부 상태를 유지해야하거나 __call__()함수에 전달되지 않은 다른 정보를 사용하는 경우 입니다.

다음은 호출 가능한 객체 및를 사용하여 파일 이름 확장자를 기준으로 파일 이름을 필터링하는 코드입니다 filter().

호출 가능 :

import os

class FileAcceptor(object):
    def __init__(self, accepted_extensions):
        self.accepted_extensions = accepted_extensions

    def __call__(self, filename):
        base, ext = os.path.splitext(filename)
        return ext in self.accepted_extensions

class ImageFileAcceptor(FileAcceptor):
    def __init__(self):
        image_extensions = ('.jpg', '.jpeg', '.gif', '.bmp')
        super(ImageFileAcceptor, self).__init__(image_extensions)

용법:

filenames = [
    'me.jpg',
    'me.txt',
    'friend1.jpg',
    'friend2.bmp',
    'you.jpeg',
    'you.xml']

acceptor = ImageFileAcceptor()
image_filenames = filter(acceptor, filenames)
print image_filenames

산출:

['me.jpg', 'friend1.jpg', 'friend2.bmp', 'you.jpeg']

0

너무 늦었지만 예를 들겠습니다. Vector수업과 수업 이 있다고 상상해보십시오 Point. 둘 다 x, y위치 인수입니다. 벡터에 놓을 점을 이동시키는 함수를 만들고 싶다고 가정 해 봅시다.

4 가지 솔루션

  • put_point_on_vec(point, vec)

  • 벡터 클래스의 메소드로 만드십시오. 예 : my_vec.put_point(point)

  • 그것을 Point수업 의 방법으로 만드십시오 .my_point.put_on_vec(vec)
  • Vector구현 __call__하므로 다음과 같이 사용할 수 있습니다.my_vec_instance(point)

이것은 실제로 조만간 발표 할 Maths에 설명 된 Dunder 메소드에 대한 가이드를 위해 작업하고있는 일부 예제의 일부입니다.

나는이 질문에 관한 것이 아니기 때문에 포인트 자체를 이동시키는 논리를 남겼습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.