2010-09-10 6 views
8

Tengo algunas funciones en mi código que aceptan ya sea un objeto o un iterable de objetos como entrada. Me enseñaron a utilizar nombres significativos para todo, pero no estoy seguro de cómo cumplirlo aquí. ¿Qué debería llamar un parámetro que puede un objeto simple o una iterable de objetos? He llegado con dos ideas, pero no me gusta ninguno de los dos:Nombres de parámetros en funciones de Python que toman un solo objeto o iterable

  1. FooOrManyFoos - Esto expresa lo que pasa, pero me podía imaginar que alguien no acostumbrado a que podría tener problemas para entender lo que significa la derecha
  2. param - Algún nombre genérico. Esto deja en claro que puede ser varias cosas, pero no explica nada sobre para qué se usa el parámetro.

Normalmente llamo iterables de objetos al plural de lo que yo llamaría un solo objeto. Sé que esto puede parecer un poco compulsivo, pero se supone que Python debe ser (entre otros) sobre la legibilidad.

+2

¿Debe aceptar un solo objeto, en lugar de un objeto iterable? Eso haría su código mucho más claro (es decir, pasar '[foo]' en lugar de 'foo'). – katrielalex

+0

@katrielalex: Esto es posible. Tengo antecedentes en C++ y estoy acostumbrado a usar sobrecargas para este tipo de problema. Quizás tenga que replantearme algo aquí. –

Respuesta

7

Tengo algunas funciones en mi código que aceptan ya sea un objeto o un iterable de objetos como entrada.

Esto es algo muy excepcional ya menudo muy malo. Es trivialmente evitable.

es decir, pase [foo] en lugar de foo al llamar a esta función.

La única vez que puede justificar hacer esto es cuando (1) tiene una base de software instalada que espera un formulario (iterable o singleton) y (2) tiene que expandirlo para soportar el otro caso de uso. Asi que. Usted solo haga esto al expandir una función existente que tiene una base de código existente.

Si se trata de un nuevo desarrollo, no haga esto.

he llegado con dos ideas, pero no me gusta ninguno de los dos:

[Solo dos?]

FooOrManyFoos - Esto expresa lo que pasa, pero me podía imaginar que alguien no acostumbrado a que podría tener problemas para entender lo que significa de inmediato

¿Qué? ¿Dice que no proporciona NINGUNA otra documentación ni ninguna otra capacitación? ¿Sin soporte? No hay consejos? ¿Quién es el "alguien que no está acostumbrado"? Háblales. No asumas ni imagines cosas sobre ellos.

Además, no utilizan líder mayúsculas Nombres.

parámetro - Algunos nombre genérico. Esto deja en claro que puede ser varias cosas, pero no explica nada sobre para qué se usa el parámetro.

Terrible. Nunca. Hacer. Esta.

miré en la biblioteca de Python para ejemplos. La mayoría de las funciones que hacen esto tienen descripciones simples.

http://docs.python.org/library/functions.html#isinstance

isinstance (objeto, ClassInfo)

Lo llaman "ClassInfo" y que puede ser una clase o una tupla de clases.

se puede hacer eso, también.

Usted debe considerar el caso de uso común y las excepciones. Sigue la regla 80/20.

  1. 80% del tiempo, puede reemplazar esto con un iterable y no tener este problema.

  2. En el 20% restante de los casos, tiene una base instalada de software basada en una suposición (ya sea un elemento iterable o único) y necesita agregar el otro caso. No cambie el nombre, simplemente cambie la documentación. Si solía decir "foo", todavía dice "foo" pero le haces aceptar un iterable de "foo" sin hacer ningún cambio en los parámetros. Si solía decir "foo_list" o "foo_iter", sigue diciendo "foo_list" o "foo_iter", pero tolerará en silencio un singleton sin romperse.

    • 80% del código es el legado ("foo" o "foo_list")

    • 20% del código es la nueva función ("foo" puede ser un iterable o "foo_list" . puede ser un único objeto)

-1

Estoy trabajando en un proyecto bastante grande ahora y estamos pasando mapas y simplemente llamando a nuestro parámetro map. Los contenidos del mapa varían según la función a la que se llama. Esta probablemente no es la mejor situación, pero reutilizamos mucho del mismo código en los mapas, por lo que copiar y pegar es más fácil.

Yo diría que en lugar de nombrarlo como es, debes nombrarlo para qué se usa. Además, solo tenga cuidado de no llamar al uso in en un formato no iterable.

+3

'map' está definido por Python, esta es una opción potencialmente confusa como nombre de parámetro: las personas esperan que' map' signifique el 'map' de Python. – EOL

+1

Estoy de acuerdo con lo que dijo @EOL. Es una buena idea evitar el uso de nombres de funciones incorporadas como nombres de parámetros. –

+3

Además, copiar y pegar el código generalmente no es un buen método de reutilización de código ... – nikow

0

Me gustaría ir con un nombre que explique que el parámetro puede ser una instancia o una lista de instancias. Diga one_or_more_Foo_objects. Lo encuentro mejor que el insípido param.

1

¿Puede nombrar su parámetro de una manera muy alta? las personas que leen el código están más interesadas en saber qué representa el parámetro ("clientes") que cuál es su tipo ("list_of_tuples"); el tipo se puede definir en la cadena de documentación de la función, lo cual es bueno, ya que podría cambiar, en el futuro (el tipo es a veces un detalle de implementación).

+0

Mi problema aquí es que algo como 'clients' sugiere varios objetos. Si un llamador quiere pasar solo un objeto, puede pensar que necesita envolverlo en algo iterable (esa sería mi intuición). Estoy buscando una manera de expresar que un solo objeto también está bien. –

+1

@ Space_C0wb0y: Me rendiría indicando el tipo de parámetro en el nombre del parámetro, e incluiré esta información al principio de la cadena de documentación. Después de todo, a menudo * tiene * para llamar a una lista como 'file_lines' incluso cuando sabe que esta lista puede contener solo una línea. Nadie esperará que lo llames 'file_line_or_file_lines'. Por lo tanto, sugeriría que su código sea perfectamente legible si usa un nombre descriptivo corto y de alto nivel. :) – EOL

3

suena como si estuviera agonizando sobre la fealdad de código como:

def ProcessWidget(widget_thing): 
    # Infer if we have a singleton instance and make it a 
    # length 1 list for consistency 
    if isinstance(widget_thing, WidgetType): 
    widget_thing = [widget_thing] 

    for widget in widget_thing: 
    #... 

Mi sugerencia es evitar sobrecargar la interfaz para manejar dos casos distintos.Tiendo a escribir código que favorece la reutilización y la denominación clara de los métodos más dinámico uso inteligente de parámetros:

def ProcessOneWidget(widget): 
    #... 

def ProcessManyWidgets(widgets): 
    for widget in widgets: 
    ProcessOneWidget(widget) 

A menudo, comienzo con este modelo simple, pero luego tienen la oportunidad de optimizar los "muchos" caso cuando hay eficiencias de ganancia que compensan la complejidad adicional del código y la duplicación parcial de la funcionalidad. Si esta convención parece demasiado prolija, se puede optar por nombres como "ProcessWidget" y "ProcessWidgets", aunque la diferencia entre los dos es un solo carácter que se puede perder fácilmente.

2

Puede usar * args magic (varargs) para hacer que sus parámetros sean siempre iterables.

pasar un solo elemento o varios elementos conocidos como argumentos de funciones normales como func (arg1, arg2, ...) y pasar argumentos iterables con un asterisco antes, como func (* args)

ejemplo:

# magic *args function 
def foo(*args): 
    print args 

# many ways to call it 
foo(1) 
foo(1, 2, 3) 

args1 = (1, 2, 3) 
args2 = [1, 2, 3] 
args3 = iter((1, 2, 3)) 

foo(*args1) 
foo(*args2) 
foo(*args3) 
0

que haría 1 cosa,

def myFunc(manyFoos): 
    if not type(manyFoos) in (list,tuple): 
     manyFoos = [manyFoos] 
    #do stuff here 

es así, lo hace no necesita preocuparse más sobre su nombre.

en una función que debe tratar de lograr para tener 1 acción, acepte el mismo tipo de parámetro y devuelva el mismo tipo.

En lugar de llenar las funciones con ifs, puede tener 2 funciones.

0

Dado que no te importa exactamente qué tipo de iterable obtienes, puedes intentar obtener un iterador para el parámetro usando iter(). Si iter() genera una excepción TypeError, el parámetro no es iterable, entonces usted crea una lista o tupla de un elemento, que es iterable y Bob es su tío.

def doIt(foos): 
    try: 
     iter(foos) 
    except TypeError: 
     foos = [foos] 
    for foo in foos: 
     pass # do something here 

El único problema con este enfoque es si foo es una cadena. Una cadena es iterable, por lo que pasar una sola cadena en lugar de una lista de cadenas dará como resultado iterar sobre los caracteres en una cadena. Si esto es una preocupación, puede agregar una prueba if para ello. En este punto, se está volviendo prolijo para el código repetitivo, así que lo dividiré en su propia función.

def iterfy(iterable): 
    if isinstance(iterable, basestring): 
     iterable = [iterable] 
    try: 
     iter(iterable) 
    except TypeError: 
     iterable = [iterable] 
    return iterable 

def doIt(foos): 
    for foo in iterfy(foos): 
     pass # do something 

A diferencia de algunos de los que respondieron, me gusta hacer esto, ya que elimina una cosa la persona que llama podría equivocarse al usar su API. "Sé conservador en lo que generas pero liberal en lo que aceptas".

Para responder a su pregunta original, es decir, lo que debe nombrar el parámetro, aún seguiría con "foos" aunque acepte un solo elemento, ya que su intento es aceptar una lista. Si no es iterable, es técnicamente un error, aunque uno lo corregirá para la persona que llama dado que procesar solo un elemento es probablemente lo que ellos quieren. Además, si la persona que llama piensa, debe pasar un elemento iterable de un elemento, bueno, eso funcionará bien y requiere muy poca sintaxis, entonces ¿por qué preocuparse por corregir su error de comprensión?

4

Supongo que llego un poco tarde a la fiesta, pero me sorprende que nadie haya sugerido un decorador.

def withmany(f): 
    def many(many_foos): 
     for foo in many_foos: 
      yield f(foo) 
    f.many = many 
    return f 

@withmany 
def process_foo(foo): 
    return foo + 1 


processed_foo = process_foo(foo) 

for processed_foo in process_foo.many(foos): 
    print processed_foo 

Vi un patrón similar en una de las publicaciones de Alex Martelli, pero no recuerdo el enlace de la mano.

Cuestiones relacionadas