我还没有找到一种完美地创建具有相同签名的函数的方法,但我认为我的实现缺点并不太严重。我想出的解决方案是一个函数装饰器。
用法示例:
class Base(object):
def __init__(self, a=1, b=2, c=3, d=4, e=5, f=6, g=7):
self.a = a
class DerivedA(Base):
@copysig(Base.__init__)
def __init__(self, args, kwargs, z=0):
super().__init__(*args, **kwargs)
self.z = z
所有继承的命名参数都将通过
kwargs
字典传递给函数。
args
参数仅用于将varargs传递给函数。 如果父函数没有varargs,则
args
始终为空元组。
已知问题和限制:
- 不适用于python2!(你为什么还在使用python 2?)
- 未能完美保留装饰函数的所有属性。例如,
function.__code__.co_filename
将被设置为"<string>"
。
如果装饰的函数抛出异常,则异常回溯中将会显示一个额外的函数调用,例如:
>>> f2()
Traceback (most recent call last):
File "", line 1, in
File "", line 3, in f2
File "untitled.py", line 178, in f2
raise ValueError()
ValueError
- 如果装饰的是一个方法,第一个参数必须被称为"self"。
实现
import inspect
def copysig(from_func, *args_to_remove):
def wrap(func):
oldsig= inspect.signature(from_func)
oldsig= _remove_args(oldsig, args_to_remove)
newsig= _add_args(oldsig, func)
code= '''
def {name}{signature}:
{func}({args})
'''.format(name=func.__name__,
signature=newsig,
func='_'+func.__name__,
args=_forward_args(oldsig, newsig))
globs= {'_'+func.__name__: func}
exec(code, globs)
newfunc= globs[func.__name__]
newfunc.__doc__= func.__doc__
newfunc.__module__= func.__module__
return newfunc
return wrap
def _collectargs(sig):
"""
Writes code that gathers all parameters into "self" (if present), "args" and "kwargs"
"""
arglist= list(sig.parameters.values())
selfarg= ''
if arglist:
arg= arglist[0]
if arg.name=='self':
selfarg= 'self, '
del arglist[0]
args= 'tuple(), '
kwargs= ''
kwarg= ''
for arg in arglist:
if arg.kind in (arg.POSITIONAL_ONLY,arg.POSITIONAL_OR_KEYWORD,arg.KEYWORD_ONLY):
kwargs+= '("{0}",{0}), '.format(arg.name)
elif arg.kind==arg.VAR_POSITIONAL:
args= arg.name+', '
elif arg.kind==arg.VAR_KEYWORD:
assert not kwarg
kwarg= 'list({}.items())+'.format(arg.name)
else:
assert False, arg.kind
kwargs= 'dict({}[{}])'.format(kwarg, kwargs[:-2])
return '{}{}{}'.format(selfarg, args, kwargs)
def _forward_args(args_to_collect, sig):
collect= _collectargs(args_to_collect)
collected= {arg.name for arg in args_to_collect.parameters.values()}
args= ''
for arg in sig.parameters.values():
if arg.name in collected:
continue
if arg.kind==arg.VAR_POSITIONAL:
args+= '*{}, '.format(arg.name)
elif arg.kind==arg.VAR_KEYWORD:
args+= '**{}, '.format(arg.name)
else:
args+= '{0}={0}, '.format(arg.name)
args= args[:-2]
code= '{}, {}'.format(collect, args) if args else collect
return code
def _remove_args(signature, args_to_remove):
"""
Removes named parameters from a signature.
"""
args_to_remove= set(args_to_remove)
varargs_removed= False
args= []
for arg in signature.parameters.values():
if arg.name in args_to_remove:
if arg.kind==arg.VAR_POSITIONAL:
varargs_removed= True
continue
if varargs_removed and arg.kind==arg.KEYWORD_ONLY:
arg= arg.replace(kind=arg.POSITIONAL_OR_KEYWORD)
args.append(arg)
return signature.replace(parameters=args)
def _add_args(sig, func):
"""
Merges a signature and a function into a signature that accepts ALL the parameters.
"""
funcsig= inspect.signature(func)
vararg= None
kwarg= None
insert_index_default= None
insert_index_nodefault= None
default_found= False
args= list(sig.parameters.values())
for index,arg in enumerate(args):
if arg.kind==arg.VAR_POSITIONAL:
vararg= arg
insert_index_default= index
if default_found:
insert_index_nodefault= index+1
else:
insert_index_nodefault= index
elif arg.kind==arg.VAR_KEYWORD:
kwarg= arg
if insert_index_default is None:
insert_index_default= insert_index_nodefault= index
else:
if arg.default!=arg.empty:
default_found= True
if insert_index_default is None:
insert_index_default= insert_index_nodefault= len(args)
newargs= list(funcsig.parameters.values())
if not newargs:
raise Exception('The decorated function must accept at least 2 parameters')
if newargs[0].name=='self':
del newargs[0]
if len(newargs)<2:
raise Exception('The decorated function must accept at least 2 parameters')
newargs= newargs[2:]
if newargs:
new_vararg= None
for arg in newargs:
if arg.kind==arg.VAR_POSITIONAL:
if vararg is None:
new_vararg= arg
else:
raise Exception('Cannot add varargs to a function that already has varargs')
elif arg.kind==arg.VAR_KEYWORD:
if kwarg is None:
args.append(arg)
else:
raise Exception('Cannot add kwargs to a function that already has kwargs')
else:
if arg.default!=arg.empty or not default_found:
args.insert(insert_index_default, arg)
insert_index_nodefault+= 1
insert_index_default+= 1
else:
arg= arg.replace(kind=arg.KEYWORD_ONLY)
args.insert(insert_index_nodefault, arg)
if insert_index_default==insert_index_nodefault:
insert_index_default+= 1
insert_index_nodefault+= 1
if new_vararg is not None:
for i,arg in enumerate(args):
if arg.kind not in (arg.POSITIONAL_ONLY,arg.POSITIONAL_OR_KEYWORD):
break
else:
i+= 1
args.insert(i, new_vararg)
return inspect.Signature(args, return_annotation=funcsig.return_annotation)
简要说明:
装饰器会创建一种形式的字符串
def functionname(arg1, arg2, ...):
real_function((arg1, arg2), {'arg3':arg3, 'arg4':arg4}, z=z)
然后用exec
执行它,并返回动态创建的函数。
额外功能:
如果你不想“继承”参数 x 和 y,请使用
@copysig(parentfunc, 'x', 'y')
eval
让我感觉有点不舒服 :-D 但我看不出有什么其他方法可以解决它。 - Tom Swirlyboltons.funcutils
,但我认为没有任何东西可以帮助我。(至少不是显著的)。实际上并没有任何工具可以帮助从/向签名中删除/添加参数。 - Aran-Fey