Is there a smart way to write the following code in three or four lines?
a=l["artist"]
if a:
b=a["projects"]
if b:
c=b["project"]
if c:
print c
So I thought for something like pseudocode:
a = l["artist"] if True:
How about:
try:
print l["artist"]["projects"]["project"]
except KeyError:
pass
except TypeError:
pass # None["key"] raises TypeError.
This will try to print the value, but if a KeyError is raised, the except block will be run. pass means to do nothing. This is known and EAFP: it’s Easier to Ask Forgiveness than Permission.
I don't necessarily think that this is better but you could do:
try:
c = l["artist"]["projects"]["project"]
except (KeyError, TypeError) as e:
print e
pass
p = l.get('artist') and l['artist'].get('projects') and l['artist']['projects'].get('project')
if p:
print p
You can also make a more general function for this purpose:
def get_attr(lst, attr):
current = lst
for a in attr:
if current.get(a) is not None:
current = current.get(a)
else:
break
return current
>>> l = {'artist':{'projects':{'project':1625}}}
>>> get_attr(l,['artist','projects','project'])
1625
One-liner (as in the title) without exceptions:
if "artist" in l and l["artist"] and "projects" in l["artist"] and l["artist"]["projects"] and "project" in l["artist"]["projects"]: print l["artist"]["projects"]["project"]
Since you're dealing with nested dictionaries, you might find this generic one-liner useful because it will allow you to access values at any level just by passing it more keys arguments:
nested_dict_get = lambda item, *keys: reduce(lambda d, k: d.get(k), keys, item)
l = {'artist': {'projects': {'project': 'the_value'}}}
print( nested_dict_get(l, 'artist', 'projects', 'project') ) # -> the_value
Note: In Python 3, you'd need to add a from functools import reduce at the top.
Related
I would like to improve the way this code is written. Right now I have six methods that are almost copy-paste, only one line is changing. How can I make a generic method and depending on the property of the data input to change the calculations? I was thinking to use functional programming to achieve that, but I am not sure how to do it properly.
The method is getting a dict object. Then this object is transformed into JSON. The mid variable is storing a JSON with midrate for currency from external API, it must be before the for loop otherwise the API will be called in every iteration and this slows down the process a lot! Then in the for loop, I iterate through the data from the input. The only difference between methods is the calculation before inserting it in the list. .append(mid_current - bankMSell)
def margin_to_exchange_rate_sell(data):
j = data.to_JSON()
list_p = []
mid = midrate.get_midrate(j["fromCurrency"][0])
for idx, val in enumerate(j['toCurrency']):
try:
mid_current = 1/get_key(mid, j['toCurrency'][idx])
bankMSell = float(j['sellMargin'][idx])
list_p.append(mid_current - bankMSell)
except Exception as e:
list_p.append(0)
print(str(e))
return list_p
Another one of the methods:
def margin_to_exchange_rate_buy(data):
j = data.to_JSON()
list_p = []
mid = midrate.get_midrate(j["fromCurrency"][0])
for idx, val in enumerate(j['toCurrency']):
try:
mid_current = 1/get_key(mid, j['toCurrency'][idx])
bankMSell = float(j['sellMargin'][idx])
list_p.append(mid_current + bankMSell)
except Exception as e:
list_p.append(0)
print(str(e))
return list_p
Indeed, there is a way to reduce code here with lambdas:
def margin_to_exchange_rate_sell(data):
return margin_to_exchange_rate(data, lambda m, b: m - b)
def margin_to_exchange_rate_buy(data):
return margin_to_exchange_rate(data, lambda m, b: m + b)
def margin_to_exchange_rate(data, operation):
j = data.to_JSON()
list_p = []
mid = midrate.get_midrate(j["fromCurrency"][0])
for idx, val in enumerate(j['toCurrency']):
try:
mid_current = 1/get_key(mid, j['toCurrency'][idx])
bankMSell = float(j['sellMargin'][idx])
list_p.append(operation(mid_current, bankMSell))
except Exception as e:
list_p.append(0)
print(str(e))
return list_p
Is there better way of writing this code:
def add (exe1, exe2):
try:
a = float (exe1)
b = float (exe2)
total = float (a + b)
except ValueError:
return None
else:
return total
You can have it all inside a try/except block (calculation and return):
def add(exe1, exe2):
try:
return float(exe1) + float(exe2)
except ValueError:
return None
Also note, the default return value from function is None, so the second return is not really necessary (you could have pass instead), but it makes code more readable.
You can also use contextlib.suppress, if you find it more readable.
from contextlib import suppress
def add(exe1, exe2):
with suppress(ValueError):
return float(exe1) + float(exe2)
See the documentation here.
I have a yaml file with test(s) configuration(s) and there is an optional parameter "ignore-dup-txn" in optional section "test-options".
test-name:
test-type: trh_txn
test-src-format: excel
test-src-excel-sheet: invalid_txns
test-options:
ignore-dup-txn: True
I read section "test-name" to "test" dict and for now I check it this way:
if 'test-options' in test and 'ignore-dup-txn' in test['test-options']:
ignore_dups = test['test-options']['ignore-dup-txn']
else:
ignore_dups = None
What would be the pythonic way to do it? More clear, simple and shorter.
I was thinking to do "getter", but if I do get(test['test-option']['ignore-dup-txn']), I will get an exception in case if option is not defined, obviously.
You can use the get method:
test['test-options'].get('ignore-dup-txn',default-value)
This would work:
test.get('test-options', {}).get('ignore-dup-txn', None)
If you just want a "one-liner" and don't want an empty dict to be created you can do:
ignore_dups = test['test-options'].get('ignore-dup-txn') if 'test-options' in test else None
but this leads to long lines, and doesn't expand well to another level and is not very pythonic.
For something that is IMO a more pythonic first look at what happens when you have a dict and use a list as key for assignment or as first argument to .get() ¹:
d = dict()
l = ['a', 'b', 'c']
try:
d[l] = 3
except TypeError as e:
assert e.message == "unhashable type: 'list'"
else:
raise NotImplementedError
try:
d.get(l, None)
except TypeError as e:
assert e.message == "unhashable type: 'list'"
else:
raise NotImplementedError
That means some_dict.get(['a', 'b', 'c'], default) will throw a TypeError. on the other hand that is rather a nice concise syntax to get a value from a dict within a dict within ... .
So the question becomes how can I get such a .get() to work?
First you have to realise you cannot just replace the .get() method on a dict, you'll get an AttributeError:
d = dict()
def alt_get(key, default):
pass
try:
d.get = alt_get
except AttributeError as e:
assert e.message == "'dict' object attribute 'get' is read-only"
else:
raise NotImplementedError
So you will have to subclass dict, this allows you to override the .get() method:
class ExtendedDict(dict):
def multi_level_get(self, key, default=None):
if not isinstance(key, list):
return self.get(key, default)
# assume that the key is a list of recursively accessible dicts
# *** using [] and not .get() in the following on purpose ***
def get_one_level(key_list, level, d):
if level >= len(key_list):
if level > len(key_list):
raise IndexError
return d[key_list[level-1]]
return get_one_level(key_list, level+1, d[key_list[level-1]])
try:
return get_one_level(key, 1, self)
except KeyError:
return default
get = multi_level_get # delete this if you don't want to mask get()
# you can still use the multi_level-get()
d = dict(a=dict(b=dict(c=42)))
assert d['a']['b']['c'] == 42
try:
d['a']['xyz']['c'] == 42
except KeyError as e:
assert e.message == 'xyz'
else:
raise NotImplementedError
ed = ExtendedDict(d)
assert ed['a']['b']['c'] == 42
assert ed.get(['a', 'b', 'c'], 196) == 42
assert ed.get(['a', 'xyz', 'c'], 196) == 196 # no execption!
This works fine when only having dicts within dicts recursively, but also to a limited extend when you mix these with lists:
e = dict(a=[dict(c=42)])
assert e['a'][0]['c'] == 42
ee = ExtendedDict(e)
# the following works becauuse get_one_level() uses [] and not get()
assert ee.get(['a', 0, 'c'], 196) == 42
try:
ee.get(['a', 1, 'c'], 196) == 42
except IndexError as e:
assert e.message == 'list index out of range'
else:
raise NotImplementedError
try:
ee.get(['a', 'b', 'c'], 196) == 42
except TypeError as e:
assert e.message == 'list indices must be integers, not str'
else:
raise NotImplementedError
You can of course catch the latter two errors as well in multi_level_get() by using except (KeyError, TypeError, IndexError): and returning the default
for all these cases.
In ruamel.yaml ² this multi-level-get is implemented as mlget() (which requires an extra parameter to allow lists to be part of the hierarchy):
import ruamel.yaml as yaml
from ruamel.yaml.comments import CommentedMap
yaml_str = """\
test-name:
test-type: trh_txn
test-src-format: excel
test-src-excel-sheet: invalid_txns
test-options:
ignore-dup-txn: True
"""
data = yaml.load(yaml_str, Loader=yaml.RoundTripLoader)
assert data['test-name']['test-options']['ignore-dup-txn'] is True
assert data.mlget(['test-name', 'test-options', 'ignore-dup-txn'], 42) is True
assert data.mlget(['test-name', 'test-options', 'abc'], 42) == 42
print(data['test-name']['test-src-format'])
which prints:
excel
¹ In the examples I rather use assertions to confirm what is happening than print statements and then separate explanations on what gets printed. This keeps the explanation more conscise, and in the case of assertions within try/except blocks clear that the exception was thrown, without breaking the code and prohibiting following code from being executed. All of the example example code comes from python file that runs and only prints one value.
² I am the author of that package, which is an enhanced version of PyYAML.
I have a feeling that this sequence might be written shorter:
dim = Dimension.objects.get(pk=rows['pk'])
try:
dim.name = rows['name']
except KeyError:
pass
try:
dim.external_flg = rows['external_flg']
except:
pass
try:
dim.ext_owner = rows['ext_owner']
except KeyError:
pass
try:
dim.ext_table_name = rows['ext_table_name']
except KeyError:
pass
try:
dim.ext_start_date_column_name = rows['ext_start_date_column_name']
except KeyError:
pass
try:
dim.ext_end_date_column_name = rows['ext_end_date_column_name']
except KeyError:
pass
I've never had any experience in Python code optimization and working with exceptions but I'd be glad to have an alternative examples how it could be shortened.
Thank you!
Use the dict.get() method:
dim.name = rows.get('name', dim.name)
Or, for more DRY solution, put all assignments into the loop:
for field_name in ('name', 'external_flg', 'ext_owner', 'ext_table_name',
'ext_start_date_column_name', 'ext_end_date_column_name'):
if field_name in rows:
setattr(dim, field_name, rows[field_name])
To handle the list data you can make the similar loop:
for i, field_name in enumerate(('name', 'external_flg', 'ext_owner',
'ext_table_name', 'ext_start_date_column_name',
'ext_end_date_column_name')):
if i < len[rows]:
setattr(dim, field_name, rows[i])
Something like this should work — this answer includes try/catch block to catch KeyError :-)
attributes = ['name', 'external_flg', 'ext_owner', 'ext_table_name',
'ext_start_date_column_name', 'ext_end_date_column_name']
dim = Dimension.objects.get(pk=rows['pk'])
for attr in attributes:
try:
setattr(dim, attr, rows[attr])
except KeyError:
pass
I have a tree of objects and I need to check that particular object contains specific branch of objects. For example:
def specificNodeHasTitle(specificNode):
# something like this
return specificNode.parent.parent.parent.header.title != None
Is there an elegant way to do this without throwing exception if needed attribute is missing?
This works as long as you don't need indexes of arrays in your path to the item.
def getIn(d, arraypath, default=None):
if not d:
return d
if not arraypath:
return d
else:
return getIn(d.get(arraypath[0]), arraypath[1:], default) \
if d.get(arraypath[0]) else default
getIn(specificNode,["parent", "parent", "parent", "header", "title"]) is not None
Use try..except:
def specificNodeHasTitle(specificNode):
try:
return specificNode.parent.parent.parent.header.title is not None
except AttributeError:
# handle exception, for example
return False
There is nothing wrong with raising exceptions, by the way. It is a normal part of Python programming. Using try..except is the way to handle them.
For your specific case, the solution provided by unutbu is the best and the most pythonic, but I can't help trying to show the great capabilities of python and its getattr method:
#!/usr/bin/env python
# https://stackoverflow.com/questions/22864932/python-check-if-object-path-exists-in-tree-of-objects
class A(object):
pass
class Header(object):
def __init__(self):
self.title = "Hello"
specificNode=A()
specificNode.parent = A()
specificNode.parent.parent = A()
specificNode.parent.parent.parent = A()
specificNode.parent.parent.parent.header = Header()
hierarchy1="parent.parent.parent.header.title"
hierarchy2="parent.parent.parent.parent.header.title"
tmp = specificNode
for attr in hierarchy1.split('.'):
try:
tmp = getattr(tmp, attr)
except AttributeError:
print "Ouch... nopes"
break
else:
print "Yeeeps. %s" % tmp
tmp = specificNode
for attr in hierarchy2.split('.'):
try:
tmp = getattr(tmp, attr)
except AttributeError:
print "Ouch... nopes"
break
else:
print "Yeeeps. %s" % tmp
That outputs:
Yeeeps. Hello
Ouch... nopes
Python's great :)