python - ¿Es siempre seguro modificar el diccionario `** kwargs`?
function dictionary (3)
Usando la sintaxis de la función Python def f(**kwargs)
, en la función se crea un diccionario de palabras clave kwargs
, y los diccionarios son mutables, entonces la pregunta es, si kwargs
diccionario kwargs
, ¿es posible que tenga algún efecto? fuera del alcance de mi función?
Según entiendo cómo funciona el desembalaje de diccionarios y el empaquetado de argumentos de palabras clave, no veo ninguna razón para creer que pueda ser inseguro, y me parece que no hay peligro de esto en Python 3.6:
def f(**kwargs):
kwargs[''demo''] = 9
if __name__ == ''__main__'':
demo = 4
f(demo=demo)
print(demo) # 4
kwargs = {}
f(**kwargs)
print(kwargs) # {}
kwargs[''demo''] = 4
f(**kwargs)
print(kwargs) # {''demo'': 4}
Sin embargo, ¿es esta implementación específica, o es parte de la especificación de Python? ¿Estoy pasando por alto cualquier situación o implementación en la que (salvo modificaciones a argumentos que son ellos mismos mutables, como kwargs[''somelist''].append(3)
) este tipo de modificación podría ser un problema?
Ambas respuestas anteriores son correctas al afirmar que técnicamente, los kwargs
mutantes nunca tendrán un efecto en los ámbitos principales.
Pero ... ese no es el final de la historia . Es posible que una referencia a kwargs
se comparta fuera del alcance de la función, y luego te kwargs
con todos los problemas de estado mutados compartidos habituales que podrías esperar.
def create_classes(**kwargs):
class Class1:
def __init__(self):
self.options = kwargs
class Class2:
def __init__(self):
self.options = kwargs
return (Class1, Class2)
Class1, Class2 = create_classes(a=1, b=2)
a = Class1()
b = Class2()
a.options[''c''] = 3
print(b.options)
# {''a'': 1, ''b'': 2, ''c'': 3}
# other class''s options are mutated because we forgot to copy kwargs
Técnicamente, esto responde a su pregunta, ya que compartir una referencia a kwargs mutable
conduce a efectos fuera del ámbito de la función.
Me han mordido varias veces esto en el código de producción, y es algo que explícitamente cuido por ahora, tanto en mi propio código como cuando reviso otros. El error es obvio en mi ejemplo artificial anterior, pero es mucho más engañoso en el código real cuando se crean funciones de fábrica que comparten algunas opciones comunes.
Para el código de nivel Python, el dict kwargs
dentro de una función siempre será un nuevo dict.
Sin embargo, para las extensiones C , ten cuidado. La versión C API de kwargs
a veces pasa un dict directamente. En versiones anteriores, incluso pasaría las subclases dict directamente, lo que llevaría al error ( ahora corregido ) donde
''{a}''.format(**collections.defaultdict(int))
produciría ''0''
lugar de generar un KeyError
.
Si alguna vez tiene que escribir extensiones C, posiblemente incluyendo Cython, no intente modificar el equivalente kwargs
, y kwargs
cuidado con las subclases dict en las versiones antiguas de Python.
Siempre es seguro. Como dice la especificación
Si el formulario "** identificador" está presente, se inicializa a una nueva asignación ordenada que recibe cualquier exceso de argumentos de palabra clave, por defecto a una nueva asignación vacía del mismo tipo.
Énfasis añadido.
Siempre se garantiza que obtendrá un nuevo objeto de correlación dentro del invocable. Mira este ejemplo
def f(**kwargs):
print((id(kwargs), kwargs))
kwargs = {''foo'': ''bar''}
print(id(kwargs))
# 140185018984344
f(**kwargs)
# (140185036822856, {''foo'': ''bar''})
Entonces, aunque f
puede modificar un objeto que se pasa a través de **
, no puede modificar el objeto **
autor de la llamada.
Actualización : ya que preguntaste sobre casos de esquina, aquí hay un infierno especial para ti que de hecho modifica los kwargs
la persona que kwargs
:
def f(**kwargs):
kwargs[''recursive!''][''recursive!''] = ''Look ma, recursive!''
kwargs = {}
kwargs[''recursive!''] = kwargs
f(**kwargs)
assert kwargs[''recursive!''] == ''Look ma, recursive!''
Sin embargo, es probable que no lo veas en la naturaleza.