优雅的互斥关键字参数模式?

15 投票
8 回答
5845 浏览
提问于 2025-04-16 12:56

有时候在我的代码里,有一个函数可以用两种方式来接收参数。就像这样:

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)

有没有更优雅的方法来实现这个?如果这个参数可以有三种不同的方式呢?如果这些类型是完全不同的,我可以这样做:

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),这听起来有点奇怪。

8 个回答

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 错误。

3

无论如何,类似的事情在标准库中也会发生;比如,在gzip.py文件中,GzipFile的开头就有这样的情况(这里去掉了文档字符串):

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这两个参数,并且如果同时收到这两个参数时,会有特定的处理方式;不过总体的方法看起来几乎是一样的。

9

你可以考虑使用一种叫做命令分发模式的东西:

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

这里的 type1type2 等等,实际上是指Python中的数据类型,比如整数(int)、浮点数(float)等等。

撰写回答