python - standard - Diccionarios y valores predeterminados
python package name (8)
(esta es una respuesta tardía)
Una alternativa es subclasificar la clase dict
e implementar el __missing__()
, como este:
class ConnectionDetails(dict):
def __missing__(self, key):
if key == ''host'':
return "localhost"
raise KeyError(key)
Ejemplos:
>>> connection_details = ConnectionDetails(port=80)
>>> connection_details[''host'']
''localhost''
>>> connection_details[''port'']
80
>>> connection_details[''password'']
Traceback (most recent call last):
File "python", line 1, in <module>
File "python", line 6, in __missing__
KeyError: ''password''
Suponiendo que connectionDetails
es un diccionario de Python, ¿cuál es la mejor, más elegante y más "pitonica" forma de refactorizar código como este?
if "host" in connectionDetails:
host = connectionDetails["host"]
else:
host = someDefaultValue
Hay un método en los diccionarios de python para hacer esto: dict.setdefault
connectionDetails.setdefault(''host'',someDefaultValue)
host = connectionDetails[''host'']
Sin embargo, este método establece el valor de connectionDetails[''host'']
en someDefaultValue
si el host
clave no está ya definido, a diferencia de lo que hizo la pregunta.
Me gusta esto:
host = connectionDetails.get(''host'',''someDefault'')
Mientras que .get()
es un buen modismo, es más lento que if/else
(y más lento que try/except
si se puede esperar la presencia de la clave en el diccionario la mayor parte del tiempo):
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
... stmt="try:/n a=d[1]/nexcept KeyError:/n a=10")
0.07691968797894333
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
... stmt="try:/n a=d[2]/nexcept KeyError:/n a=10")
0.4583777282275605
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
... stmt="a=d.get(1, 10)")
0.17784020746671558
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
... stmt="a=d.get(2, 10)")
0.17952161730158878
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
... stmt="if 1 in d:/n a=d[1]/nelse:/n a=10")
0.10071221458065338
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
... stmt="if 2 in d:/n a=d[2]/nelse:/n a=10")
0.06966537335119938
Para múltiples valores predeterminados diferentes pruebe esto:
connectionDetails = { "host": "www.example.com" }
defaults = { "host": "127.0.0.1", "port": 8080 }
completeDetails = {}
completeDetails.update(defaults)
completeDetails.update(connectionDetails)
completeDetails["host"] # ==> "www.example.com"
completeDetails["port"] # ==> 8080
Probando la sospecha de @Tim Pietzcker sobre la situación en PyPy (5.2.0-alpha0) para Python 3.3.5, encuentro que, de hecho, tanto .get()
como el modo if
/ else
funcionan de manera similar. En realidad, parece que en el caso if / else solo hay una sola búsqueda si la condición y la asignación involucran la misma clave (compárelo con el último caso donde hay dos búsquedas).
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="try:/n a=d[1]/nexcept KeyError:/n a=10")
0.011889292989508249
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="try:/n a=d[2]/nexcept KeyError:/n a=10")
0.07310474599944428
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="a=d.get(1, 10)")
0.010391917996457778
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="a=d.get(2, 10)")
0.009348208011942916
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="if 1 in d:/n a=d[1]/nelse:/n a=10")
0.011475925013655797
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="if 2 in d:/n a=d[2]/nelse:/n a=10")
0.009605801998986863
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="if 2 in d:/n a=d[2]/nelse:/n a=d[1]")
0.017342638995614834
Puede usar una función lamba para esto como una línea. Haga un nuevo objeto connectionDetails2
que se accede como una función ...
connectionDetails2 = lambda k: connectionDetails[k] if k in connectionDetails.keys() else "DEFAULT"
Ahora usa
connectionDetails2(k)
en lugar de
connectionDetails[k]
que devuelve el valor del diccionario si k
está en las teclas, de lo contrario, devuelve "DEFAULT"
También puedes usar el defaultdict
así:
from collections import defaultdict
a = defaultdict(lambda: "default", key="some_value")
a["blabla"] => "default"
a["key"] => "some_value"
Puede pasar cualquier función ordinaria en lugar de lambda:
from collections import defaultdict
def a():
return 4
b = defaultdict(a, key="some_value")
b[''absent''] => 4
b[''key''] => "some_value"