key*_*eys 62 python templates generic-programming
python如何处理通用/模板类型场景?假设我想创建一个外部文件"BinaryTree.py"并让它处理二进制树,但对于任何数据类型.
所以我可以传递一个自定义对象的类型,并有一个该对象的二叉树.这是如何在python中完成的?
mom*_*omo 113
其他答案完全没问题:
但是,如果您仍然想要一个类型化的变体,那么从 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)
And*_*ron 65
Python使用duck typing,因此它不需要特殊的语法来处理多种类型.
如果您来自C++背景,您将会记住,只要模板函数/类中使用的操作是在某种类型T(在语法级别)定义的,您就可以T在模板中使用该类型.
所以,基本上,它的工作方式相同:
但是,您会注意到,除非您编写显式类型检查(通常不鼓励),否则您将无法强制二叉树仅包含所选类型的元素.
小智 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来存储内部类的各种形式并返回先前创建的内部类,而不是创建一个新类.这样可以防止创建具有相同参数的重复类型.如果有兴趣,可以使用装饰器和/或元类来制作更优雅的解决方案.
这是这个答案的一个变体,它使用元类来避免混乱的语法,并使用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)