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
Related
I'm writing a challenge in Python and I would like to know what is the right approach to return None case my function throws an Exception.
def get(email):
try:
customer = get_customer(email)
return customer
except TypeError as e:
logging.error(f'Customer {email} not found', e)
Should I do something like customer = None and return customer after except block?
Next function will fail with some data. Where is it better to wrap it with a try-except block?
if attr in obj_request:
new_record = GosReestr()
new_record.display_name_department = request.user.profile_ad.department
new_record.set_data(**obj_request[attr][_main_data])
print('before')
new_record.save()
print('after')
Is this better?
if attr in obj_request:
new_record = GosReestr()
new_record.display_name_department = request.user.profile_ad.department
new_record.set_data(**obj_request[attr][_main_data])
print('before')
try:
new_record.save()
except as e
print('after')
Or is it better to wrap everything in one if block?
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.
I have a request with JSON data, it may or may not contain 'items' key, if it does it has to be a list of objects, that I want to process individually. So I have to write something like:
json_data = request.get_json()
for item in json_data['items']:
process_item(item)
But, since presence of the 'items' key is not mandatory, an additional measure needs to be taken. I would like to follow EAFP approach, so wrapping it up into try ... except statement:
json_data = request.get_json()
try:
for item in json_data['items']:
process_item(item)
except KeyError as e:
pass
Let's assume that a KeyError exception can happened inside the process_item(...) function, that may indicate a code error, thus it should not go unnoticed, so I want to make sure that I will catch only exceptions coming from for statement predicate, as a workaround I came up with:
json_data = request.get_json()
try:
for item in json_data['items']:
process_item(item)
except KeyError as e:
if e.message != 'items':
raise e
pass
But
It looks ugly
It relies on knowledge of the process_item(...) implementation, assuming that KeyError('items') cannot be raised inside of it.
If the for statement becomes more complex e.g. for json_data['raw']['items'] so will the except clause making it even less readable and maintainable.
Update:
The suggested alternative
json_data = request.get_json()
try:
items = json_data["items"]
except KeyError:
items = []
for item in items:
process_item(item)
is essentially the same as
json_data = request.get_json()
if json_data.has('items')
items = json_data['items']
else:
items = []
for item in items:
process_item(item)
So we check before we loop. I would like to know if there is any more pythonic/EAFP approach?
You can catch the exception only when accessing "items":
json_data = request.get_json()
try:
items = json_data["items"]
except KeyError:
items = []
for item in items:
process_item(item)
However, we can replace the try-block with a call to the .get() function, making it much cleaner:
for item in request.get_json().get("items", []):
process_item(item)
I think the cleanest option is to use atryblock around only the code that attempts to retrieve the data associated with the'items'key:
json_data = request.get_json()
try:
items = json_data['items']
except KeyError:
print "no 'items' to process" # or whatever you want to...
else:
for item in items:
process_item(item)
This layout will allow to you clearly separate the error handling as you see fit. You can add a separate independenttry/exceptaround theforloop if desired.
I have this long list of try except statement:
try:
uri = entry_obj['media$group']['media$content'][0]['url']
except (KeyError, IndexError):
uri = None
try:
position = entry_obj['yt$position']['$t']
except KeyError:
position = None
try:
description = entry_obj['content']['$t']
except KeyError:
description = None
try:
seconds = entry_obj['media$group']['yt$duration']['seconds']
except KeyError:
seconds = None
try:
thumbnails = entry_obj['media$group']['media$thumbnail']
except KeyError:
thumbnails = None
Is there a more concise way to write this?
If you tire of figuring out what to use for default values in get() calls, just write a helper function:
def resolve(root, *keys):
for key in keys:
try:
root = root[key]
except (KeyError, IndexError):
return None
return root
Then you just write, e.g.:
uri = resolve(entry_obj, 'media$group', 'media$content', 0, 'url')
To simplify the calls a little, you might beef up the helper function to take a single string for the keys and split on spaces; that way you don't have to type so many quotes, and we can also add a default value argument:
def resolve(root, keys, default=None):
for key in keys.split():
try:
root = root[key]
except (TypeError, KeyError):
try:
root = root[int(key)]
except (IndexError, ValueError, KeyError):
return default
uri = resolve(entry_obj, 'media$group media$content 0 url', '')
I thought of another good way to do this, not sure how it compares to kindall's method. We first define a method property:
def res(self, property):
try:
return property()
except (KeyError, IndexError):
return None
Then replace the try-except statements with:
url = res(lambda: entry_obj['media$group']['media$content'][0]['url'])
position = res(lambda: entry_obj['yt$position']['$t'])
description = res(lambda: entry_obj['content']['$t'])
duration = res(lambda: entry_obj['media$group']['yt$duration']['seconds'])
thumbnails = res(lambda: entry_obj['media$group']['media$thumbnail'])
Use the get method of dictionaries instead:
position = entry_object.get('yt$position').get('$t')
get will handle the case of a key not existing for you, and give you a (changable) fallback value instead in that case. You'll still need to handle the first IndexError manually, but all the ones that are just except KeyError: will disappear.