Python 中的泛型/模板?
- 2025-03-18 08:54:00
- admin 原创
- 40
问题描述:
python 如何处理通用/模板类型场景?假设我想创建一个外部文件“BinaryTree.py”并让它处理二叉树,但适用于任何数据类型。
因此我可以向其传递自定义对象的类型,并得到该对象的二叉树。如何在 Python 中实现这一点?
解决方案 1:
其他答案都很好:
Python 中不需要特殊的语法来支持泛型
正如André指出的那样,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"
参考:mypy 关于泛型和运行 mypy 的文档
解决方案 2:
Python 使用鸭子类型,因此不需要特殊的语法来处理多种类型。
如果您有 C++ 背景,您会记得,只要模板函数/类中使用的操作是在某种类型上定义的T
(在语法级别),您就可以T
在模板中使用该类型。
因此,基本上,它的工作原理是相同的:
为想要插入二叉树的项目类型定义一个合同。
记录此合同(即在课堂文档中)
仅使用合约中指定的操作来实现二叉树
享受
但是您会注意到,除非您编写明确的类型检查(通常不鼓励这样做),否则您将无法强制二叉树仅包含所选类型的元素。
解决方案 3:
实际上,现在你可以在 Python 3.5+ 中使用泛型。请参阅PEP-484和typing 模块文档。
根据我的实践,它不是很无缝和清晰,特别是对于那些熟悉 Java 泛型的人来说,但仍然可用。
解决方案 4:
在想出一些关于在 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 )
作为父类继承的多个类将从两个单独的类继承。为了解决这个问题,您需要创建一个字典来存储内部类的各种形式并返回之前创建的内部类,而不是创建一个新的内部类。这将防止创建具有相同参数的重复类型。如果感兴趣,可以使用装饰器和/或元类来制作更优雅的解决方案。
解决方案 5:
从Python 3.12开始,现在可以使用泛型类型和类型别名!(今天发布!)
按照PEP-695 的说法,现在可以编写如下代码:
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]
最后一个例子取自官方指南:
type 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
解决方案 6:
这是该答案的变体,它使用元类来避免混乱的语法,并使用typing
-styleList[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
解决方案 7:
由于 Python 是动态类型的,所以这非常简单。事实上,你必须为 BinaryTree 类做额外的工作,才能使其不适用于任何数据类型。
例如,如果您想要使用在对象中可用的方法来放置对象到树中的键值,那么key()
只需key()
在对象上调用该方法即可。例如:
class BinaryTree(object):
def insert(self, object_to_insert):
key = object_to_insert.key()
请注意,您无需定义 object_to_insert 是什么类型的类。只要它有一个key()
方法,它就可以工作。
例外情况是,如果您希望它处理字符串或整数等基本数据类型。您必须将它们包装在一个类中,才能使它们与您的通用二叉树一起工作。如果这听起来太沉重,而您想要的只是存储字符串的额外效率,对不起,这不是 Python 擅长的。
解决方案 8:
看看内置容器是如何做到的。 dict
等等list
包含您喜欢的任何类型的异构元素。如果您insert(val)
为树定义一个函数,它将在某个时候执行类似的事情node.value = val
,Python 将处理其余的事情。
解决方案 9:
如果您使用 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
支持添加
?
支持代码清理
解决方案 10:
幸运的是,在 Python 中,已经有人为泛型编程做出了一些努力。有一个库:generic
这是它的文档:http://generic.readthedocs.org/en/latest/
多年来它没有什么进步,但您可以大致了解如何使用和创建自己的图书馆。
干杯
扫码咨询,免费领取项目管理大礼包!