Python 中的泛型/模板?

Python 如何处理泛型/模板类型场景?假设我想创建一个外部文件“ BinaryTree.py”,并让它处理任何数据类型的二进制树。

所以我可以把自定义对象的类型传递给它,然后得到这个对象的二叉树。蟒蛇是怎么做到的?

169988 次浏览

看看内置容器是如何做到这一点的。dictlist等包含您喜欢的任何类型的异构元素。如果您为树定义一个 insert(val)函数,那么它将在某个时刻执行类似于 node.value = val的操作,Python 将处理其余的事情。

因为 python 是动态类型化的,所以这非常简单。实际上,您必须为 BinaryTree 类做额外的工作才能不与任何数据类型一起工作。

例如,如果您想要使用键值来将对象放置在树中,这些键值可以通过类似 key()的方法从对象中获得,那么您只需对对象调用 key()即可。例如:

class BinaryTree(object):


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

请注意,您永远不需要定义 object _ to _ insert 是什么类。只要它有一个 key()方法,它就会工作。

例外的情况是,如果您希望它与基本数据类型(如字符串或整数)一起工作。您必须将它们包装在一个类中才能让它们与您的泛型 BinaryTree 一起工作。如果这听起来过于沉重,并且您希望实际上只存储字符串的额外效率,那么抱歉,这不是 Python 擅长的。

Python 使用 鸭子打字,因此它不需要特殊的语法来处理多种类型。

如果您来自 C + + 背景,您会记得,只要模板函数/类中使用的操作是在某种类型 T上定义的(在语法级别) ,您就可以在模板中使用该类型 T

所以,基本上,它的工作原理是一样的:

  1. 为要插入到二进制树中的项的类型定义一个约定。
  2. 记录这份合同(例如在类别文件中)
  3. 只使用契约中指定的操作来实现二叉树
  4. 好好享受

然而,您会注意到,除非您编写显式的类型检查(这通常是不鼓励的) ,否则您将无法强制二进制树只包含所选类型的元素。

幸运的是,对于 python 中的泛型,已经做出了一些努力。 有一个库:

下面是它的文档: http://generic.readthedocs.org/en/latest/

它多年来没有进步,但你可以有一个粗略的想法如何使用和制作自己的图书馆。

干杯

实际上,现在您可以在 Python 3.5 + 中使用泛型。 参见 PEP-484打字模块文档

根据我的实践,它不是非常无缝和清晰,特别是对于那些熟悉 Java 泛型,但仍然可用。

在想出了一些关于在 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 )作为父类的多个类将从两个单独的类继承。为了克服这个问题,您需要创建一个 dict 来存储内部类的各种形式并返回先前创建的内部类,而不是创建一个新的内部类。这将防止创建具有相同参数的重复类型。如果感兴趣,可以使用装饰器和/或元类来制作一个更优雅的解决方案。

如果您使用 Python2或者想要重写 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支持
  • 添加 ?支持
  • 代码清理

下面是 这个答案的一个变体,它使用元类来避免混乱的语法,并使用 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 中,不需要特殊的语法来支持泛型
  • Python 使用了 安德烈指出的 Duck 类型。

但是,如果您仍然需要 打好了变体,那么自 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.

静态类型检查 :

您必须使用 静态类型检查器静态类型检查器,如 我的天火葬(由 Meta/FB 开发)来分析源代码。

安装 mypy:

python3 -m pip install mypy

分析源代码,例如某个文件:

mypy foo.py

或目录:

mypy some_directory

Mypy 将检测和打印类型错误:

foo.py:23: error: Argument 1 to "push" of "Stack" has incompatible type "str"; expected "int"

参考文献 : 关于 非专利药和 < a href = “ https://mypy.readthedocs.io/en/stat/running _ mypy.html”rel = “ noReferrer”> running mypy 的 mypy 文档