ChatGPT解决这个技术问题 Extra ChatGPT

python中的泛型/模板?

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

所以我可以将自定义对象的类型传递给它,并拥有该对象的二叉树。这是如何在 python 中完成的?

python有鸭子模板

m
mkrieger1

其他答案完全没问题:

不需要特殊的语法来支持 Python 中的泛型

正如 André 所指出的,Python 使用鸭子类型。

但是,如果你仍然想要一个类型化的变体,那么从 Python 3.5 开始就有一个内置的解决方案。

Python documentation 中提供了可用类型注释的完整列表。

通用类:

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.

静态类型检查:

您必须使用 静态类型检查器,例如 mypyPyre(由 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"

参考资料:关于 genericsrunning mypy 的 mypy 文档


绝对是这里的最佳答案
@Sush因为如果您知道这一点,那么您对 abc.ABC 的所有现有知识都适用于此处的 Stack 类。
我运行了上面的堆栈代码,并且由于某种原因在 stack.push("x") 上没有收到任何错误。这是为什么?
@QuocAnhTran 我添加了一个新部分“静态类型检查”以进行进一步解释。
@cikatomo 我们能够编写 Stack[int] 因为我们的 Stack 类继承自 Generic[T],我们用 [T] 指定我们的 Stack 类采用单个类型参数。
A
André Caron

Python 使用 duck typing,因此它不需要特殊的语法来处理多种类型。

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

因此,基本上,它的工作方式相同:

为要在二叉树中插入的项目类型定义合同。记录此合同(即在类文档中)仅使用合同中指定的操作来实现二叉树享受

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


André,我想了解为什么在 Python 中通常不鼓励显式类型检查。我很困惑,因为它似乎是一种动态类型的语言,如果我们不能保证可能进入函数的类型,我们可能会遇到很多麻烦。但是,话又说回来,我对 Python 很陌生。 :-)
@ScottEdwards2000 您可以使用 PEP 484 中的类型提示和类型检查器进行隐式类型检查
在 Python 纯粹主义者看来,Python 是一种动态语言,而鸭式类型是 范式;即,类型安全被裁定为“非 Pythonic”。这是我很难接受的东西——有一段时间——因为我非常依赖 C#。一方面,我发现类型安全是必要的。由于我已经平衡了 .Net 世界和 Pythonic 范式之间的尺度,我已经接受类型安全确实是一种安抚奶嘴,如果我需要,我所要做的就是 if isintance(o, t):if not isinstance(o, t): ...漂亮简单的。
感谢评论者,很好的答案。读完之后我意识到我真的只是想通过类型检查来发现我自己的错误。所以我将只使用隐式类型检查。
我认为许多 Python 主义者都忽略了这一点——泛型是一种同时提供自由和安全的方法。即使抛开泛型和只使用类型参数,函数编写者也知道他们可以修改代码以使用类提供的任何方法;使用duck typing 如果你开始使用以前没有使用过的方法,你突然改变了duck 的定义,事情可能会中断。
C
Community

实际上,现在您可以在 Python 3.5+ 中使用泛型。请参阅 PEP-484typing module documentation

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


这看起来像是对仿制药的廉价抄袭。就像有人拿到了仿制药,把它们放在搅拌机里,让它运行然后忘记它,直到搅拌机电机烧坏,然后在 2 天后把它拿出来说:“嘿,我们有仿制药”。
这些是“类型提示”,它们与泛型无关。
在打字稿中相同,但它在 Java 中的工作方式(语法上)。这些语言中的泛型只是类型提示
Y
Yevhen Kuzmovych

在对在 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来存储内部类的各种形式并返回之前创建的内部类,而不是创建一个新的。这将防止创建具有相同参数的重复类型。如果有兴趣,可以使用装饰器和/或元类制作更优雅的解决方案。


您能否详细说明在上面的示例中如何使用 dict ?你在 git 或其他东西中有一个片段吗?谢谢..
我没有示例,现在可能有点耗时。然而,这些原则并不难。 dict 充当缓存。创建新类时,它需要查看类型参数以为该类型和参数配置创建标识符。然后它可以将它用作字典中的键来查找先前存在的类。这样,它将一遍又一遍地使用那个类。
感谢您的启发 - 请参阅 my answer 以了解此技术与元类的扩展
L
Leopd

由于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 中都是对象。它们不需要被包装(如在 Java 中使用 Integer 装箱/拆箱)。
E
Eric

这是 this answer 的变体,它使用元类来避免混乱的语法,并使用 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

F
Florian Steenbuck

如果你使用 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)

待办事项

编译器

让泛型类和类型正常工作(对于像 > 之类的东西)

添加超级支持

添加 ?支持

代码清理


J
John Zwinck

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


i
igauravsehrawat

幸运的是,python 中的泛型编程已经做出了一些努力。有一个图书馆:generic

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

它多年来一直没有进展,但您可以大致了解如何使用和制作自己的库。

干杯