python中的泛型/模板?

key*_*eys 62 python templates generic-programming

python如何处理通用/模板类型场景?假设我想创建一个外部文件"BinaryTree.py"并让它处理二进制树,但对于任何数据类型.

所以我可以传递一个自定义对象的类型,并有一个该对象的二叉树.这是如何在python中完成的?

mom*_*omo 113

其他答案完全没问题:

  • 不需要特殊的语法来支持 Python 中的泛型
  • 正如André所指出的,Python 使用鸭子类型。

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


通用类

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
Run Code Online (Sandbox Code Playgroud)
# Construct an empty Stack[int] instance
stack = Stack[int]()
stack.push(2)
stack.pop()
stack.push('x')        # Type error
Run Code Online (Sandbox Code Playgroud)

通用功能:

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.
Run Code Online (Sandbox Code Playgroud)

静态类型检查

您必须使用static type checker诸如mypy 之类的工具来分析您的源代码。

安装mypy:

python3 -m pip install mypy
Run Code Online (Sandbox Code Playgroud)

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

mypy foo.py
Run Code Online (Sandbox Code Playgroud)

或目录:

mypy some_directory
Run Code Online (Sandbox Code Playgroud)

mypy 将检测并打印类型错误。上面提供的 Stack 示例的具体输出:

foo.py:23: error: Argument 1 to "push" of "Stack" has incompatible type "str"; expected "int"
Run Code Online (Sandbox Code Playgroud)

参考资料:关于泛型运行 mypy 的mypy 文档

  • 绝对是这里最好的答案 (4认同)
  • 我运行了上面的堆栈代码,由于某种原因,在 stack.push("x") 上没有收到任何错误。这是为什么? (3认同)
  • @Sush因为如果您知道这一点,那么您现有的所有 abc.ABC 知识都适用于此处的 Stack 类。 (2认同)
  • @QuocAnhTran 我添加了一个新部分“静态类型检查”以进行进一步解释。 (2认同)
  • @cikatomo 我们能够编写 Stack[int] 是因为我们的 Stack 类继承自 `Generic[T]`,其中我们用 `[T]` 指定我们的 Stack 类采用单个类型参数。 (2认同)
  • @cikatomo 让我给你一个指示:[PEP 484 - 用户定义的泛型类型](https://www.python.org/dev/peps/pep-0484/#user-define-generic-types) (2认同)

And*_*ron 65

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

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

所以,基本上,它的工作方式相同:

  1. 为要在二叉树中插入的项目类型定义合同.
  2. 记录本合同(即在课堂文件中)
  3. 仅使用合同中指定的操作来实现二叉树
  4. 请享用

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

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

8bi*_*oey 18

实际上现在你可以在Python 3.5+中使用泛型.请参阅PEP-484键入库文档.

根据我的实践,它对于那些熟悉Java Generics但仍然可用的人来说并不是非常无缝和清晰.

  • 这似乎是仿制药tbh的廉价盗版。就像有人获得了仿制药一样,将它们放入搅拌机中,让它运行并忘了它,直到搅拌机电动机烧毁,然后两天后将其取出并说:“嘿,我们得到了仿制药”。 (4认同)
  • 这些是“类型提示”,它们与泛型无关。 (4认同)

小智 9

在想出了关于在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 
Run Code Online (Sandbox Code Playgroud)

您现在可以从此泛型类型派生类型.

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
Run Code Online (Sandbox Code Playgroud)

这个解决方案很简单,它确实有它的局限性.每次创建泛型类型时,它都会创建一个新类型.因此,List( str )作为父级继承的多个类将从两个单独的类继承.要克服这个问题,您需要创建一个dict来存储内部类的各种形式并返回先前创建的内部类,而不是创建一个新类.这样可以防止创建具有相同参数的重复类型.如果有兴趣,可以使用装饰器和/或元类来制作更优雅的解决方案.


Eri*_*ric 5

这是这个答案的一个变体,它使用元类来避免混乱的语法,并使用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]
        ))
Run Code Online (Sandbox Code Playgroud)

有了这个新的元类,我们可以将我链接到的答案中的示例重写为:

@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
Run Code Online (Sandbox Code Playgroud)

这种方法有一些好处

@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
Run Code Online (Sandbox Code Playgroud)