파이썬의 제네릭 / 템플릿?


84

파이썬은 제네릭 / 템플릿 유형 시나리오를 어떻게 처리합니까? "BinaryTree.py"외부 파일을 만들고 이진 트리를 처리하도록하고 싶지만 모든 데이터 유형에 대해 사용하고 싶다고 가정 해 보겠습니다.

그래서 사용자 지정 개체의 유형을 전달하고 해당 개체의 이진 트리를 가질 수 있습니다. 이것은 파이썬에서 어떻게 이루어 집니까?


14
파이썬은 오리 템플릿이 있습니다
데이비드 헤퍼에게

답변:


82

파이썬은 덕 타이핑을 사용 하므로 여러 유형을 처리하기 위해 특별한 구문이 필요하지 않습니다.

C ++ 배경 인 경우 템플릿 함수 / 클래스에서 사용되는 작업 T이 구문 수준에서 특정 유형에 정의되어 T있는 한 템플릿에서 해당 유형 을 사용할 수 있다는 것을 기억할 것입니다 .

따라서 기본적으로 동일한 방식으로 작동합니다.

  1. 바이너리 트리에 삽입하려는 항목 유형에 대한 계약을 정의하십시오.
  2. 이 계약을 문서화 (즉, 클래스 문서에)
  3. 계약에 지정된 작업 만 사용하여 이진 트리 구현
  4. 즐겨

그러나 명시적인 유형 검사 (일반적으로 권장되지 않음)를 작성하지 않는 한 이진 트리에 선택한 유형의 요소 만 포함하도록 강제 할 수 없습니다.


5
André, 왜 명시 적 유형 검사가 일반적으로 Python에서 권장되지 않는지 이해하고 싶습니다. 동적으로 입력되는 언어 인 것처럼 보이므로 혼란 스럽습니다. 함수에 들어갈 수있는 유형을 보장 할 수 없으면 많은 문제가 발생할 수 있습니다. 그러나 다시 저는 Python을 처음 접했습니다. :-)
ScottEdwards2000

2
@ ScottEdwards2000 당신은 암시 적 타입 PEP 484에 타입 힌트를 확인하고 유형 검사 할 수 있습니다
noɥʇʎԀʎzɐɹƆ

6
파이썬 순수 주의자의 관점에서, 파이썬은 동적 언어와 오리 - 입력이입니다 패러다임; 즉, 형식 안전성은 'non-Pythonic'으로 간주됩니다. 이것은 내가 C #에 많은 권한을 가지고 있기 때문에 당분간은 수용하기가 어려웠습니다. 한편으로는 형식 안전성이 필수라고 생각합니다. .Net 세계와 Pythonic 패러다임 사이의 척도를 균형있게 조정했기 때문에 유형 안전성이 정말 고무 젖꼭지라는 점을 받아 들였습니다. 필요한 경우해야 할 일은 또는 ... 아주 간단합니다. if isintance(o, t):if not isinstance(o, t):
IAbstract

2
댓글 작성자, 훌륭한 답변에 감사드립니다. 나는 그것들을 읽은 후 내 자신의 오류를 포착하기 위해 유형 검사를 정말로 원한다는 것을 깨달았습니다. 따라서 암시 적 유형 검사를 사용합니다.
ScottEdwards2000

1
제네릭은 자유와 안전을 동시에 제공하는 방법입니다. 제네릭은 제쳐두고 형식화 된 매개 변수 만 사용하더라도 함수 작성자는 클래스가 제공하는 모든 메서드를 사용하도록 코드를 수정할 수 있음을 알고 있습니다. 이전에 사용하지 않은 방법을 사용하기 시작하면 오리 타이핑을 사용하면 갑자기 오리의 정의가 변경되어 문제가 발생할 수 있습니다.
Ken Williams

46

다른 대답은 완전히 괜찮습니다.

  • 파이썬에서 제네릭을 지원하기 위해 특별한 구문이 필요하지 않습니다.
  • Python은 André가 지적한대로 덕 타이핑을 사용합니다 .

그러나 유형화 된 변형이 필요한 경우 Python 3.5 이후 기본 제공 솔루션이 있습니다.

일반 클래스 :

from typing import TypeVar, Generic

T = TypeVar('T')

class Stack(Generic[T]):
    def __init__(self) -> None:
        # Create an empty list with items of type T
        self.items: List[T] = []

    def push(self, item: T) -> None:
        self.items.append(item)

    def pop(self) -> T:
        return self.items.pop()

    def empty(self) -> bool:
        return not self.items
# Construct an empty Stack[int] instance
stack = Stack[int]()
stack.push(2)
stack.pop()
stack.push('x')        # Type error

일반 기능 :

from typing import TypeVar, Sequence

T = TypeVar('T')      # Declare type variable

def first(seq: Sequence[T]) -> T:
    return seq[0]

def last(seq: Sequence[T]) -> T:
    return seq[-1]


n = first([1, 2, 3])  # n has type int.

참조 : 제네릭 에 대한 mypy 문서 .


19

실제로 이제 Python 3.5 이상에서 제네릭을 사용할 수 있습니다. PEP-484입력 라이브러리 설명서를 참조하십시오 .

내 관행에 따르면 특히 Java Generics에 익숙하지만 여전히 사용 가능한 사람들에게는 매끄럽고 명확하지 않습니다.


10
그것은 제네릭 tbh의 값싼 찢어짐처럼 보입니다. 마치 누군가가 제네릭을 가지고 블렌더에 넣고 블렌더 모터가 다 타 버릴 때까지 잊은 다음 2 일 후에 꺼내서 "제네릭을 얻었습니다"라고 말하는 것과 같습니다.
모두

3
그것들은 "타입 힌트"이며 제네릭과는 아무 관련이 없습니다.
wool.in.silver

typescript에서 동일하지만 Java 에서처럼 작동합니다 (구문 적으로). 이러한 언어의 제네릭은 힌트를 입력하는
다비드

11

파이썬에서 제네릭 타입을 만드는 것에 대한 좋은 생각을 떠난 후, 같은 아이디어를 가진 다른 사람들을 찾기 시작했지만 아무것도 찾을 수 없었습니다. 그래서 여기 있습니다. 나는 이것을 시도했고 잘 작동합니다. 파이썬에서 타입을 매개 변수화 할 수 있습니다.

class List( type ):

    def __new__(type_ref, member_type):

        class List(list):

            def append(self, member):
                if not isinstance(member, member_type):
                    raise TypeError('Attempted to append a "{0}" to a "{1}" which only takes a "{2}"'.format(
                        type(member).__name__,
                        type(self).__name__,
                        member_type.__name__ 
                    ))

                    list.append(self, member)

        return List 

이제이 제네릭 유형에서 유형을 파생 할 수 있습니다.

class TestMember:
        pass

class TestList(List(TestMember)):

    def __init__(self):
        super().__init__()


test_list = TestList()
test_list.append(TestMember())
test_list.append('test') # This line will raise an exception

이 솔루션은 단순하며 한계가 있습니다. 제네릭 유형을 만들 때마다 새 유형이 생성됩니다. 따라서 List( str )부모로 상속하는 여러 클래스 는 두 개의 개별 클래스에서 상속됩니다. 이를 극복하려면 새로운 내부 클래스를 생성하는 대신 다양한 형태의 내부 클래스를 저장하고 이전에 생성 된 내부 클래스를 반환하는 dict를 생성해야합니다. 이렇게하면 동일한 매개 변수를 가진 중복 유형이 생성되지 않습니다. 관심이 있다면 데코레이터 및 / 또는 메타 클래스를 사용하여보다 우아한 솔루션을 만들 수 있습니다.


위의 예에서 dict를 어떻게 사용할 수 있는지 자세히 설명해 주시겠습니까? git 또는 무언가에 대한 스 니펫이 있습니까? 감사합니다 ..
gnomeria

나는 예가 없으며 지금 당장은 시간이 조금 걸릴 수 있습니다. 그러나 원칙은 그렇게 어렵지 않습니다. dict는 캐시 역할을합니다. 새 클래스가 생성되면 해당 유형 및 매개 변수 구성에 대한 식별자를 생성하기 위해 유형 매개 변수를 살펴 봐야합니다. 그런 다음 이전에 존재하는 클래스를 조회하기 위해 dict의 키로 사용할 수 있습니다. 이렇게하면 한 클래스를 계속해서 사용합니다.
Ché on The Scene 2015 년

영감을 주셔서 감사합니다 - 볼 내 대답을 메타 클래스와이 기술의 연장
에릭

4

Python은 동적으로 유형이 지정되기 때문에 많은 경우 객체 유형이 중요하지 않습니다. 무엇이든 받아들이는 것이 더 좋은 생각입니다.

내가 의미하는 바를 설명하기 위해이 트리 클래스는 두 가지 분기에 대해 무엇이든 허용합니다.

class BinaryTree:
    def __init__(self, left, right):
        self.left, self.right = left, right

그리고 다음과 같이 사용할 수 있습니다.

branch1 = BinaryTree(1,2)
myitem = MyClass()
branch2 = BinaryTree(myitem, None)
tree = BinaryTree(branch1, branch2)

6
개체 유형 중요합니다. 컨테이너의 항목을 반복하고 foo각 개체에 대해 메서드 를 호출하는 경우 컨테이너에 문자열을 넣는 것은 좋지 않습니다. 그것은 아니다 받아 들일 생각이 아무것도 . 그러나 컨테이너의 모든 객체가 class에서 파생되도록 요구하지 않는 것이 편리 합니다 HasAFooMethod.
André Caron 2011

1
실제로 유형 중요합니다. 주문해야합니다.
Fred Foo

오 그래. 나는 그때 오해했다.
Andrea

3

파이썬은 동적으로 입력되기 때문에 매우 쉽습니다. 사실, 어떤 데이터 유형과도 작동하지 않도록 BinaryTree 클래스에 대한 추가 작업을 수행해야합니다.

예를 들어, 개체를 key()호출 하는 것과 같은 메서드에서 개체 내에서 사용 가능한 트리에 개체를 배치하는 데 사용되는 키 값을 원하는 경우 key()입니다. 예를 들면 :

class BinaryTree(object):

    def insert(self, object_to_insert):
        key = object_to_insert.key()

object_to_insert 클래스의 종류를 정의 할 필요가 없습니다. 너무 오래 그것은이 같은 key()방법을, 그것을 작동합니다.

예외는 문자열이나 정수와 같은 기본 데이터 유형으로 작업하려는 경우입니다. 일반 BinaryTree와 함께 작동하도록하려면 클래스에 래핑해야합니다. 너무 무겁게 들리고 실제로 문자열을 저장하는 추가 효율성을 원하면 죄송합니다. 파이썬이 잘하는 것은 아닙니다.


7
반대로 모든 데이터 유형은 Python의 객체입니다. 래핑 할 필요가 없습니다 ( Integer복싱 / 언 박싱 을 사용하는 Java에서와 같이 ).
George Hilliard

2

다음 은 지저분한 구문을 피하기 위해 메타 클래스를 사용하고 -style 구문을 사용하는 이 답변 의 변형입니다 .typingList[int]

class template(type):
    def __new__(metacls, f):
        cls = type.__new__(metacls, f.__name__, (), {
            '_f': f,
            '__qualname__': f.__qualname__,
            '__module__': f.__module__,
            '__doc__': f.__doc__
        })
        cls.__instances = {}
        return cls

    def __init__(cls, f):  # only needed in 3.5 and below
        pass

    def __getitem__(cls, item):
        if not isinstance(item, tuple):
            item = (item,)
        try:
            return cls.__instances[item]
        except KeyError:
            cls.__instances[item] = c = cls._f(*item)
            item_repr = '[' + ', '.join(repr(i) for i in item) + ']'
            c.__name__ = cls.__name__ + item_repr
            c.__qualname__ = cls.__qualname__ + item_repr
            c.__template__ = cls
            return c

    def __subclasscheck__(cls, subclass):
        for c in subclass.mro():
            if getattr(c, '__template__', None) == cls:
                return True
        return False

    def __instancecheck__(cls, instance):
        return cls.__subclasscheck__(type(instance))

    def __repr__(cls):
        import inspect
        return '<template {!r}>'.format('{}.{}[{}]'.format(
            cls.__module__, cls.__qualname__, str(inspect.signature(cls._f))[1:-1]
        ))

이 새로운 메타 클래스를 사용하면 내가 링크하는 답변의 예제를 다음과 같이 다시 작성할 수 있습니다.

@template
def List(member_type):
    class List(list):
        def append(self, member):
            if not isinstance(member, member_type):
                raise TypeError('Attempted to append a "{0}" to a "{1}" which only takes a "{2}"'.format(
                    type(member).__name__,
                    type(self).__name__,
                    member_type.__name__ 
                ))

                list.append(self, member)
    return List

l = List[int]()
l.append(1)  # ok
l.append("one")  # error

이 접근 방식에는 몇 가지 좋은 이점이 있습니다.

print(List)  # <template '__main__.List[member_type]'>
print(List[int])  # <class '__main__.List[<class 'int'>, 10]'>
assert List[int] is List[int]
assert issubclass(List[int], List)  # True

1

기본 제공 컨테이너가 어떻게 작동하는지 살펴보십시오. dictlist등등 당신이 좋아하는 어떤 종류의 이질적인 요소가 포함되어 있습니다. 예를 들어, insert(val)트리에 대한 함수를 정의하면 어느 시점에서 다음과 같은 작업을 수행 node.value = val하고 Python이 나머지를 처리합니다.


1

다행히도 파이썬에서 제네릭 프로그래밍에 대한 노력이있었습니다. 라이브러리가 있습니다 : 일반

이에 대한 문서는 다음과 같습니다. http://generic.readthedocs.org/en/latest/

수년 동안 진행되지 않았지만 자신의 라이브러리를 사용하고 만드는 방법을 대략적으로 알 수 있습니다.

건배


1

Python 2를 사용하거나 Java 코드를 다시 작성하려는 경우. 이것에 대한 실제 해결책이 아닙니다. 다음은 내가 밤에 작업하는 것입니다. https://github.com/FlorianSteenbuck/python-generics 여전히 컴파일러가 없으므로 현재 다음과 같이 사용합니다.

class A(GenericObject):
    def __init__(self, *args, **kwargs):
        GenericObject.__init__(self, [
            ['b',extends,int],
            ['a',extends,str],
            [0,extends,bool],
            ['T',extends,float]
        ], *args, **kwargs)

    def _init(self, c, a, b):
        print "success c="+str(c)+" a="+str(a)+" b="+str(b)

할 일

  • 컴파일러
  • 일반 클래스와 유형 (같은 것들에 대한 작업 가져 오기 <? extends List<Number>>)
  • super지원 추가
  • ?지원 추가
  • 코드 정리
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.