相互排斥的关键字args的优雅模式?

时间:2011-03-03 17:42:13

标签: python design-patterns coding-style

有时在我的代码中,我有一个函数可以用两种方式之一进行参数化。类似的东西:

def func(objname=None, objtype=None):
    if objname is not None and objtype is not None:
        raise ValueError("only 1 of the ways at a time")
    if objname is not None:
        obj = getObjByName(objname)
    elif objtype is not None:
        obj = getObjByType(objtype)
    else:
        raise ValueError("not given any of the ways")

    doStuffWithObj(obj)

还有更优雅的方法吗?如果arg可以采用三种方式之一怎么办?如果类型不同我可以做:

def func(objnameOrType):
    if type(objnameOrType) is str:
        getObjByName(objnameOrType)
    elif type(objnameOrType) is type:
        getObjByType(objnameOrType)
    else:
        raise ValueError("unk arg type: %s" % type(objnameOrType))

但如果他们不是呢?这种选择似乎很愚蠢:

def func(objnameOrType, isName=True):
    if isName:
        getObjByName(objnameOrType)
    else:
        getObjByType(objnameOrType)

因为你必须像func(mytype, isName=False)那样称呼它,这很奇怪。

7 个答案:

答案 0 :(得分:6)

如何使用命令调度模式之类的东西:

def funct(objnameOrType):
   dispatcher = {str: getObjByName,
                 type1: getObjByType1,
                 type2: getObjByType2}
   t = type(objnameOrType)
   obj = dispatcher[t](objnameOrType)
   doStuffWithObj(obj)

其中type1type2等是实际的python类型(例如int,float等)。

答案 1 :(得分:3)

使其略微缩短的一种方法是

def func(objname=None, objtype=None):
    if [objname, objtype].count(None) != 1:
        raise TypeError("Exactly 1 of the ways must be used.")
    if objname is not None:
        obj = getObjByName(objname)
    else: 
        obj = getObjByType(objtype)

我还没有决定是否称之为“优雅”。

请注意,如果给出错误的参数数量,则应提出TypeError,而不是ValueError

答案 2 :(得分:3)

听起来应该转到https://codereview.stackexchange.com/

无论如何,保持相同的界面,我可以尝试

arg_parsers = {
  'objname': getObjByName,
  'objtype': getObjByType,
  ...
}
def func(**kwargs):
  assert len(kwargs) == 1 # replace this with your favorite exception
  (argtypename, argval) = next(kwargs.items())
  obj = arg_parsers[argtypename](argval) 
  doStuffWithObj(obj)

或简单地创建2个函数?

def funcByName(name): ...
def funcByType(type_): ...

答案 3 :(得分:3)

无论价值多少,标准图书馆都会发生类似的事情;例如,请参阅gzip.py中GzipFile的开头(此处显示已删除docstrings):

class GzipFile:
    myfileobj = None
    max_read_chunk = 10 * 1024 * 1024   # 10Mb
    def __init__(self, filename=None, mode=None,
                 compresslevel=9, fileobj=None):
        if mode and 'b' not in mode:
            mode += 'b'
        if fileobj is None:
            fileobj = self.myfileobj = __builtin__.open(filename, mode or 'rb')
        if filename is None:
            if hasattr(fileobj, 'name'): filename = fileobj.name
            else: filename = ''
        if mode is None:
            if hasattr(fileobj, 'mode'): mode = fileobj.mode
            else: mode = 'rb'

当然,这会接受filenamefileobj关键字,并在收到两者时定义特定行为;但一般的方法似乎非常相同。

答案 4 :(得分:1)

我使用装饰者:

from functools import wraps

def one_of(kwarg_names):
    # assert that one and only one of the given kwarg names are passed to the decorated function
    def inner(f):
        @wraps(f)
        def wrapped(*args, **kwargs):
            count = 0
            for kw in kwargs:
                if kw in kwarg_names and kwargs[kw] is not None:
                    count += 1

            assert count == 1, f'exactly one of {kwarg_names} required, got {kwargs}'

            return f(*args, **kwargs)
        return wrapped
    return inner

用作:

@one_of(['kwarg1', 'kwarg2'])
def my_func(kwarg1='default', kwarg2='default'):
    pass

请注意,这仅考虑作为关键字参数传递的非None值。例如。如果除{1}之外的其他所有值都为kwarg_names,则仍可传递None的倍数。

为了允许传递任何一个kwargs只是断言计数是< = 1。

答案 5 :(得分:0)

听起来你正在寻找function overloading,这在Python 2中没有实现。在Python 2中,你的解决方案几乎和你期望的一样好。

您可以通过允许您的函数处理多个对象并返回一个生成器来绕过额外参数问题:

import types

all_types = set([getattr(types, t) for t in dir(types) if t.endswith('Type')])

def func(*args):
    for arg in args:
        if arg in all_types:
            yield getObjByType(arg)
        else:
            yield getObjByName(arg)

测试:

>>> getObjByName = lambda a: {'Name': a}
>>> getObjByType = lambda a: {'Type': a}
>>> list(func('IntType'))
[{'Name': 'IntType'}]
>>> list(func(types.IntType))
[{'Type': <type 'int'>}]

答案 6 :(得分:0)

内置sum()可用于布尔表达式列表。在Python中,boolint的子类,在算术运算中,True的行为为1,而False的行为为0。

这意味着这段相当短的代码将测试任意数量参数的互斥性:

def do_something(a=None, b=None, c=None):
    if sum([a is not None, b is not None, c is not None]) != 1:
        raise TypeError("specify exactly one of 'a', 'b', or 'c'")

变化也是可能的:

def do_something(a=None, b=None, c=None):
    if sum([a is not None, b is not None, c is not None]) > 1:
        raise TypeError("specify at most one of 'a', 'b', or 'c'")