Elegant template for mutually exclusive keywords?

Sometimes in my code there is a function that can take an argument in one of two ways. Something like:

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) 

Is there a more elegant way to do this? What if arg can come in one of three ways? If the types are different, I could do:

 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)) 

But what if it is not? This alternative seems silly:

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

then you should call it like func(mytype, isName=False) which is weird.

+6
python design coding-style design-patterns
source share
5 answers

How to use something like a command manager template:

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

where type1 , type2 , etc. are valid python types (e.g. int, float, etc.).

+6
source share

It looks like it should go to https://codereview.stackexchange.com/

Anyway, keeping the same interface, I can try

 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) 

or just create 2 functions?

 def funcByName(name): ... def funcByType(type_): ... 
+3
source share

One way to make it a little shorter:

 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) 

I have not decided yet whether I will call it "elegant."

Note that you must raise a TypeError if the wrong number of arguments was specified, not a ValueError .

+1
source share

Whatever the cost, in Standard Libraries similar things happen; see, for example, the beginning of GzipFile in gzip.py (shown here with docstrings removed):

 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' 

Of course, this allows both the filename and fileobj and determines the specific behavior if it gets both; but the general approach seems almost identical.

+1
source share

It looks like you're looking for a <function overload that is not implemented in Python 2. In Python 2, your solution is almost as you might expect.

You can probably get around the problem of extra arguments by allowing your function to process multiple objects and return a generator:

 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) 

Test:

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

All Articles