Marcos Mayo
En Python puede tener una definición de función:
def info(obj, spacing=10, collapse=1)
que podría llamarse de cualquiera de las siguientes formas:
info(odbchelper)
info(odbchelper, 12)
info(odbchelper, collapse=0)
info(spacing=15, object=odbchelper)
gracias a que Python permite argumentos de cualquier orden, siempre que se nombren.
El problema que tenemos es que, a medida que crecen algunas de nuestras funciones más grandes, es posible que las personas agreguen parámetros entre spacing
y collapse
, lo que significa que los valores incorrectos pueden ir a parámetros que no tienen nombre. Además, a veces no siempre está claro lo que debe entrar.
¿Cómo podemos obligar a las personas a nombrar ciertos parámetros, no solo un estándar de codificación, sino idealmente un complemento de bandera o pydev?
De modo que en los 4 ejemplos anteriores, solo el último pasaría la verificación ya que se nombran todos los parámetros.
Eli Bendersky
En Python 3 – Sí, puede especificar *
en la lista de argumentos.
De documentos:
Los parámetros después de “*” o “*identificador” son parámetros de solo palabras clave y solo se pueden pasar argumentos de palabras clave usados.
>>> def foo(pos, *, forcenamed):
... print(pos, forcenamed)
...
>>> foo(pos=10, forcenamed=20)
10 20
>>> foo(10, forcenamed=20)
10 20
>>> foo(10, 20)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() takes exactly 1 positional argument (2 given)
Esto también se puede combinar con **kwargs
:
def foo(pos, *, forcenamed, **kwargs):
Para completar el ejemplo:
def foo(pos, *, forcenamed ):
print(pos, forcenamed)
foo(pos=10, forcenamed=20)
foo(10, forcenamed=20)
# basically you always have to give the value!
foo(10)
producción:
Traceback (most recent call last):
File "/Users/brando/anaconda3/envs/metalearning/lib/python3.9/site-packages/IPython/core/interactiveshell.py", line 3444, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-12-ab74191b3e9e>", line 7, in <module>
foo(10)
TypeError: foo() missing 1 required keyword-only argument: 'forcenamed'
Así que estás obligado a dar siempre el valor. Si no lo llama, no tiene que hacer nada más llamado argumento forzado.
-
Además de la respuesta. También puede considerar usar la función de sugerencias de escritura de Python para hacer cumplir la firma de su función. De esta manera, puede detectar llamadas malas mediante el uso de controles mypy, por ejemplo.
– rkachach
5 de abril de 2022 a las 8:59
-
PEP 3102 – Argumentos de solo palabras clave: peps.python.org/pep-3102
–Jean Monet
27 de mayo de 2022 a las 7:51
-
Nota: si la función tiene un decorador, esto no funcionará. En su lugar, avance a una segunda función (privada) con el decorador.
– Kevinarpe
2 de junio a las 6:48
Puede obligar a las personas a usar argumentos de palabras clave en Python3 definiendo una función de la siguiente manera.
def foo(*, arg0="default0", arg1="default1", arg2="default2"):
pass
Al hacer que el primer argumento sea un argumento posicional sin nombre, obliga a todos los que llaman a la función a usar los argumentos de palabras clave, que es lo que creo que estaba preguntando. En Python2, la única forma de hacer esto es definir una función como esta
def foo(**kwargs):
pass
Eso obligará a la persona que llama a usar kwargs, pero esta no es una solución tan buena, ya que entonces tendría que marcar para aceptar solo el argumento que necesita.
mario rossi
Verdadero, la mayoría de los lenguajes de programación hacer que el orden de los parámetros forme parte del contrato de llamada de función, pero esto no necesidad ser tan. ¿Por qué lo haría? Mi comprensión de la pregunta es, entonces, si Python es diferente a otros lenguajes de programación a este respecto. Además de otras buenas respuestas para Python 2, considere lo siguiente:
__named_only_start = object()
def info(param1,param2,param3,_p=__named_only_start,spacing=10,collapse=1):
if _p is not __named_only_start:
raise TypeError("info() takes at most 3 positional arguments")
return str(param1+param2+param3) +"-"+ str(spacing) +"-"+ str(collapse)
La única forma en que una persona que llama podría proporcionar argumentos spacing
y collapse
posicionalmente (sin excepción) sería:
info(arg1, arg2, arg3, module.__named_only_start, 11, 2)
La convención de no usar elementos privados pertenecientes a otros módulos ya es muy básica en Python. Al igual que con el propio Python, esta convención para los parámetros solo se aplicaría a medias.
De lo contrario, las llamadas tendrían que ser de la forma:
info(arg1, arg2, arg3, spacing=11, collapse=2)
Una llamada
info(arg1, arg2, arg3, 11, 2)
asignaría el valor 11 al parámetro _p
y una excepción planteada por la primera instrucción de la función.
Características:
- Parámetros antes
_p=__named_only_start
son admitidos posicionalmente (o por nombre). - Parámetros después
_p=__named_only_start
debe ser proporcionado por su nombre solamente (a menos que el conocimiento sobre el objeto centinela especial__named_only_start
se obtiene y utiliza).
Ventajas:
- Los parámetros son explícitos en número y significado (el último si también se eligen buenos nombres, por supuesto).
- Si el centinela se especifica como primer parámetro, todos los argumentos deben especificarse por nombre.
- Al llamar a la función, es posible cambiar al modo posicional usando el objeto centinela
__named_only_start
en el puesto correspondiente. - Se puede anticipar un mejor rendimiento que otras alternativas.
Contras:
La verificación ocurre durante el tiempo de ejecución, no durante el tiempo de compilación.- Uso de un parámetro adicional (aunque no argumento) y una verificación adicional. Pequeña degradación del rendimiento con respecto a las funciones regulares.
- La funcionalidad es un truco sin soporte directo por parte del lenguaje (ver nota a continuación).
- Al llamar a la función, es posible cambiar al modo posicional usando el objeto centinela
__named_only_start
en la posición correcta. Sí, esto también se puede ver como un profesional.
Tenga en cuenta que esta respuesta solo es válida para Python 2. Python 3 implementa el mecanismo compatible con lenguaje similar, pero muy elegante, descrito en otras respuestas.
Descubrí que cuando abro mi mente y pienso en ello, ninguna pregunta o decisión de los demás parece estúpida, tonta o simplemente tonta. Todo lo contrario: normalmente aprendo mucho.
-
“La comprobación se produce durante el tiempo de ejecución, no durante el tiempo de compilación”. – Creo que eso es cierto para todas las comprobaciones de argumentos de funciones. Hasta que realmente ejecute la línea de invocación de la función, no siempre sabrá qué función se está ejecutando. También, +1 – esto es inteligente.
–Eric
23 de julio de 2013 a las 18:24
-
@Eric: Es solo que hubiera preferido la verificación estática. Pero tienes razón: eso no habría sido Python en absoluto. Aunque no es un punto decisivo, la construcción “*” de Python 3 también se verifica dinámicamente. Gracias por tu comentario.
–Mario Rossi
27 de julio de 2013 a las 5:42
-
Además, si nombra la variable del módulo
_named_only_start
, se vuelve imposible referenciarlo desde un módulo externo, lo que saca un pro y un contra. (los guiones bajos individuales iniciales en el ámbito del módulo son privados, IIRC)–Eric
27 de julio de 2013 a las 23:54
-
Con respecto al nombramiento del centinela, también podríamos tener tanto un
__named_only_start
y unnamed_only_start
(sin guion bajo inicial), el segundo para indicar que el modo nombrado es “recomendado”, pero no al nivel de ser “promocionado activamente” (ya que uno es público y el otro no). En cuanto a la “privacidad” de_names
Comenzando con guiones bajos, el idioma no lo aplica con mucha fuerza: se puede eludir fácilmente mediante el uso de importaciones específicas (no *) o nombres calificados. Esta es la razón por la que varios documentos de Python prefieren usar el término “no público” en lugar de “privado”.–Mario Rossi
29 de julio de 2013 a las 23:02
Antonio
Puedes hacerlo de una manera que funciona tanto en Python 2 como en Python 3, creando un primer argumento de palabra clave “falso” con un valor predeterminado que no ocurrirá “naturalmente”. Ese argumento de palabra clave puede estar precedido por uno o más argumentos sin valor:
_dummy = object()
def info(object, _kw=_dummy, spacing=10, collapse=1):
if _kw is not _dummy:
raise TypeError("info() takes 1 positional argument but at least 2 were given")
Esto permitira:
info(odbchelper)
info(odbchelper, collapse=0)
info(spacing=15, object=odbchelper)
pero no:
info(odbchelper, 12)
Si cambia la función a:
def info(_kw=_dummy, spacing=10, collapse=1):
entonces todos los argumentos deben tener palabras clave y info(odbchelper)
ya no funcionará.
Esto le permitirá colocar argumentos de palabras clave adicionales en cualquier lugar después _kw
, sin obligarte a ponerlos después de la última entrada. Esto a menudo tiene sentido, por ejemplo, agrupar las cosas de forma lógica u ordenar las palabras clave alfabéticamente puede ayudar con el mantenimiento y el desarrollo.
Así que no hay necesidad de volver a usar def(**kwargs)
y perder la información de la firma en su editor inteligente. Su contrato social es proporcionar cierta información, al obligar (algunos de ellos) a requerir palabras clave, el orden en que se presentan se ha vuelto irrelevante.
Los argumentos de solo palabra clave de python3 (*
) se puede simular en python2.x con **kwargs
Considere el siguiente código python3:
def f(pos_arg, *, no_default, has_default="default"):
print(pos_arg, no_default, has_default)
y su comportamiento:
>>> f(1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() takes 1 positional argument but 3 were given
>>> f(1, no_default="hi")
1 hi default
>>> f(1, no_default="hi", has_default="hello")
1 hi hello
>>> f(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() missing 1 required keyword-only argument: 'no_default'
>>> f(1, no_default=1, wat="wat")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() got an unexpected keyword argument 'wat'
Esto se puede simular usando lo siguiente, tenga en cuenta que me he tomado la libertad de cambiar TypeError
a KeyError
en el caso de “argumento con nombre requerido”, no sería demasiado trabajo hacer que ese mismo tipo de excepción también
def f(pos_arg, **kwargs):
no_default = kwargs.pop('no_default')
has_default = kwargs.pop('has_default', 'default')
if kwargs:
raise TypeError('unexpected keyword argument(s) {}'.format(', '.join(sorted(kwargs))))
print(pos_arg, no_default, has_default)
Y comportamiento:
>>> f(1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() takes exactly 1 argument (3 given)
>>> f(1, no_default="hi")
(1, 'hi', 'default')
>>> f(1, no_default="hi", has_default="hello")
(1, 'hi', 'hello')
>>> f(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in f
KeyError: 'no_default'
>>> f(1, no_default=1, wat="wat")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in f
TypeError: unexpected keyword argument(s) wat
La receta funciona igual de bien en python3.x, pero debe evitarse si solo es python3.x
-
Ah! entonces
kwargs.pop('foo')
Qué es un modismo de Python 2? Necesito actualizar mi estilo de codificación. Todavía estaba usando este enfoque en Python 3 🤔– Neil
18 de junio de 2019 a las 10:04
Actualizar:
Me di cuenta de que usando **kwargs
no resolvería el problema. Si sus programadores cambian los argumentos de la función como lo desean, uno podría, por ejemplo, cambiar la función a esto:
def info(foo, **kwargs):
y el código antiguo volvería a romperse (porque ahora cada llamada de función tiene que incluir el primer argumento).
Realmente todo se reduce a lo que dice Bryan.
(…) la gente podría estar agregando parámetros entre
spacing
ycollapse
(…)
En general, al cambiar funciones, los nuevos argumentos siempre deben ir al final. De lo contrario, rompe el código. Debería ser obvio.
Si alguien cambia la función para que el código se rompa, este cambio debe ser rechazado.
(como dice bryan es como un contrato)
(…) a veces no siempre está claro lo que hay que poner.
Mirando la firma de la función (es decir, def info(object, spacing=10, collapse=1)
) uno debería ver inmediatamente que cada argumento que tiene no un valor predeterminado, es obligatorio.
Qué el argumento es para, debe ir a la cadena de documentación.
Respuesta anterior (mantenida para completar):
Esta probablemente no sea una buena solución:
Puede definir funciones de esta manera:
def info(**kwargs):
''' Some docstring here describing possible and mandatory arguments. '''
spacing = kwargs.get('spacing', 15)
obj = kwargs.get('object', None)
if not obj:
raise ValueError('object is needed')
kwargs
es un diccionario que contiene cualquier argumento de palabra clave. Puede verificar si un argumento obligatorio está presente y, si no, generar una excepción.
La desventaja es que puede que ya no sea tan obvio qué argumentos son posibles, pero con una cadena de documentación adecuada, debería estar bien.
-
Ah! entonces
kwargs.pop('foo')
Qué es un modismo de Python 2? Necesito actualizar mi estilo de codificación. Todavía estaba usando este enfoque en Python 3 🤔– Neil
18 de junio de 2019 a las 10:04
Daniel Newby
Como dicen otras respuestas, cambiar las firmas de funciones es una mala idea. Agregue nuevos parámetros al final o corrija cada llamador si se insertan argumentos.
Si todavía quieres hacerlo, usa un decorador de funciones y el inspeccionar.getargspec función. Se usaría algo como esto:
@require_named_args
def info(object, spacing=10, collapse=1):
....
Implementación de require_named_args
se deja como ejercicio para el lector.
no me molestaría Será lento cada vez que se llame a la función, y obtendrá mejores resultados al escribir el código con más cuidado.