¿Hay alguna forma de saber si el valor de un argumento es el predeterminado frente al especificado por el usuario?

Estoy buscando algo que funcione como las variables arg-supply-p de Lisp, para ayudar a diferenciar un valor predeterminado del mismo valor especificado por un usuario.

Ejemplo:

def foo(a=10): pass 

Me gustaría saber en foo si fue llamado así:

 foo() 

o así:

 foo(10) 

o incluso así:

 foo(a=10) 

Los 2 últimos son suficientes para mí; No necesito saber ese detalle. He revisado un poco el módulo de inspección, pero getargspec devuelve exactamente los mismos resultados para las 3 llamadas.

A excepción de inspeccionar el código fuente, no hay manera de diferenciar las tres llamadas de función, se supone que tienen el mismo significado. Si necesita diferenciarlos, use un valor predeterminado diferente, por ejemplo, None .

 def foo(a=None): if a is None: a = 10 # no value for a provided 

Como señala Sven, es típico usar Ninguno para un argumento predeterminado. Si incluso necesita decir la diferencia entre la ausencia de argumentos y la provisión de una explícita, puede usar un objeto centinela:

 sentinel = object() def foo(a=sentinel): if a is sentinel: # No argument was provided ... 

Realmente no. Puede usar foo.func_defaults para obtener una lista de los argumentos predeterminados de la función, por lo que podría usarla para verificar la identidad del objeto con los argumentos pasados. Pero esto solo funcionará de forma confiable para objetos mutables. No hay forma de saber, como en su ejemplo, si alguien pasó 10 o usó el 10 predeterminado, porque es probable que ambos 10 sean el mismo objeto.

De todos modos, esto es solo una aproximación aproximada, porque no le dice cómo se llamó a la función: le dice cuáles son los argumentos predeterminados, y si observa los argumentos reales, e intente adivinar si son los mismos. No hay manera de obtener acceso a la forma sintáctica utilizada para llamar a la función.

Aquí hay un ejemplo que muestra cómo a veces funciona y otras no.

 >>> def f(x="this is crazy"): ... print x is f.func_defaults[0] >>> f() True >>> f("this is crazy") False >>> def f(x=10): ... print x is f.func_defaults[0] >>> f() True >>> f(10) True 

La función a la que se llama solo obtiene el valor del argumento final, ya sea el valor predeterminado o algo que se pasa. Entonces, lo que debe hacer para saber si la persona que llama pasó algo es hacer que el valor predeterminado sea algo que no pueda pasar. en.

Pueden pasar en None , así que no puedes usar eso. ¿Qué puedes usar?

La solución más simple es crear un objeto de Python de algún tipo, usarlo como el valor predeterminado en la función y luego del para que los usuarios no tengan forma de referirse a él. Técnicamente, los usuarios pueden extraer esto de sus objetos de función, pero rara vez van a llegar a tal longitud, y si lo hacen, pueden argumentar que merecen lo que obtienen.

 default = [] def foo(a=default): if a is default: a = 10 print a del default 

El “valor predeterminado” que estoy usando aquí es una lista vacía. Dado que las listas son mutables, nunca se reutilizan, lo que significa que cada lista vacía es un objeto único. (Python en realidad usa el mismo objeto de tupla vacío para todas las tuplas vacías, así que no use una tupla vacía. Los literales de cadena y los enteros pequeños se reutilizan de manera similar, así que tampoco los use). Una instancia de object estaría bien. Cualquier cosa, siempre y cuando sea mutable y por lo tanto no se puede compartir.

Ahora, el problema es que el código anterior no se ejecutará realmente desde que hiciste la del default . ¿Cómo se obtiene una referencia a este objeto en su función en tiempo de ejecución para que pueda realizar una prueba? Una forma de hacerlo es con un valor de argumento predeterminado que no se usa para un argumento real.

 default = [] def foo(a=default, default=default): if a is default: a = 10 print a del default 

Esto vincula el objeto al valor predeterminado del argumento cuando se define la función, por lo que no importa que el nombre global se elimine un segundo más tarde. Pero el problema es que si alguien help() en su función, o si lo hace de manera introspectiva, parecerá que hay un argumento adicional que pueden transmitir. Una mejor solución es un cierre:

 default = [] def foo(default=default): # binds default to outer func's local var default def foo(a=default): # refers to outer func's local var default if a is default: a = 10 print a return foo foo = foo() del default 

Todo esto es mucho trabajo, así que, realmente, no te molestes.