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