2011-03-03 9 views
6

A veces en mi código tengo una función que puede tomar un argumento de una de dos maneras. Algo así como:¿Patrón elegante para args de palabra clave mutuamente excluyentes?

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) 

¿Hay alguna forma más elegante de hacer esto? ¿Qué pasa si el arg puede venir en una de tres maneras? Si los tipos son distintos, podría hacer:

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

Pero, ¿y si no lo son? Esta alternativa parece tonta:

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

causa, entonces usted tiene que llamar así func(mytype, isName=False) lo cual es raro.

+8

¿Por qué no tendría simplemente dos funciones separadas, una aceptando un nombre y la otra aceptando un tipo? Este enfoque parece que solo confundiría a los consumidores de la API. –

+2

O, si realmente quieres tener una sola función que lo haga todo, ¿por qué no simplemente aceptas un solo argumento y luego descubres cuál es? Si el argumento es un tipo, busque el objeto por tipo, de lo contrario, búsquelo por su nombre. O simplemente pruebe uno y luego el otro y vea cuál funciona (no puedo imaginar un caso en el que ambos recuperen algo). – kindall

+1

@Mark: si el func es un constructor que no funcionará – Claudiu

Respuesta

6

¿Y si uso algo así como un patrón de comando de envío:

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

donde type1, type2, etc, son tipos reales Python (por ejemplo, int, float, etc).

1

Una forma para que sea un poco más corto es

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) 

Todavía no he decidido si iba a llamar a este "elegante".

Tenga en cuenta que debe generar un TypeError si se proporcionó una cantidad incorrecta de argumentos, no un ValueError.

+0

¿Por qué TypeError? ¿Porque uno de los tipos de argumentos debe ser NoneType? –

+0

@Ryan: para ser coherente con el uso general de 'ValueError' y' TypeError' en Python. Si pasa una cantidad incorrecta de argumentos a una función, Python generará un 'TypeError', por lo que debería hacer lo mismo. –

+0

@Sven, ¿qué le parece ['if bool (objname) == bool (objtype):'] (http://stackoverflow.com/questions/432842/how-do-you-get-the-logical-xor-of -two-variables-in-python/433161 # 433161) – senderle

3

suena como tiene que ir a https://codereview.stackexchange.com/

De todos modos, manteniendo la misma interfaz, que puede probar

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) 

o simplemente crear 2 funciones?

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

+1 para la revisión del código, ¡ni siquiera lo sabía! ¿qué es la función 'next' aunque? – Claudiu

+0

@Claudiu: 'next' devuelve un elemento del iterador y avanza el iterador. En este uso, es más o menos equivalente a 'x [0]'. – kennytm

+0

ah ok lo escribí en python, pero dije que no es definido, aparentemente es nuevo en 2.6 – Claudiu

0

Parece que usted está buscando function overloading, que no está implementado en Python 2. En Python 2, la solución es casi tan bueno como se puede esperar obtener.

Probablemente se podría pasar por alto la adicional problema argumento al permitir que su función para procesar varios objetos y devolver un generador:

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) 

prueba:

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

Por lo que vale, el mismo género de cosas suceden en las bibliotecas estándar; ver, por ejemplo, el comienzo de GzipFile en gzip.py (mostrado aquí con docstrings retirados):

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' 

Por supuesto, esto acepta tanto filename y fileobj palabras clave y define un comportamiento en particular en el caso de que recibe ambos; pero el enfoque general parece bastante idéntico.

Cuestiones relacionadas