关于python:互斥关键字args的优雅模式?

Elegant pattern for mutually exclusive keyword args?

有时在我的代码中,我有一个函数,它可以用两种方法之一接受参数。类似:

1
2
3
4
5
6
7
8
9
10
11
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可以通过三种方式之一来实现呢?如果类型不同,我可以这样做:

1
2
3
4
5
6
7
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))

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

1
2
3
4
5
def func(objnameOrType, isName=True):
    if isName:
        getObjByName(objnameOrType)
    else:
        getObjByType(objnameOrType)

因为你必须把它叫做func(mytype, isName=False),这很奇怪。


如何使用的东西像一个指挥调度模式:

1
2
3
4
5
6
7
def funct(objnameOrType):
   dispatcher = {str: getObjByName,
                 type1: getObjByType1,
                 type2: getObjByType2}
   t = type(objnameOrType)
   obj = dispatcher[t](objnameOrType)
   doStuffWithObj(obj)

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


任何值得研究的东西,一种类似于标准库;湖泊,例如,开始在大学gzipfile(显示在一起gzip.py代码删除):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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'

当然,这两个filename接受和fileobj关键字和定义特定的行为案例,它接收两个将军;但似乎相当多相同的方法。


听起来像它应该去codereview.stackexchange.com https:///

无论如何,保持相同的接口,我可以试试

1
2
3
4
5
6
7
8
9
10
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?

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


单让它稍短。

1
2
3
4
5
6
7
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)

我还没有决定,如果我想调用这个"雅"。

注意,你应该加注A TypeError如果没有什么错误的数量的参数,A ValueError困境。


A:我用鱼鳞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

可作为:

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

注意,这个非营利None只读帐户是作为关键字参数传递的值。例如,可能是多个kwarg_names安静但所有的帽子,如果他们中的一个有价值的None

为不让传递的简单的断言,是kwargs count<=1。


酒店建在一个可以用sum()on a list of布尔表达式。在Python中,一个int收藏指正bool),和算术操作,Truebehaves Falsebehaves为1和0。

这意味着我要在短码数的相互exclusivity任何测试参数:

1
2
3
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'")

的变化是可能的。

1
2
3
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'")


它听起来像是你要找的函数重载,这不是Python实现,2。Python中的解决方案是接近2,你认为你可以得到良好。

你可以绕过额外的问题可能让你的论点是由函数发生器和多对象返回a的流程:

1
2
3
4
5
6
7
8
9
10
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)

测试:

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