Compound dictionary keys - attributes

I have a particular case where using compound dictionary keys would make a task easier. I have a working solution, but feel it is inelegant. How would you do it?
context = {
'database': {
'port': 9990,
'users': ['number2', 'dr_evil']
},
'admins': ['number2#virtucon.com', 'dr_evil#virtucon.com'],
'domain.name': 'virtucon.com'
}
def getitem(key, context):
if hasattr(key, 'upper') and key in context:
return context[key]
keys = key if hasattr(key, 'pop') else key.split('.')
k = keys.pop(0)
if keys:
try:
return getitem(keys, context[k])
except KeyError, e:
raise KeyError(key)
if hasattr(context, 'count'):
k = int(k)
return context[k]
if __name__ == "__main__":
print getitem('database', context)
print getitem('database.port', context)
print getitem('database.users.0', context)
print getitem('admins', context)
print getitem('domain.name', context)
try:
getitem('database.nosuchkey', context)
except KeyError, e:
print "Error:", e
Thanks.

>>> def getitem(context, key):
try:
return context[key]
except KeyError:
pass
cur, _, rest = key.partition('.')
rest = int(rest) if rest.isdigit() else rest
return getitem(context[cur], rest)
>>> getitem(context, 'admins.0')
'number2#virtucon.com'
>>> getitem(context, 'database.users.0')
'number2'
>>> getitem(context, 'database.users.1')
'dr_evil'
I've changed the order of the arguments, because that's how most Python's functions work, cf. getattr, operator.getitem, etc.

The accepted solution (as well as my first attempt) failed due to the ambiguity inherent in the specs: '.' may be "just a separator" or a part of the actual key string. Consider, for example, that key may be 'a.b.c.d.e.f' and the actual key to use at the current level is 'a.b.c.d' with 'e.f' left over for the next-most-indented level. Also, the spec is ambiguous in another sense: if more than one dot-joined prefix of 'key' is present, which one to use?
Assume the intention is to try every such feasible prefix: this would possibly produce multiple solutions but we can arbitrarily return the first solution found in this case.
def getitem(key, context):
stk = [(key.split('.'), context)]
while stk:
kl, ctx = stk.pop()
if not kl: return ctx
if kl[0].isdigit():
ik = int(kl[0])
try: stk.append((kl[1:], ctx[ik]))
except LookupError: pass
for i in range(1, len(kl) + 1):
k = '.'.join(kl[:i])
if k in ctx: stk.append((kl[i:], ctx[k]))
raise KeyError(key)
I was originally trying to avoid all try/excepts (as well as recursion and introspection via hasattr, isinstance, etc), but one snuck back in: it's hard to check if an integer is an acceptable index/key into what might be either a dict or a list, without either some introspection to distinguish the cases, or (and it looks simpler here) a try/except, so I went fir te latter, simplicity being always near the top of my concerns. Anyway...
I believe variants on this approach (where all the "possible continuation-context pairs" that might still be feasible at any point are kept around) are the only working way to deal with the ambiguities I've explained above (of course, one might choose to collect all possible solutions, arbitrarily pick one of them according to whatever heuristic criterion is desire, or maybe raise if the ambiguity is biting so there are multiple solutions, etc, etc, but these are minor variants of this general idea).

The following code works. It checks for the special case of a single key having a period in it. Then, it splits the key apart. For each subkey, it tries to fetch the value from a list-like context, then it tries from a dictionary-type context, then it gives up.
This code also shows how to use unittest/nose, which is highly recommended. Test with "nosetests mysource.py".
Lastly, consder using Python's built-in ConfigParser class, which is really useful for this type of configuration task: http://docs.python.org/library/configparser.html
#!/usr/bin/env python
from nose.tools import eq_, raises
context = {
'database': {
'port': 9990,
'users': ['number2', 'dr_evil']
},
'admins': ['number2#virtucon.com', 'dr_evil#virtucon.com'],
'domain.name': 'virtucon.com'
}
def getitem(key, context):
if isinstance(context, dict) and context.has_key(key):
return context[key]
for key in key.split('.'):
try:
context = context[int(key)]
continue
except ValueError:
pass
if isinstance(context, dict) and context.has_key(key):
context = context[key]
continue
raise KeyError, key
return context
def test_getitem():
eq_( getitem('database', context), {'port': 9990, 'users': ['number2', 'dr_evil']} )
eq_( getitem('database.port', context), 9990 )
eq_( getitem('database.users.0', context), 'number2' )
eq_( getitem('admins', context), ['number2#virtucon.com', 'dr_evil#virtucon.com'] )
eq_( getitem('domain.name', context), 'virtucon.com' )
#raises(KeyError)
def test_getitem_error():
getitem('database.nosuchkey', context)

As the key to getitem must be a string (or a list which is passed in the recursive call) I've come up with the following:
def getitem(key, context, first=True):
if not isinstance(key, basestring) and not isinstance(key, list) and first:
raise TypeError("Compound key must be a string.")
if isinstance(key, basestring):
if key in context:
return context[key]
else:
keys = key.split('.')
else:
keys = key
k = keys.pop(0)
if key:
try:
return getitem(keys, context[k], False)
except KeyError, e:
raise KeyError(key)
# is it a sequence type
if hasattr(context, '__getitem__') and not hasattr(context, 'keys'):
# then the index must be an integer
k = int(k)
return context[k]
I am on the fence as to whether this is an improvement.

I'm leaving my original solution for posterity:
CONTEXT = {
"database": {
"port": 9990,
"users": ["number2", "dr_evil"]},
"admins": ["number2#virtucon.com", "dr_evil#virtucon.com"],
"domain": {"name": "virtucon.com"}}
def getitem(context, *keys):
node = context
for key in keys:
node = node[key]
return node
if __name__ == "__main__":
print getitem(CONTEXT, "database")
print getitem(CONTEXT, "database", "port")
print getitem(CONTEXT, "database", "users", 0)
print getitem(CONTEXT, "admins")
print getitem(CONTEXT, "domain", "name")
try:
getitem(CONTEXT, "database", "nosuchkey")
except KeyError, e:
print "Error:", e
But here's a version that implements an approach similar to the getitem interface suggested by doublep. I am specifically not handling dotted keys, but rather forcing the keys into separate nested structures because that seems cleaner to me:
CONTEXT = {
"database": {
"port": 9990,
"users": ["number2", "dr_evil"]},
"admins": ["number2#virtucon.com", "dr_evil#virtucon.com"],
"domain": {"name": "virtucon.com"}}
if __name__ == "__main__":
print CONTEXT["database"]
print CONTEXT["database"]["port"]
print CONTEXT["database"]["users"][0]
print CONTEXT["admins"]
print CONTEXT["domain"]["name"]
try:
CONTEXT["database"]["nosuchkey"]
except KeyError, e:
print "Error:", e
You might notice that what I've really done here is eliminate all ceremony regarding accessing the data structure. The output of this script is the same as the original except that it does not contain a dotted key. This seems like a more natural approach to me but if you really wanted to be able to handle dotted keys, you could do something like this I suppose:
CONTEXT = {
"database": {
"port": 9990,
"users": ["number2", "dr_evil"]},
"admins": ["number2#virtucon.com", "dr_evil#virtucon.com"],
"domain": {"name": "virtucon.com"}}
def getitem(context, dotted_key):
keys = dotted_key.split(".")
value = context
for key in keys:
try:
value = value[key]
except TypeError:
value = value[int(key)]
return value
if __name__ == "__main__":
print getitem(CONTEXT, "database")
print getitem(CONTEXT, "database.port")
print getitem(CONTEXT, "database.users.0")
print getitem(CONTEXT, "admins")
print getitem(CONTEXT, "domain.name")
try:
CONTEXT["database.nosuchkey"]
except KeyError, e:
print "Error:", e
I'm not sure what the advantage of this type of approach would be though.

Related

How to return two different error messages when querying the same model in Django

Take a look at the following:
def mutate(self, info, first_id, second_id):
try:
first = Model.objects.get(pk=first_id)
second = Model.objects.get(pk=second_id)
except Model.DoesNotExist:
return Exception('Object does not exist.')
else:
...
How can I return a custom error message depending on which of the ids actually does not exist? It's be nice to have something like:
{first_id} does not exist
I can't have two different except blocks because it's the same Model. What to do?
You can simply split up your query's in two statements:
def mutate(self, info, first_id, second_id):
try:
first = Model.objects.get(pk=first_id)
except Model.DoesNotExist:
raise Exception('Your first id {} Does not exist'.format(first_id))
try:
second = Model.objects.get(pk=second_id)
except Model.DoesNotExist:
raise Exception('Your second id {} Does not exist'.format(second_id))
...
PS: you need to raise exceptions. Not return them.

Python3 missing exception when looping

I have to define an attribute in a class and I would like to manage error in the most pythonic way.
Here is the code I have tried so far. I can't figure out why I can not "reach" the exception in the following code.
# global variable to be used in the example
my_dict = {"key1": {"property": 10}, "key2": {}}
class Test(object):
#property
def my_attribute(self):
try:
return self._my_attribute
except AttributeError:
self._my_attribute = {}
for key, value in my_dict.items():
print(key)
self._my_attribute[key] = value['property']
except Exception:
print('error')
# I would like to manage my error here with a log or something
print("I am not reaching here")
finally:
return self._my_attribute
if __name__ == '__main__':
Test().my_attribute
I expected to reach the Exception case in the second iteration of the for loop since it is a KeyError ("key2" has no "property"). But it just passes by it. In this example, if the script is run, it does not print "I am not reaching here". Could anyone explain why I am seeing this wrong? Thanks!
The potential KeyError in self._my_attribute[key] = value['property'] is not covered by the except Exception block. Once it is raised the finally block is executed (as a matter of fact the finally block is always executed, regardless of an exception being raised or even handled). This can be easily verified by using a step-by-step debugger or with a simple print('finally') inside the finally block.
This is (among other reasons) why try blocks should be as minimal as possible. If you know that line might raise a KeyError then explicitly try-except it:
for key, value in my_dict.items():
print(key)
try:
self._my_attribute[key] = value['property']
except KeyError as e:
print('Key ', e, 'does not exist')

Cannot pass updated list in recursion function

I have a homework problem which requires me to check if a dictionary value is also a key word, and continue calling values until I find one that is not also a key. The trick is, you can get stuck on a never ending loop.
I tried to remedy this by tracking which values have previously been used in order to be able to kick out of recursion, but the homework problems auto grader does not allow this.
Here is my code:
def rabbit_hole(my_dict, string, new_list = []):
if string in new_list:
return False
try:
new_list.append(string)
value = my_dict[string]
return rabbit_hole(my_dict, value, new_list)
except:
return string
d = {"bat": "pig", "pig": "cat", "cat": "dog", "dog": "ant",
"cow": "bee", "bee": "elk", "elk": "fly", "ewe": "cod",
"cod": "hen", "hog": "fox", "fox": "jay", "jay": "doe",
"rat": "ram", "ram": "rat"}
In the example above, if we execute...
print(rabbit_hole(d, "rat"))
... this shows an example of a never ending loop and I would like it to return False. Is there an obvious reason this doesn't work with the auto grader? Or is there another way to track which keys have already been used?
It could be that your grader is checking for additional arguments to the function and doesn't allow for any.
How about just deleting the dictionary entry you've already checked?
def rabbit_hole(my_dict, string):
try:
value = my_dict[string]
my_dict.pop(string, None)
return rabbit_hole(my_dict, value)
except:
return string

Can't call a previously declared dictionary though a method

I'm fairly new to python...aka just started.
So i was making a simple game and i'm trying to get a dictionary to work through out a claas and cant get it to work.
class Map(object):
def __init__(self):
self.Eng = {
"1": "Map_Eng()",
"2": "Guard_Fight()",
"3": "Item_Eng()"
}
def enter_room(self):
pass
def exit_room(self):
print("You move onto the next room.")
return self.Eng[1]
The problem is small. Your dictionary has an entry with key "1" (a string), but not with key 1 (a number). To fix your problem, change the line
return self.Eng[1]
to
return self.Eng["1"]

python string format suppress/silent keyerror/indexerror [duplicate]

This question already has answers here:
How to get Python to gracefully format None and non-existing fields [duplicate]
(3 answers)
Closed 8 years ago.
Is there a way to use python string.format such that no exception is thrown when an index is missing, instead an empty string is inserted.
result = "i am an {error} example string {error2}".format(hello=2,error2="success")
here,result should be :
"i am an example string success"
Right now, python throws a keyerror and stops formatting. Is it possible to change this behavior ?
Thanks
Edit:
There exists Template.safe_substitute (even that leaves the pattern intact instead of inserting an empty string) , but couldn't something similar for string.format
The desired behavior would be similar to string substitution in php.
class Formatter(string.Formatter):
def get_value(self,key,args,kwargs):
try:
if hasattr(key,"__mod__"):
return args[key]
else:
return kwargs[key]
except:
return ""
This seems to provide the desired behavior.
The official solution (Python 3 Docs) for strings in format mappings is to subclass the dict class and to define the magic-method __missing__(). This method is called whenever a key is missing, and what it returns is used for the string formatting instead:
class format_dict(dict):
def __missing__(self, key):
return "..."
d = format_dict({"foo": "name"})
print("My %(foo)s is %(bar)s" % d) # "My name is ..."
print("My {foo} is {bar}".format(**d)) # "My name is ..."
Edit: the second print() works in Python 3.5.3, but it does not in e.g. 3.7.2: KeyError: 'bar' is raised and I couldn't find a way to catch it.
After some experiments, I found a difference in Python's behavior. In v3.5.3, the calls are __getitem__(self, "foo") which succeeds and __getitem__(self, "bar") which can not find the key "bar", therefore it calls __missing__(self, "bar") to handle the missing key without throwing a KeyError. In v3.7.2, __getattribute__(self, "keys") is called internally. The built-in keys() method is used to return an iterator over the keys, which yields "foo", __getitem__("foo") succeeds, then the iterator is exhausted. For {bar} from the format string there is no key "bar". __getitem__() and hence __missing_() are not called to handle the situation. Instead, the KeyError is thrown. I don't know how one could catch it, if at all.
In Python 3.2+ you should use format_map() instead (also see Python Bug Tracker - Issue 6081):
from collections import defaultdict
d = defaultdict(lambda: "...")
d.update({"foo": "name"})
print("My {foo} is {bar}".format_map(d)) # "My name is ..."
If you want to keep the placeholders, you can do:
class Default(dict):
def __missing__(self, key):
return key.join("{}")
d = Default({"foo": "name"})
print("My {foo} is {bar}".format_map(d)) # "My name is {bar}"
As you can see, format_map() does call __missing__().
The following appears to be the most compatible solution as it also works in older Python versions including 2.x (I tested v2.7.15):
class Default(dict):
def __missing__(self, key):
return key.join("{}")
d = Default({"foo": "name"})
import string
print(string.Formatter().vformat("My {foo} is {bar}", (), d)) # "My name is {bar}"
To keep placeholders as-is including the format spec (e.g. {bar:<15}) the Formatter needs to be subclassed:
import string
class Unformatted:
def __init__(self, key):
self.key = key
def __format__(self, format_spec):
return "{{{}{}}}".format(self.key, ":" + format_spec if format_spec else "")
class Formatter(string.Formatter):
def get_value(self, key, args, kwargs):
if isinstance(key, int):
try:
return args[key]
except IndexError:
return Unformatted(key)
else:
try:
return kwargs[key]
except KeyError:
return Unformatted(key)
f = Formatter()
s1 = f.vformat("My {0} {1} {foo:<10} is {bar:<15}!", ["real"], {"foo": "name"})
s2 = f.vformat(s1, [None, "actual"], {"bar":"Geraldine"})
print(s1) # "My real {1} name is {bar:<15}!"
print(s2) # "My real actual name is Geraldine !"
Note that the placeholder indices are not changed ({1} remains in the string without a {0}), and in order to substitute {1} you need to pass an array with any odd first element and what you want to substitute the remaining placeholder with as second element (e.g. [None, "actual"]).
You can also call the format() method with positional and named arguments:
s1 = f.format("My {0} {1} {foo:<10} is {bar:<15}!", "real", foo="name")
s2 = f.format(s1, None, "actual", bar="Geraldine")
str.format() doesn't expect a mapping object. Try this:
from collections import defaultdict
d = defaultdict(str)
d['error2'] = "success"
s = "i am an {0[error]} example string {0[error2]}"
print s.format(d)
You make a defaultdict with a str() factory that returns "". Then you make one key for the defaultdict. In the format string, you access keys of the first object passed. This has the advantage of allowing you to pass other keys and values, as long as your defaultdict is the first argument to format().
Also, see http://bugs.python.org/issue6081
Unfortunately, no, there is no such way to do by default. However you can provide it defaultdict or object with overridden __getattr__, and use like this:
class SafeFormat(object):
def __init__(self, **kw):
self.__dict = kw
def __getattr__(self, name):
if not name.startswith('__'):
return self.__dict.get(name, '')
print "i am an {0.error} example string {0.error2}".format(SafeFormat(hello=2,error2="success"))
i am an example string success
I made a version that does work similarly to Daniel's method but without the {0.x} attribute access.
import string
class SafeFormat(object):
def __init__(self, **kw):
self.__dict = kw
def __getitem__(self, name):
return self.__dict.get(name, '{%s}' % name)
string.Formatter().vformat('{what} {man}', [], SafeFormat(man=2))
prints out
'{what} 2'

Resources