- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
我是一个大量使用继承的 Python 项目的维护者。有一个反模式给我们带来了一些问题并使阅读变得困难,我正在寻找解决它的好方法。
问题是将非常长的参数列表从派生类转发到基类——主要是但不总是在构造函数中。
考虑这个人为的例子:
class Base(object):
def __init__(self, a=1, b=2, c=3, d=4, e=5, f=6, g=7):
self.a = a
# etc
class DerivedA(Base):
def __init__(self, a=1, b=2, c=300, d=4, e=5, f=6, g=700, z=0):
super().__init__(a=a, b=b, c=c, d=d, e=e, f=f, g=g)
self.z = z
class DerivedB(Base):
def __init__(self, z=0, c=300, g=700, **kwds):
super().__init__(c=c, g=g, **kwds)
self.z = z
此时,一切看起来都像 DerivedA
- 长参数列表,所有这些都显式传递给基类。
不幸的是,过去几年我们遇到了一些问题,包括忘记传递参数并获取默认值,以及没有注意到派生类中的一个默认参数与默认值不同。
它还会使代码不必要地庞大,因此难以阅读。
DerivedB
更好并修复了这些问题,但有一个新问题,即派生类中方法的 Python 帮助/sphinx HTML 文档具有误导性,因为许多重要参数隐藏在**kwds
。
是否有某种方法可以将正确的签名(或至少是正确签名的文档)从基类方法“转发”到派生类方法?
最佳答案
我还没有找到一种方法来完美地创建具有相同签名的函数,但我认为我的实现的缺点并不太严重。我提出的解决方案是函数装饰器。
class Base(object):
def __init__(self, a=1, b=2, c=3, d=4, e=5, f=6, g=7):
self.a = a
# etc
class DerivedA(Base):
@copysig(Base.__init__)
def __init__(self, args, kwargs, z=0):
super().__init__(*args, **kwargs)
self.z = z
所有命名的继承参数将通过 kwargs
传递给函数字典。 args
参数仅用于将可变参数传递给函数。如果父函数没有可变参数,args
永远是一个空元组。
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
import inspect
def copysig(from_func, *args_to_remove):
def wrap(func):
#add and remove parameters
oldsig= inspect.signature(from_func)
oldsig= _remove_args(oldsig, args_to_remove)
newsig= _add_args(oldsig, func)
#write some code for a function that we can exec
#the function will have the correct signature and forward its arguments to the real function
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__]
#copy as many attributes as possible
newfunc.__doc__= func.__doc__
newfunc.__module__= func.__module__
#~ newfunc.__closure__= func.__closure__
#~ newfunc.__code__.co_filename= func.__code__.co_filename
#~ newfunc.__code__.co_firstlineno= func.__code__.co_firstlineno
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())
#check if the first parameter is "self"
selfarg= ''
if arglist:
arg= arglist[0]
if arg.name=='self':
selfarg= 'self, '
del arglist[0]
#all named parameters will be passed as kwargs. args is only used for varargs.
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:
#~ assert not args
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:#if varargs have been removed, there are no more keyword-only parameters
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)
#find out where we want to insert the new parameters
#parameters with a default value will be inserted before *args (if any)
#if parameters with a default value exist, parameters with no default value will be inserted as keyword-only AFTER *args
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)
#find the new parameters
#skip the first two parameters (args and kwargs)
newargs= list(funcsig.parameters.values())
if not newargs:
raise Exception('The decorated function must accept at least 2 parameters')
#if the first parameter is called "self", ignore the first 3 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:]
#add the new parameters
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:
#we can insert it as a positional parameter if it has a default value OR no other parameter has a default value
if arg.default!=arg.empty or not default_found:
#do NOT change the parameter kind here. Leave it as it was, so that the order of varargs and keyword-only parameters is preserved.
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 varargs need to be added, insert them before keyword-only arguments
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
s 它并返回动态创建的函数。
如果你不想“继承”参数x和y,使用
@copysig(parentfunc, 'x', 'y')
关于python - 完美转发——Python,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42471083/
我在正在构建的应用程序中集成了转发系统,但很难弄清楚在同一流中检索帖子和转发的逻辑。 我有一个 post_reblog 数据透视表来存储 post_id 和 user_id - 转发该帖子的用户的 I
好的,由于阅读了数百本手册和说明页但仍然没有得到它,我正处于某个时刻,我觉得自己非常愚蠢。我希望你能帮帮我! 我有一台运行 Ubuntu Server 的服务器。在服务器上,我正在运行 ddclien
我在我的项目中使用嵌入 jetty 并提出了一些问题。我有这两页: íindex.jsp 结果.jsp 这两个 servlet: 上传 搜索 在 íindex.jsp 中有一个用于上传文件的表单,上传
我正在使用 c# asp.net 创建一个基于 Web 的电子邮件客户端。 令人困惑的是,各种电子邮件客户端在回复电子邮件时似乎以多种不同方式添加原文。 我想知道的是,是否有某种标准化的方式来消除这个
我正在开发一个评估系统,让考生尝试参加考试。因此,呈现问题及其选择的页面是带有 iframe 的页面,在该页面中呈现问题。包含 iframe 的页面包含 JavaScript 计时器。 因此,当问题在
QOTD(每日问题)的 Twilio 新手。我认为这应该是非常基本的,但我似乎找不到答案。 我成功地将一个电话号码转发到我的手机上……很简单……但问题是我经营着几家公司,我想将号码转发到我的手机上。问
我正在尝试在 pod 上公开一个端口 8080,这样我就可以直接从服务器获取 wget。使用端口转发一切正常(kubectl --namespace jenkins port-forward pods
我想转发一个 url,这样如果你在地址栏中输入 www.example.com,你就会被转发到 www.test.com/test.php。 我所做的是在我的区域文件中添加了一个 cname 记录。
我正在尝试在构建 docker 镜像时克隆一个私有(private) github 存储库。我安装了 docker 18.09.2 并根据 Build secrets and SSH forwardi
Grails 2.2.0 我正在探索grails和ajax,也许我是一个狂热的ajax适配器,但是我确实认为它是一个改变游戏规则的人,所以我要走在前面。 数据模型是主要细节(1:n)。客户端中的一个表
Tumblr API似乎不支持帖子的某些细节:评论、转发或点赞的数量。 真的没有办法从每个帖子的 Tumblr API 获取这个吗? 最佳答案 tumblr API 在报告笔记的方式上非常有限。它可以
我正在制作一个greasemonkey脚本,我想要一个链接来前进并修改当前的html并允许用户单击返回以转到原始页面。 我该怎么做?使用jquery+greasemonkey+javascript。主
我的包含文件有一个小问题,我已经对我的问题做了一个简化的模型。假设我正在编译一些需要名为的头文件的源代码 header.h 里面有: #ifndef HEADER_INCLUDED #define H
我遇到了一个奇怪的问题。我有一个自定义组件来为我处理 UICollectionView 的布局。该代码是用 Swift 编写的。 class MyCustomCollectionViewHandler
有什么方法可以导出命令输出的颜色吗? 让我们用一个小例子来解释它: ls -alh --color=auto 将打印目录的彩色内容,而 ls -alh --color=auto | cat 不会打印一
我希望能够转发 url,例如 http://external_url.com/auth => http://internal_url.com:8080/app/auth https://externa
我有一个在 nginx 中混合运行 PHP 和 Tomcat 的域。这是它的样子。我在这个站点上有一个域 example.com 我安装了 Wordpress。这完全没有问题。现在我想要的是,当您导航
我在一种模板化的层次结构中有一堆相关的指标,看起来像 template struct index{ index w; int x, y; }; template <> struct
我之前发布了有关使用Processing与Leap Motion的文章https://www.leapmotion.com/为了构建一个可以检测手颤的应用程序。我相信我需要执行 FFT 才能实现此目的
Peer 必须能够转发数据,以便在点对点覆盖(例如 Chord)中进行广播。当每个节点(对等体)接收到数据时,它会将数据转发到其路由表中的所有其他节点,然后这些节点将再次转发相同的数据,直到环中的所有
我是一名优秀的程序员,十分优秀!