python 中的泛型/模板?

发布于 2024-11-24 02:21:45 字数 132 浏览 3 评论 0原文

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

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

How does python handle generic/template type scenarios? Say I want to create an external file "BinaryTree.py" and have it handle binary trees, but for any data type.

So I could pass it the type of a custom object and have a binary tree of that object. How is this done in python?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

夕嗳→ 2024-12-01 02:21:45

其他答案完全没问题:

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

但是,如果您仍然想要类型化变体,自 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.

静态类型检查

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

参考:有关 泛型运行 mypy

The other answers are totally fine:

  • One does not need a special syntax to support generics in Python
  • Python uses duck typing as pointed out by André.

However, if you still want a typed variant, there is a built-in solution since Python 3.5.

A full list of available type annotations is available in the Python documentation.


Generic classes:

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

Generic functions:

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.

Static type checking:

You must use a static type checker such as mypy or Pyre (developed by Meta/FB) to analyze your source code.

Install mypy:

python3 -m pip install mypy

Analyze your source code, for example a certain file:

mypy foo.py

or directory:

mypy some_directory

mypy will detect and print type errors. A concrete output for the Stack example provided above:

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

References: mypy documentation about generics and running mypy

懒的傷心 2024-12-01 02:21:45

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

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

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

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

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

Python uses duck typing, so it doesn't need special syntax to handle multiple types.

If you're from a C++ background, you'll remember that, as long as the operations used in the template function/class are defined on some type T (at the syntax level), you can use that type T in the template.

So, basically, it works the same way:

  1. define a contract for the type of items you want to insert in the binary tree.
  2. document this contract (i.e. in the class documentation)
  3. implement the binary tree using only operations specified in the contract
  4. enjoy

You'll note however, that unless you write explicit type checking (which is usually discouraged), you won't be able to enforce that a binary tree contains only elements of the chosen type.

要走干脆点 2024-12-01 02:21:45

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

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

Actually now you can use generics in Python 3.5+.
See PEP-484 and typing module documentation.

According to my practice it is not very seamless and clear especially for those who are familiar with Java Generics, but still usable.

温柔一刀 2024-12-01 02:21:45

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

After coming up with some good thoughts on making generic types in python, I started looking for others who had the same idea, but I couldn't find any. So, here it is. I tried this out and it works well. It allows us to parameterize our types in 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 

You can now derive types from this generic type.

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

This solution is simplistic, and it does have it's limitations. Each time you create a generic type, it will create a new type. Thus, multiple classes inheriting List( str ) as a parent would be inheriting from two separate classes. To overcome this, you need to create a dict to store the various forms of the inner class and return the previous created inner class, rather than creating a new one. This would prevent duplicate types with the same parameters from being created. If interested, a more elegant solution can be made with decorators and/or metaclasses.

妄想挽回 2024-12-01 02:21:45

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

It is now possible to use generic types and type aliases starting from Python 3.12! (released today!)

Following PEP-695, one can now write code like:

def max[T](args: Iterable[T]) -> T:
    ...

class list[T]:
    def __getitem__(self, index: int, /) -> T:
        ...

    def append(self, element: T) -> None:
        ...

Type aliasses also work now! Even using generics!

type Point = tuple[float, float]

Last example taken from the official guidelines:

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
信仰 2024-12-01 02:21:45

这是这个答案的一个变体,它使用元类来避免混乱的语法,并使用打字 -style 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

Here's a variant of this answer that uses metaclasses to avoid the messy syntax, and use the typing-style List[int] syntax:

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]
        ))

With this new metaclass, we can rewrite the example in the answer I link to as:

@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

This approach has some nice benefits

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
话少情深 2024-12-01 02:21:45

由于 python 是动态类型的,所以这非常简单。事实上,您必须为 BinaryTree 类做额外的工作,才能不使用任何数据类型。

例如,如果您希望通过 key() 之类的方法将用于将对象放置在树中的键值在对象内可用,您只需调用 key()在物体上。例如:

class BinaryTree(object):

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

请注意,您永远不需要定义 object_to_insert 是什么类型的类。只要它有 key() 方法,它就可以工作。

例外情况是,如果您希望它能够处理字符串或整数等基本数据类型。您必须将它们包装在一个类中才能使它们与您的通用二叉树一起使用。如果这听起来太重了,并且您想要实际上仅存储字符串的额外效率,那么抱歉,这不是 Python 所擅长的。

Since python is dynamically typed, this is super easy. In fact, you'd have to do extra work for your BinaryTree class not to work with any data type.

For example, if you want the key values which are used to place the object in the tree available within the object from a method like key() you just call key() on the objects. For example:

class BinaryTree(object):

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

Note that you never need to define what kind of class object_to_insert is. So long as it has a key() method, it will work.

The exception is if you want it to work with basic data types like strings or integers. You'll have to wrap them in a class to get them to work with your generic BinaryTree. If that sounds too heavy weight and you want the extra efficiency of actually just storing strings, sorry, that's not what Python is good at.

只为守护你 2024-12-01 02:21:45

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

Look at how the built-in containers do it. dict and list and so on contain heterogeneous elements of whatever types you like. If you define, say, an insert(val) function for your tree, it will at some point do something like node.value = val and Python will take care of the rest.

霓裳挽歌倾城醉 2024-12-01 02:21:45

如果你使用Python 2或者想重写java代码。他们并不是真正的解决方案。这是我一晚上的工作内容: https://github.com/FlorianSteenbuck/python-generics< /a> 我仍然没有编译器,所以你目前使用它是这样的:

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)

TODOs

  • 编译器
  • 获取通用类和类型工作(对于像 >)
  • 添加super 支持
  • 添加? 支持
  • 代码清理

If you using Python 2 or want to rewrite java code. Their is not real solution for this. Here is what I get working in a night: https://github.com/FlorianSteenbuck/python-generics I still get no compiler so you currently using it like that:

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)

TODOs

  • Compiler
  • Get Generic Classes and Types working (For things like <? extends List<Number>>)
  • Add super support
  • Add ? support
  • Code Clean Up
々眼睛长脚气 2024-12-01 02:21:45

幸运的是,Python 中的通用编程已经做出了一些努力。
有一个库: generic

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

多年来没有任何进展,但您可以大致了解如何使用 &创建你自己的图书馆。

干杯

Fortunately there has been some efforts for the generic programming in python .
There is a library : generic

Here is the documentation for it: http://generic.readthedocs.org/en/latest/

It hasn't progress over years , but you can have a rough idea how to use & make your own library.

Cheers

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文