ChatGPT解决这个技术问题 Extra ChatGPT

Generics/templates in 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?

python has duck templates

m
mkrieger1

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


Definitely the best answer here
@Sush Because if you know that, then all of your existing knowledge of abc.ABC is applicable to the Stack class here.
I ran the stack code above and didn't get any errors on stack.push("x") for some reason. Why is that?
@QuocAnhTran I added a new part "static type checking' for further explanation.
@cikatomo We are able to write Stack[int] because our Stack class inherits from Generic[T], where we specify with [T] that our Stack class takes a single type parameter.
A
André Caron

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:

define a contract for the type of items you want to insert in the binary tree. document this contract (i.e. in the class documentation) implement the binary tree using only operations specified in the contract 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.


André, I'd like to understand why explicit type checking is normally discouraged in Python. I'm confused because it would seem to be with a dynamically typed language, we could get in alot of trouble if we can't guarantee the possible types that will come into the function. But, then again, I'm very new to Python. :-)
@ScottEdwards2000 You can have implicit type checking with type hints in PEP 484 and a type checker
In the Python purist's perspective, Python is a dynamic language and duck-typing is the paradigm; i.e., type-safety is ruled 'non-Pythonic'. This is something that was difficult for me to find acceptable - for a while - as I am heavily vested in C#. On one hand, I find type-safety a necessity. As I have balanced the scales between the .Net world and the Pythonic paradigm, I have accepted that type-safety is really a pacifier and if I need to, all I have to do is if isintance(o, t): or if not isinstance(o, t): ... pretty simple.
Thanks commenters, great answers. I realized after reading them that i really just want type checking to catch my own errors. So i will just use implicit type checking.
I think many Pythonists miss the point about this - generics are a way to provide freedom and safety at the same time. Even leaving aside generics & just using typed parameters, the function writer knows that they can modify their code to use any method the class provides; with duck typing if you start using a method you didn't use before, you suddenly changed the definition of the duck, and things will probably break.
C
Community

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.


That looks like a cheap rip-off of generics tbh. It's like someone got generics, put them in a blender, let it run and forgot about it until the blender motor burnt out, and then took it out 2 days later and said: "hey we got generics".
Those are "type hints", they have nothing to do with generics.
Same in typescript but there it works like in Java (syntactically). Generics in these languages are just type hints
Y
Yevhen Kuzmovych

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.


Can you elaborate on how the dict can be used in the above example? Do you have a snippet for that either in git or something? Thank you..
I don't have an example, and it could be a bit time consuming right now. However, the principles are not that difficult. The dict acts as cache. When the new class is create, it needs to look at the type parameters to create an identifier for that type and parameter configuration. Then it can use it as a key in a dict to lookup the previously existing class. This way, it will use that one class over and over again.
Thanks for the inspiration - see my answer for an extension of this technique with metaclasses
L
Leopd

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.


On the contrary: all data types are objects in Python. They do not need to be wrapped (as in Java with Integer boxing/unboxing).
E
Eric

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

F
Florian Steenbuck

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

Add super support

Add ? support

Code Clean Up


J
John Zwinck

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.


i
igauravsehrawat

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