方法重构:从许多kwargs到一个arg-object

时间:2014-03-17 08:52:44

标签: python design-patterns refactoring

有时,方法的kwargs数量会增加到我认为应该重构的水平。

示例:

def foo(important=False, debug=False, dry_run=False, ...):
    ....
    sub_foo(important=imporant, debug=debug, dry_run=dry_run, ...)

我目前首选的解决方案:

class Args(object):
    ...

def foo(args):
    sub_foo(args)

第一个问题:如何致电Args?是否有众所周知的描述或设计模式?

第二个问题:Python是否有一些我可以用作Args的基类?

更新

我从13年开始每天使用Python工作。我使用了许多kwargs的方法,并用许多kwargs编写了方法。在过去的几周里,读了一本书“干净的代码”,我喜欢它。不知怎的,就像现在戴着另一副眼镜一样。我的旧代码有效,但看起来并不好。将长方法拆分成几种较小的方法很容易。但我不知道如何处理kwargs-bloat的方法。

7 个答案:

答案 0 :(得分:3)

我认为你所描述的是" Context"设计模式。

我通常会打电话给你的" Args" a"上下文" (或者" FooContext"如果它具有足够的foo特性)。

我认为我看到的最佳解释是:http://accu.org/index.php/journals/246(" The Encapsulate Context Pattern",Allen Kelly在Overload Journal#63 - 2004年10月,我从另一个SO回答中看到:https://stackoverflow.com/a/9458244/3427357)。

如果你想进行深入探索,还有一些不错的论文可以进一步阐述: http://www.two-sdg.demon.co.uk/curbralan/papers/europlop/ContextEncapsulation.pdf https://www.dre.vanderbilt.edu/~schmidt/PDF/Context-Object-Pattern.pdf

正如另一个SO答案(https://stackoverflow.com/a/1135454/3427357)所指出的那样,某些人认为上下文模式是危险的(c.f。http://misko.hevery.com/2008/07/18/breaking-the-law-of-demeter-is-like-looking-for-a-needle-in-the-haystack/)。

但我认为德米特的法律和#34;警告并不会使你的早期设计过于复杂,而不是清理在你解决其他问题时意外增长的残骸。如果您通过了重要的" boolean通过多个函数调用层你已经开始测试地狱了,在那种情况下,你所描述的重构通常是我的经验中的纯粹胜利。

我不认为这是python中的标准基类,除非你可能懒得将argparse.Namespace作为你的上下文对象传递,因为你已经有了你的参数值那里。

答案 1 :(得分:1)

def foo(*args, **kwargs):
    sub_foo(*args, **kwargs)

答案 2 :(得分:0)

最好是使用内省来调用子函数。

您只需要一种方法来获取有关该功能的信息。你可以这样做:

def passthru(func):
    l = inspect.stack()[1][0].f_locals
    args = inspect.getargspec(func)[0]
    kwargs = dict((x, l[x]) for x in args if x in l)
    return func(**kwargs)

def f(x=1, y=2):
    print x,y

def g(x=4):
    passthru(f)

f()
1 2

g()
4 2

g(6)
6 2
但是,它似乎有一些开销。

答案 3 :(得分:0)

我不确定你在寻找什么,所以也许编辑添加一些额外的信息可能会有所帮助(例如,干净的代码是什么意思,为什么不* args,** kwargs满足那,你想要完成的最终目标是什么,等等。

我会抛出一个未提及的额外想法。您可以使用**

创建一个字典并将其作为关键字参数传递
def foo(important=False, debug=False, dry_run=False):
    print important, debug, dry_run

args = dict()
args['important'] = True
args['debug'] = True
args['dry_run'] = False
foo(**args)

或者既然你想要涉及OOP,你或许可以使用一个对象。

class Args(object):
    pass

def foo(important=False, debug=False, dry_run=False):
    print important, debug, dry_run

args = Args()
args.important = True
args.debug = True
args.dry_run = False
foo(**args.__dict__)

答案 4 :(得分:0)

我不明白为什么你会这样做。通常,如果一个方法有很多参数,问题是方法做得太多,而不是你需要将参数包装在某个对象中。如果您只想传递参数,可以使用**kwargs

那就是说,如果你有一些奇怪的用例并且真的需要这个,你可以使用NamedTuple

def foo(a=1, b=2, c=3, d=4, e=5, f=6, g=7): # kwarg way
    do_things(a, 7, b, 12, c, 3, d, e, f, g) # or whatever

FooArgs = collections.namedtuple('FooArgs', ['a', 'b', 'c', 'd', 'e', 'f', 'g'])
foo_args = FooArgs(1, 2, 3, 4, 5, 6, 7)
foo_args.a # 1
foo_args.e # 5

def foo(args): # namedtuple way
    do_things(args.a, 7, args.b, 12, args.c, 3, args.d, args.e, args.f, args.g)

答案 5 :(得分:0)

我看到了几条出路:

自动,例如线程本地存储或可以从中获取这些值的其他上下文。网络框架通常遵循这一点,例如在这里https://stackoverflow.com/a/19484699/705086我觉得这个pythonic最多,从某种意义上来说它更容易阅读。称之为穷人的面向上下文的编程。它类似于直接访问sys.argv,但更精确。

最适合横切关注点,授权,日志记录,使用限制,重试......

collections.namedtuple 如果经常重复相同的参数集或者此类的多个实例很常见,尤其有用,例如:

job = collections.namedtuple("job", "id started foo bar")
todo = [job(record) for record in db.select(…)]
传递意外关键字参数时,

**kwargs ,匿名,容易出错。

self ,如果你继续将参数从一个函数传递到下一个函数,也许这些应该是类/对象成员

您还可以在示例中混合和匹配这些:

  • 调试⇾自动化背景
  • dry_run⇾自动化背景
  • 重要⇾保留一个名为kwarg的explicit is better than implicit

答案 6 :(得分:0)

我相信您只是浪费时间并使代码更复杂。作为一名Python开发人员,我宁愿看到一个带有20个参数的函数,而不是一个带有复杂Args对象的函数。