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) |
因为你必须把它叫做
如何使用的东西像一个指挥调度模式:
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) |
在
任何值得研究的东西,一种类似于标准库;湖泊,例如,开始在大学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' |
当然,这两个
听起来像它应该去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
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 |
注意,这个非营利
为不让传递的简单的断言,是kwargs count<=1。
酒店建在一个可以用
这意味着我要在短码数的相互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'>}] |