2011-08-22 8 views
10

Digamos que tengo este código:dict.get() devuelve un puntero

my_dict = {} 
default_value = {'surname': '', 'age': 0} 

# get info about john, or a default dict 
item = my_dict.get('john', default_value) 

# edit the data 
item[surname] = 'smith' 
item[age] = 68 

my_dict['john'] = item 

El problema se hace evidente, si ahora comprobar el valor de default_value:

>>> default_value 
{'age': 68, 'surname': 'smith'} 

Es obvio , que my_dict.get() no devolvió el valor de default_value, sino un puntero (?) a él.

El problema se podía resolver en torno al cambiar el código para:

item = my_dict.get('john', {'surname': '', 'age': 0}) 

pero eso no parece ser una buena manera de hacerlo. Alguna idea, comentarios?

Respuesta

16
item = my_dict.get('john', default_value.copy()) 

Eres siempre pasando una referencia en Python.

Esto no tiene importancia para los objetos inmutables como str, int, tuple, etc., ya que no se pueden cambiar, solo punto un nombre a un objeto diferente, pero lo hace para objetos mutables como list, set, y dict. Debes acostumbrarte a esto y siempre tenlo en cuenta.

Edit: Zach Bloom y Jonathan Sternberg señalan los métodos que puede utilizar para evitar la llamada al copy en cada búsqueda. Usted debe utilizar el método de defaultdict, algo así como primer método de Jonathan, o:

def my_dict_get(key): 
    try: 
     item = my_dict[key] 
    except KeyError: 
     item = default_value.copy() 

Esto será más rápido que if cuando la llave casi siempre ya existe en my_dict, si el dict es grande. No tiene que envolverlo en una función, pero probablemente no quiera esas cuatro líneas cada vez que acceda al my_dict.

Vea la respuesta de Jonathan para los tiempos con un pequeño dict. El método get tiene un rendimiento bajo en todos los tamaños que probé, pero el método try funciona mejor en tamaños grandes.

+1

Este es un principio muy importante de pitón - * * todos los valores se pasan por referencia. La mutabilidad de esas referencias es un tema completamente diferente (aunque a menudo hace estallar a las personas de esta manera). –

+1

Estoy seguro de haber leído sobre esto antes, pero tiendes a olvidar cosas cuando no utilizo un idioma por mucho tiempo. Gracias por aclararlo. – Armandas

+0

¿Por qué su respuesta es diferente a la respuesta proporcionada en la pregunta? La pregunta parece ser más sobre encontrar una forma elegante de devolver una nueva instancia de la dict, pero solo crearla como y cuando sea necesario. – Dunes

7

En Python los dictados son ambos objetos (por lo que siempre se pasan como referencias) y mutables (lo que significa que se pueden cambiar sin ser recreados).

puede copiar el diccionario cada vez que lo use:

my_dict.get('john', default_value.copy()) 

También puede utilizar la colección defaultdict:

from collections import defaultdict 

def factory(): 
    return {'surname': '', 'age': 0} 

my_dict = defaultdict(factory) 

my_dict['john'] 
8

No utilice conseguir. Se podría hacer:

item = my_dict.get('john', default_value.copy()) 

Pero esto requiere un diccionario para ser copiado incluso si existe la entrada del diccionario. En su lugar, considere solo verificar si el valor está allí.

item = my_dict['john'] if 'john' in my_dict else default_value.copy() 

El único problema con esto es que realizará dos búsquedas para 'john' en lugar de solo una. Si usted está dispuesto a utilizar una línea adicional (y Nada no es un valor posible que usted podría obtener del diccionario), que podría hacer:

item = my_dict.get('john') 
if item is None: 
    item = default_value.copy() 

EDIT: pensé que me gustaría hacer algunas comparaciones de velocidad con timeit . El default_value y my_dict fueron globales. Los hice ambos para ambos si la llave estaba allí, y si había una falla.

el uso de excepciones:

def my_dict_get(): 
    try: 
     item = my_dict['key'] 
    except KeyError: 
     item = default_value.copy() 

# key present: 0.4179 
# key absent: 3.3799 

utilizando GET y comprobando si es Ninguno.

def my_dict_get(): 
    item = my_dict.get('key') 
    if item is None: 
     item = default_value.copy() 

# key present: 0.57189 
# key absent: 0.96691 

Comprobación de su existencia con el especial if/else sintaxis

def my_dict_get(): 
    item = my_dict['key'] if 'key' in my_dict else default_value.copy() 

# key present: 0.39721 
# key absent: 0.43474 

Ingenuamente copiar el diccionario.

def my_dict_get(): 
    item = my_dict.get('key', default_value.copy()) 

# key present: 0.52303 (this may be lower than it should be as the dictionary I used was one element) 
# key absent: 0.66045 

En su mayor parte, todo excepto el que usa excepciones son muy similares. La sintaxis especial if/else parece tener el tiempo más bajo por alguna razón (no sé por qué).

+0

Este es un buen punto, agregaré una nota a mi respuesta. ¿Qué tal ''john 'en my_dict' en lugar de' my_dict.has_key (' john ') 'y' my_dict.get (' john ') 'en lugar de' my_dict.get (' john ', None) '? – agf

+0

Me gusta el uso de en mejor que has_key. Olvidé que existió. No sabía que my_dict.get ('john') no cumplía con devolver null (pensé que era un IndexError). –

+0

O use esto: de colecciones import defaultdict mydict = defaultdict (default_value.copy) Luego cuando haga mydict [key-thats-not-here], se invocará la función que pasó al constructor. –

2

Lo principal a tener en cuenta es que todo en Python se pasa por referencia. Un nombre de variable en un lenguaje de estilo C suele ser una abreviatura de un área de memoria con forma de objeto, y la asignación a esa variable crea una copia de otro área en forma de objeto ... en Python, las variables son solo claves en un diccionario (locals()), y el acto de asignación almacena una nueva referencia. (Técnicamente, todo es un puntero, pero eso es un detalle de implementación).

Esto tiene varias implicaciones, la principal es que nunca habrá una copia implícita de un objeto creado porque lo pasó a una función, lo asignó, etc. La única forma de obtener una copia es hacer explícitamente asi que. El python stdlib ofrece un módulo copy que contiene algunas funciones, incluidas las funciones copy() y deepcopy(), para cuando desea hacer una copia explícita de algo. Además, algunos tipos exponen una función propia de .copy(), pero esto no es un estándar o se implementa de manera consistente. Otros que son inmutables tienden a ofrecer a veces un método .replace(), que hace una copia mutada.


En el caso de su código, que pasa en la instancia original, obviamente, no funciona, y hacer una copia antes de tiempo (cuando puede que no necesite a) es un despilfarro. Por lo que la solución más sencilla es probablemente ...

item = my_dict.get('john') 
if item is None: 
    item = default_dict.copy() 

sería útil en este caso si .get() apoyaron que pasa en una función constructora valor por defecto, pero eso es probablemente el exceso de ingeniería de una clase base para un caso límite.

1

porque my_dict.get('john', default_value.copy()) crearía una copia de dict predeterminado cada vez get se llama (incluso cuando 'John' está presente y devuelto), es más rápido y muy bien usar esta try/excepto opción:

try: 
    return my_dict['john'] 
except KeyError: 
    return {'surname': '', 'age': 0} 

Como alternativa, también se puede utilizar un defaultdict:

import collections 

def default_factory(): 
    return {'surname': '', 'age': 0} 

my_dict = collections.defaultdict(default_factory) 
Cuestiones relacionadas