Pythonはジェネリック/テンプレートタイプのシナリオをどのように処理しますか?外部ファイル「BinaryTree.py」を作成し、それがバイナリツリーを処理するようにしたいが、任意のデータ型であるとします。
したがって、カスタムオブジェクトのタイプを渡して、そのオブジェクトのバイナリツリーを作成できます。これはPythonでどのように行われますか?
回答:
Pythonはダックタイピングを使用するため、複数のタイプを処理するために特別な構文は必要ありません。
C ++のバックグラウンドをお持ちの場合は、テンプレート関数/クラスで使用される操作がT
(構文レベルで)何らかの型で定義されている限り、その型T
をテンプレートで使用できることを覚えておいてください。
したがって、基本的には同じように機能します。
ただし、明示的な型チェックを記述しない限り(通常は推奨されません)、バイナリツリーに選択した型の要素のみが含まれるように強制することはできません。
if isintance(o, t):
if not isinstance(o, t):
他の答えはまったく問題ありません。
ただし、それでも型付きバリアントが必要な場合は、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ドキュメント。
実際、Python3.5以降でジェネリックを使用できるようになりました。PEP-484およびタイピングライブラリのドキュメントを参照してください。
私の実践によれば、特にJava Genericsに精通している人にとっては、それほどシームレスで明確ではありませんが、それでも使用可能です。
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を作成する必要があります。これにより、同じパラメーターを持つ重複タイプが作成されるのを防ぐことができます。興味があれば、デコレータやメタクラスを使用して、よりエレガントなソリューションを作成できます。
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)
foo
各オブジェクトのメソッドを呼び出す場合は、コンテナに文字列を配置することはお勧めできません。何かを受け入れるのは良い考えではありません。ただし、コンテナ内のすべてのオブジェクトがクラスから派生している必要はないので便利です。HasAFooMethod
Pythonは動的に型付けされるため、これは非常に簡単です。実際、どのデータ型でも機能しないようにするには、BinaryTreeクラスに追加の作業を行う必要があります。
たとえば、オブジェクトをkey()
呼び出すだけのようなメソッドから、オブジェクト内で使用可能なツリーにオブジェクトを配置するために使用されるキー値が必要な場合key()
です。例えば:
class BinaryTree(object):
def insert(self, object_to_insert):
key = object_to_insert.key()
object_to_insertのクラスを定義する必要はないことに注意してください。key()
メソッドがある限り、機能します。
例外は、文字列や整数などの基本的なデータ型で機能させたい場合です。それらを汎用のBinaryTreeで機能させるには、それらをクラスでラップする必要があります。それが重すぎるように聞こえ、実際に文字列を格納するだけの効率を高めたい場合は、申し訳ありませんが、それはPythonが得意なことではありません。
Integer
ボクシング/ボックス化解除のJavaのように)。
ここの変種だこの回答用途のメタクラスは厄介な構文を避け、かつ、使用することを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
幸いなことに、Pythonでのジェネリックプログラミングにはいくつかの努力が払われてきました。ライブラリがあります:ジェネリック
これがそのドキュメントです:http://generic.readthedocs.org/en/latest/
それは何年にもわたって進歩していませんが、あなたはあなた自身のライブラリをどのように使用して作るかについて大まかな考えを持つことができます。
乾杯
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
サポートを追加する?
サポートを追加する