Pythonのジェネリック/テンプレート?


83

Pythonはジェネリック/テンプレートタイプのシナリオをどのように処理しますか?外部ファイル「BinaryTree.py」を作成し、それがバイナリツリーを処理するようにしたいが、任意のデータ型であるとします。

したがって、カスタムオブジェクトのタイプを渡して、そのオブジェクトのバイナリツリーを作成できます。これはPythonでどのように行われますか?


14
Pythonにはアヒルのテンプレートがあります
David Heffernan 2011

回答:


82

Pythonはダックタイピングを使用するため、複数のタイプを処理するために特別な構文は必要ありません。

C ++のバックグラウンドをお持ちの場合は、テンプレート関数/クラスで使用される操作がT(構文レベルで)何らかの型で定義されている限り、その型Tをテンプレートで使用できることを覚えておいてください。

したがって、基本的には同じように機能します。

  1. 二分木に挿入するアイテムのタイプのコントラクトを定義します。
  2. この契約を文書化します(つまり、クラスの文書に)
  3. コントラクトで指定された操作のみを使用してバイナリツリーを実装する
  4. 楽しい

ただし、明示的な型チェックを記述しない限り(通常は推奨されません)、バイナリツリーに選択した型の要素のみが含まれるように強制することはできません。


5
アンドレ、Pythonで明示的な型チェックが通常推奨されない理由を理解したいと思います。動的に型付けされた言語のように見えるので混乱しています。関数に入る可能性のある型を保証できないと、多くの問題が発生する可能性があります。しかし、繰り返しになりますが、私はPythonに非常に慣れていません。:-)
ScottEdwards2000 2016年

2
@ ScottEdwards2000あなたは暗黙的な型PEP 484でタイプヒントをチェックし、型チェッカー持つことができます
noɥʇʎԀʎzɐɹƆ

6
Pythonの純粋主義者の観点では、Pythonは動的言語であり、ダックタイピングであるパラダイム。つまり、型安全性は「非Pythonic」と判断されます。これは、私がC#に深く関わっているため、しばらくの間、受け入れられるものを見つけるのが困難でした。一方で、型安全性が必要だと思います。.Netの世界とPythonのパラダイムの間でスケールのバランスを取っているので、型安全性は本当におしゃぶりであり、必要な場合は、または...非常に単純であることを受け入れました。if isintance(o, t):if not isinstance(o, t):
IAbstract 2018

2
コメント投稿者、素晴らしい回答に感謝します。それらを読んだ後、私は本当に自分のエラーをキャッチするために型チェックが欲しいだけだと気づきました。だから私は暗黙の型チェックを使うだけです。
ScottEdwards2000 2018年

1
多くのPythonistは、これについての要点を見逃していると思います。ジェネリックは、自由と安全を同時に提供する方法です。ジェネリックスを除いて、型付きパラメーターを使用するだけでも、関数の作成者は、クラスが提供する任意のメソッドを使用するようにコードを変更できることを知っています。ダックタイピングでは、これまで使用したことのない方法を使い始めると、突然アヒルの定義が変更され、問題が発生する可能性があります。
ケンウィリアムズ

45

他の答えはまったく問題ありません。

  • Pythonでジェネリックスをサポートするために特別な構文は必要ありません
  • Andréが指摘したように、Pythonはダックタイピングを使用します。

ただし、それでも型付きバリアントが必要な場合は、Python3.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ドキュメント。


18

実際、Python3.5以降でジェネリックを使用できるようになりました。PEP-484およびタイピングライブラリのドキュメントを参照してください。

私の実践によれば、特にJava Genericsに精通している人にとっては、それほどシームレスで明確ではありませんが、それでも使用可能です。


10
それはジェネリック医薬品tbhの安価な盗品のように見えます。誰かがジェネリックを手に入れ、ブレンダーに入れて、ブレンダーモーターが燃え尽きるまで動かして忘れて、2日後に取り出して「ジェネリックを手に入れました」と言ったようなものです。
全員

3
これらは「型のヒント」であり、ジェネリックとは何の関係もありません。
wool.in.silver

typescriptでも同じですが、Javaと同じように機能します(構文的に)。これらの言語のジェネリックは単なるタイプのヒントです
Davide

11

Pythonでジェネリック型を作成することについていくつかの良い考えを思いついた後、私は同じ考えを持っている他の人を探し始めましたが、何も見つかりませんでした。だから、ここにあります。私はこれを試してみました、そしてそれはうまくいきます。これにより、Pythonで型をパラメーター化できます。

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 )親として継承する複数のクラスは、2つの別々のクラスから継承します。これを克服するには、新しい内部クラスを作成するのではなく、内部クラスのさまざまな形式を格納し、以前に作成した内部クラスを返すdictを作成する必要があります。これにより、同じパラメーターを持つ重複タイプが作成されるのを防ぐことができます。興味があれば、デコレータやメタクラスを使用して、よりエレガントなソリューションを作成できます。


上記の例でdictをどのように使用できるかについて詳しく説明していただけますか?そのためのスニペットがgitか何かにありますか?ありがとう...
gnomeria

例はありませんが、今は少し時間がかかるかもしれません。ただし、原則はそれほど難しくありません。dictはキャッシュとして機能します。新しいクラスを作成するときは、型パラメーターを調べて、その型とパラメーター構成の識別子を作成する必要があります。次に、それをdictのキーとして使用して、既存のクラスを検索できます。このようにして、その1つのクラスを何度も使用します。
シーンのChé2017年

インスピレーションをありがとう-メタクラスによるこのテクニックの拡張についての私の答えを参照しください
エリック

4

Pythonは動的に型付けされるため、多くの場合、オブジェクトの型は重要ではありません。何でも受け入れる方が良い考えです。

私が何を意味するかを示すために、このツリークラスはその2つのブランチに対して何でも受け入れます。

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各オブジェクトのメソッドを呼び出す場合は、コンテナに文字列を配置することはお勧めできません。何かを受け入れるのは良い考えではありません。ただし、コンテナ内のすべてのオブジェクトがクラスから派生している必要はないので便利です。HasAFooMethod
アンドレキャロン2011

1
実際には、タイプ重要です。注文する必要があります。
フレッドフー

ああ、わかりました。その時私は誤解しました。
アンドレア

3

Pythonは動的に型付けされるため、これは非常に簡単です。実際、どのデータ型でも機能しないようにするには、BinaryTreeクラスに追加の作業を行う必要があります。

たとえば、オブジェクトをkey()呼び出すだけのようなメソッドから、オブジェクト内で使用可能なツリーにオブジェクトを配置するために使用されるキー値が必要な場合key()です。例えば:

class BinaryTree(object):

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

object_to_insertのクラスを定義する必要はないことに注意してください。key()メソッドがある限り、機能します。

例外は、文字列や整数などの基本的なデータ型で機能させたい場合です。それらを汎用のBinaryTreeで機能させるには、それらをクラスでラップする必要があります。それが重すぎるように聞こえ、実際に文字列を格納するだけの効率を高めたい場合は、申し訳ありませんが、それはPythonが得意なことではありません。


7
それどころか、すべてのデータ型はPythonのオブジェクトです。それらをラップする必要はありません(Integerボクシング/ボックス化解除のJavaのように)。
ジョージヒリアード2015

2

ここの変種だこの回答用途のメタクラスは厄介な構文を避け、かつ、使用することをtypingスタイルのList[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

組み込みコンテナがどのようにそれを行うかを見てください。 dictなどlistには、好きなタイプの異種要素が含まれています。たとえばinsert(val)、ツリーの関数を定義すると、ある時点で次のようなことが行われnode.value = val、残りはPythonが処理します。


1

幸いなことに、Pythonでのジェネリックプログラミングにはいくつかの努力が払われてきました。ライブラリがあります:ジェネリック

これがそのドキュメントです: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)

TODO

  • コンパイラ
  • ジェネリッククラスと型を機能させる(のようなもののために<? extends List<Number>>
  • superサポートを追加する
  • ?サポートを追加する
  • コードのクリーンアップ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.