Python 中的泛型/模板?

2025-04-16 08:56:00
admin
原创
15
摘要:问题描述:Python 如何处理泛型/模板类型场景?假设我想创建一个外部文件“BinaryTree.py”,让它处理二叉树,但支持任意数据类型。 所以我可以传递一个自定义对象的类型,并得到该对象的二叉树。在 Python 中如何实现?解决方案 1:其他答案完全没问题:Python 中不需要特殊的语法来支持泛型...

问题描述:

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在模板中使用该类型。

所以,基本上,它的工作原理是相同的:

  1. 为想要插入二叉树的项目类型定义一个合同。

  2. 记录此合同(即在类文档中)

  3. 仅使用合约中指定的操作来实现二叉树

  4. 享受

但是您会注意到,除非您编写明确的类型检查(通常不鼓励这样做),否则您将无法强制二叉树仅包含所选类型的元素。

解决方案 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/

多年来它一直没有进步,但您可以大致了解如何使用和创建自己的图书馆。

干杯

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2482  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1533  
  PLM(产品生命周期管理)项目对于企业优化产品研发流程、提升产品质量以及增强市场竞争力具有至关重要的意义。然而,在项目推进过程中,范围蔓延是一个常见且棘手的问题,它可能导致项目进度延迟、成本超支以及质量下降等一系列不良后果。因此,有效避免PLM项目范围蔓延成为项目成功的关键因素之一。以下将详细阐述三大管控策略,助力企业...
plm系统   0  
  PLM(产品生命周期管理)项目管理在企业产品研发与管理过程中扮演着至关重要的角色。随着市场竞争的加剧和产品复杂度的提升,PLM项目面临着诸多风险。准确量化风险优先级并采取有效措施应对,是确保项目成功的关键。五维评估矩阵作为一种有效的风险评估工具,能帮助项目管理者全面、系统地评估风险,为决策提供有力支持。五维评估矩阵概述...
免费plm软件   0  
  引言PLM(产品生命周期管理)开发流程对于企业产品的全生命周期管控至关重要。它涵盖了从产品概念设计到退役的各个阶段,直接影响着产品质量、开发周期以及企业的市场竞争力。在当今快速发展的科技环境下,客户对产品质量的要求日益提高,市场竞争也愈发激烈,这就使得优化PLM开发流程成为企业的必然选择。缺陷管理工具和六西格玛方法作为...
plm产品全生命周期管理   0  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用