Python如何处理通用/模板类型的场景?比如我想创建一个名为“BinaryTree.py”的外部文件,并使其处理二叉树,但适用于任何数据类型。
因此,我可以将自定义对象的类型传递给它,并拥有该对象的二叉树。这在Python中是如何实现的呢?
Python如何处理通用/模板类型的场景?比如我想创建一个名为“BinaryTree.py”的外部文件,并使其处理二叉树,但适用于任何数据类型。
因此,我可以将自定义对象的类型传递给它,并拥有该对象的二叉树。这在Python中是如何实现的呢?
然而,如果你仍然想要一个类型化的变体,自从Python 3.5以来就有一个内置的解决方案。
可以在Python文档中找到所有可用的类型注释列表。
通用类:
from typing import TypeVar, Generic, List
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或Pyre(由Meta/FB开发)这样的静态类型检查器来分析源代码。
安装mypy:
python3 -m pip install mypy
mypy foo.py
或目录:
mypy some_directory
mypy将检测和打印类型错误。对于上面提供的Stack示例,具体输出如下:
foo.py:23: error: Argument 1 to "push" of "Stack" has incompatible type "str"; expected "int"
Generic[T]
,在其中使用 [T]
指定了我们的 Stack 类接受单个类型参数。 - momoPython使用鸭子类型,因此不需要特殊语法来处理多种类型。
如果您来自C++背景,您会记得只要模板函数/类中使用的操作在某个类型T
上定义(在语法级别上),您就可以在模板中使用该类型T
。
所以,基本上它的工作方式相同:
但是,请注意,除非您编写显式的类型检查(通常不鼓励这样做),否则您将无法强制执行二叉树仅包含所选类型的元素。
if isinstance(o, t):
或if not isinstance(o, t):
...非常简单。 - IAbstract在想出如何在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)
作为父类继承时,将会继承两个不同的类。为了克服这个问题,你需要创建一个字典来存储内部类的各种形式,并返回先前创建的内部类,而不是创建一个新的内部类。这可以防止创建具有相同参数的重复类型。如果感兴趣,可以使用装饰器和/或元类制作更优雅的解决方案。
typing
-style 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是动态类型的,因此这非常容易。实际上,为了使您的BinaryTree类不与任何数据类型一起使用,您需要额外工作。
例如,如果您希望将用于将对象放置在树中的键值从key()
方法中可用于对象中,只需在对象上调用key()
即可。例如:
class BinaryTree(object):
def insert(self, object_to_insert):
key = object_to_insert.key()
Integer
包装/取消包装那样进行处理。 - George Hilliardclass 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
支持?
支持幸运的是,Python中已经有一些通用编程方面的努力。这里有一个库:generic
这是它的文档:http://generic.readthedocs.org/en/latest/
虽然它多年来没有进展,但您可以大致了解如何使用它并创建自己的库。
祝贺你
看看内置的容器是如何做到的。 dict
和 list
等包含各种类型的异构元素。如果你为树定义了一个 insert(val)
函数,它在某个时刻会执行类似于 node.value = val
的操作,Python 会处理其余部分。
def max[T](args: Iterable[T]) -> T:
...
class list[T]:
def __getitem__(self, index: int, /) -> T:
...
def append(self, element: T) -> None:
...
type Point = tuple[float, float]
ype IntFunc[**P] = Callable[P, int] # ParamSpec
type LabeledTuple[*Ts] = tuple[str, *Ts] # TypeVarTuple
type HashableSequence[T: Hashable] = Sequence[T] # TypeVar with bound
type IntOrStrSequence[T: (int, str)] = Sequence[T] # TypeVar with constraints