python中不同参数类型的方法重载
我正在用python编写一个预处理器,其中一部分与AST一起工作 有一个python中不同参数类型的方法重载,python,oop,design-patterns,Python,Oop,Design Patterns,我正在用python编写一个预处理器,其中一部分与AST一起工作 有一个render()方法负责将各种语句转换为源代码 现在,我有这样的(缩短): 正如您所看到的,它很乏味,容易出错,而且代码很长(我有更多种类的语句) 理想的解决方案是(用Java语法): 当然,python不能这样做,因为它有动态类型。当我搜索如何模拟方法重载时,所有的答案都是“你不想在python中这样做”。我想这在某些情况下是正确的,但是这里的kwargs实际上一点用处都没有 如果没有可怕的公里长的if类型检查ifs序列,
render()
方法负责将各种语句转换为源代码
现在,我有这样的(缩短):
正如您所看到的,它很乏味,容易出错,而且代码很长(我有更多种类的语句)
理想的解决方案是(用Java语法):
当然,python不能这样做,因为它有动态类型。当我搜索如何模拟方法重载时,所有的答案都是“你不想在python中这样做”。我想这在某些情况下是正确的,但是这里的kwargs
实际上一点用处都没有
如果没有可怕的公里长的if类型检查ifs序列,我将如何在python中实现这一点,如上图所示?还有,最好是一种“蟒蛇式”的方式
注意:可以有多个“呈现器”实现,它们以不同的方式呈现语句。因此,我无法将呈现代码移动到语句中,只需调用s.render()
。它必须在renderer类中完成
(我找到了一些,但我不确定它是否真的是我想要的东西)。有什么东西喜欢这项工作吗
self.map = {
S_Block : self._render_block,
S_Empty : self._render_empty,
S_Function: self._render_function
}
def render(self, s):
return self.map[type(s)](s)
在字典中保留对类对象的引用作为键,并将其值作为要调用的函数对象,这将使代码更短,更不容易出错。这里唯一可能发生错误的地方是字典的定义。当然也可以是您的一个内部函数。您正在寻找的重载语法可以通过使用来实现 这里是multimemethoddecorator的一个变体,它可以修饰类方法(原始的修饰普通函数)。我将变体命名为
multidispatch
,以消除其与原始版本的歧义:
import functools
def multidispatch(*types):
def register(function):
name = function.__name__
mm = multidispatch.registry.get(name)
if mm is None:
@functools.wraps(function)
def wrapper(self, *args):
types = tuple(arg.__class__ for arg in args)
function = wrapper.typemap.get(types)
if function is None:
raise TypeError("no match")
return function(self, *args)
wrapper.typemap = {}
mm = multidispatch.registry[name] = wrapper
if types in mm.typemap:
raise TypeError("duplicate registration")
mm.typemap[types] = function
return mm
return register
multidispatch.registry = {}
它可以这样使用:
class Foo(object):
@multidispatch(str)
def render(self, s):
print('string: {}'.format(s))
@multidispatch(float)
def render(self, s):
print('float: {}'.format(s))
@multidispatch(float, int)
def render(self, s, t):
print('float, int: {}, {}'.format(s, t))
foo = Foo()
foo.render('text')
# string: text
foo.render(1.234)
# float: 1.234
foo.render(1.234, 2)
# float, int: 1.234, 2
上面的演示代码显示了如何根据参数类型重载Foo.render
方法
此代码搜索精确的匹配类型,而不是检查
isinstance
关系。可以修改它来处理这个问题(以查找O(n)而不是O(1)为代价),但因为听起来您似乎并不需要它,所以我将以这种更简单的形式保留代码。如果您使用的是Python 3.4(或者愿意安装for Python 2.6+),那么您可以使用它*:
from functools import singledispatch
class S_Block(object): pass
class S_Empty(object): pass
class S_Function(object): pass
class Test(object):
def __init__(self):
self.render = singledispatch(self.render)
self.render.register(S_Block, self._render_block)
self.render.register(S_Empty, self._render_empty)
self.render.register(S_Function, self._render_function)
def render(self, s):
raise TypeError("This type isn't supported: {}".format(type(s)))
def _render_block(self, s):
print("render block")
def _render_empty(self, s):
print("render empty")
def _render_function(self, s):
print("render function")
if __name__ == "__main__":
t = Test()
b = S_Block()
f = S_Function()
e = S_Empty()
t.render(b)
t.render(f)
t.render(e)
输出:
render block
render function
render empty
*基于的代码,在@unutbu的答案中添加一些性能度量:
@multimethod(float)
def foo(bar: float) -> int:
return 'float: {}'.format(bar)
def foo_simple(bar):
return 'string: {}'.format(bar)
返回:
> multimethod: 16.846999883651733
> standard: 4.509999990463257
一种替代实现,使用中定义的装饰器:
从functools导入singledispatch
类别S_未知:通过
等级S_块:通过
类S_空:通过
类S_函数:pass
类S_特殊块(S_块):通过
@单发
def渲染,**kwargs:
打印('呈现未知类型')
@渲染寄存器(S_块)
定义(单位:**kwargs):
打印('呈现S_块')
@render.register(S_为空)
定义(单位:**kwargs):
打印('呈现S_为空')
@render.register(S_函数)
定义(单位:**kwargs):
打印(“呈现S_函数”)
如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
对于t in[S_未知、S_块、S_空、S_函数、S_特殊块]:
打印(f'传递一个{t.\uuuuuu name\uuuuuuu}')
render(t())
这个输出
传递S_未知
呈现未知类型
通过S_街区
渲染S_块
通过一个空的S_
将S_渲染为空
传递S_函数
呈现S_函数
通过S_特殊块
渲染S_块
我更喜欢这个版本,因为它与使用isinstance()
的实现具有相同的行为:当您传递S_SpecialBlock时,它会将其传递给接受S_块的渲染器
可利用性
正如dano在中提到的,这在Python3.4+中起作用,Python2.6+中有一个新的版本
如果您有Python 3.7+,register()
属性支持使用类型注释:
@render.register
定义(s:s_块,**kwargs):
打印('呈现S_块')
注
我看到的一个问题是,必须将s
作为位置参数传递,这意味着不能进行渲染(s=s_Block())
由于single\u dispatch
使用第一个参数的类型来确定要调用哪个版本的render()
,这将导致类型错误-“render至少需要一个位置参数”(cf)
实际上,我认为如果只有一个关键字参数,应该可以使用关键字参数。。。如果你真的需要它,那么你可以做类似的事情,用不同的包装创建一个定制的装饰器。
这也是Python的一个很好的特性。
isinstance
为子类以及类本身返回true。你不能用映射来匹配语义。@Joowani Great minds之类的东西。唯一需要考虑的是,在我的实现中,字典是静态的,将在类的所有实例中使用。每次使用函数时都必须实例化字典。@roippi我从未使用过isinstance?编辑:哦,我现在明白你的意思了。我没有考虑过这一点,但这似乎不是op所需要的。有趣的方法,而且@roippi在这里真的不是问题,我总是匹配层次结构中“最深的孩子”,例如s_If,而不是s_BranchingState(如果我有)。如果有更好的东西出现,我会等一段时间,但这看起来确实是一个很好的技术(不完美,但至少不那么冗长)。我接受了这一点,因为它最适合我的项目,而且最容易理解。但是,一般来说,其他答案可能更好。请查看以下答案。从技术上讲,这是一个复制品,但它收到了一些不同的答案,所以我犹豫是否标记它。
@multimethod(float)
def foo(bar: float) -> int:
return 'float: {}'.format(bar)
def foo_simple(bar):
return 'string: {}'.format(bar)
import time
string_type = "test"
iterations = 10000000
start_time1 = time.time()
for i in range(iterations):
foo(string_type)
end_time1 = time.time() - start_time1
start_time2 = time.time()
for i in range(iterations):
foo_simple(string_type)
end_time2 = time.time() - start_time2
print("multimethod: " + str(end_time1))
print("standard: " + str(end_time2))
> multimethod: 16.846999883651733
> standard: 4.509999990463257