Recursive function return None - python

I have a function that runs over an API output and should return the path to specific key.
Here is the function:
def find_path(obj, val, path=''):
if isinstance(obj, dict):
for k, v in obj.items():
if k == val:
return f'{path}[{k!r}]'
return find_path(v, val, path + f'[{k!r}]')
elif isinstance(obj, list):
for i, v in enumerate(obj):
if v == val:
return f'{path}[{i!r}]'
return find_path(v, val, path + f'[{i!r}]')
API output Example:
ex = {'Resources': [{'uschemas': {'emailSelfUpdateAllowed': True,
'emailVerificationDays': 30,
'approvers': {'manager': False,
'secondLevelManager': False,
'owner': False,
'workGroup': 'workgroup'}}}]}
When I run the function I get None:
bla = find_path(ex, 'approvers')
print(bla)
>>> None
I expect to get:
['Resources'][0]['uschemas']['approvers']
I can only get the expected output when I am using the function with print instead of return.
Can someone help me to understand why? and how can I make it work with returns and not prints because I need to use its output.
Thank you.

You need to return value after the for loop is done, this is the value returns from the recursion, if you don't have anything it will return None
def find_path(obj, val, path=''):
p = []
if isinstance(obj, dict):
for k, v in obj.items():
if k == val:
return f'{path}[{k!r}]'
p = find_path(v, val, path + f'[{k!r}]')
return p
elif isinstance(obj, list):
for i, v in enumerate(obj):
if v == val:
return f'{path}[{i!r}]'
p = find_path(v, val, path + f'[{i!r}]')
return p
bla = find_path(ex, 'approvers')
print(bla) # ['Resources'][0]['uschemas']['approvers']

Related

Python KeyError Messari SystemError: 404 Client Error: Not Found for url

I'm a beginner. I write this code to get some data in Messari:
from messari.messari import Messari
messari = Messari("Messari_API")
import pandas as pd
with open("MS_Asset.txt", "r") as a:
asset = a.read().splitlines()
def get_dict(key, dict_data):
for k, v in dict_data.items():
if k == key and isinstance(v, dict):
return v
elif isinstance(v, dict):
ret = get_dict(key, v)
if ret is not None:
return ret
return None
msr_p = messari.get_asset_profile(asset_slugs=asset, asset_profile_metric='economics')
But I had "SystemError: 404 Client Error: Not Found for url" because some data not found.
I wanted to avoid KeyError with multi data.
def get_dict(key, dict_data):
for k, v in dict_data.items():
if k == key and isinstance(v, dict):
return v
elif isinstance(v, dict):
ret = get_dict(key, v)
if ret is not None:
return ret
return None
Sorry for my bad!

iterate a dict and pass as function parameter

I have this function what returns an assignment
def insertData(
Model:object,
data_entry:dict
)->any:
for k, v in data_entry.items():
if isinstance(v, list):
return getattr(Model, k).in_(v)
else:
return getattr(Model, k)== v
and then these function is called here for it to pass assign those values
def get_vehicles(
db:Session,
skip: int = 0,
limit: int= 100,
query:Query=None
)-> Union[list[Vehicle], list[None]]:
real_query = get_only_passed_values(query)
if real_query:
return db.query(Vehicle).filter(
insertData(Vehicle, real_query)
).offset(skip).limit(limit).all()
else:
return db.query(Vehicle).offset(skip).limit(limit).all()
I need to filter multiple columns in database table, but only filter the first argument passed.
e.g:
return db.query(Vehicle).filter(Vehicle.color == 'red',Vehicle.brand == 'BMW').offset(skip).limit(limit).all()

How can I get the specified key value in a nested dictionary in a most effective way?

There is a nested dictionery like :
data_dict = {
"picture":"xxx.jpg",
"link_data":{
"picture":"xxxxx.jpg",
...
"child_attachments":{
"picture":"xxxxx.jpg",
...
}
}
...
}
The problem is at every level of the dictionary, the key picture may exist, how can I get the picture's value in a most effective way?
Here's my trial, but failed:
def get_picture_url(data):
for key, value in data.items():
if key == "picture":
return data[key]
else:
if isinstance(value, dict):
return get_picture_url(value)
get_picture_url(data_dict)
This should work for the general case of an arbitrarily nested dictionary with JSON-like structure:
def get_picture(data):
# you can remove this case if the
# input doesn't contain lists
if isinstance(data, list):
ans = []
for e in data:
ans += get_picture(e)
return ans
elif not isinstance(data, dict):
return []
else:
ans = []
for k, v in data.items():
if k == 'picture':
ans.append(v)
else:
ans += get_picture(v)
return ans
It'll traverse all levels of the data structure, looking for keys named 'picture' and accumulating all of their values in a single output list. If you're sure that there are no lists in the input, we can simplify the solution a bit:
def get_picture(data):
ans = []
if isinstance(data, dict):
for k, v in data.items():
if k == 'picture':
ans.append(v)
else:
ans += get_picture(v)
return ans
Either way, it works as expected for your sample input:
data_dict = {
"picture":"xxx.jpg",
"link_data":{
"picture":"xxxx.jpg",
"child_attachments":{
"picture":"xxxxx.jpg"
}
}
}
get_picture(data_dict)
=> ['xxx.jpg', 'xxxx.jpg', 'xxxxx.jpg']
You are not checking the returned value of the recursive call to get_picture_url.
This should give you the top most picture in your dict:
def get_picture_url(data, picture_key="picture"):
if not isinstance(data, dict):
return None
picture_url = data.get(picture_key)
if picture_url is not None:
return picture_url
for value in data.values():
picture_url = get_picture_url(value)
if picture_url is not None:
return picture_url
return None

Extracting all path from a multi-level dictionary

I have a dictionary like this:
dirDict = {"DIR1" : {
"DIR11" : {
"DIR111" : "Maki111",
"DIR112" : "Maki112"
},
"DIR12" : "Maki12",
"DIR13" : {
"DIR131" : "Maki131"
}
}
}
Imagine this like a folder structure. And I would like to get similar as os.walk would do with a folder structure. Something like this:
["DIR1/DIR11/DIR111/Maki111",
"DIR1/DIR11/DIR112/Maki112",
"DIR1/DIR12/Maki12",
"DIR1/DIR13/DIR131/Maki131"]
So it is basically all the path for the dictionary values. I tried it many ways with recursive functions but I got lost.
Here is my latest trial:
def walk(input_dict, path_string = "", result = ""):
for key, value in input_dict.items():
if isinstance(value, dict):
path_string += "/" + key
print "==== DICT ====", "\nkey: ", key, "\nvalue: ", value, "\n\t\tpath_string: ", path_string
result = walk(value, path_string)
print "\t\t\t\tresulting: ", result
elif isinstance(value, str):
print "==== NOT DICT ===="
path_string += "/" + value
print "\t\tpath_string: ", path_string, "\nvalue: ", value
return path_string
else:
path_string = "/" + key
result += "\n" + result
return result
Using Python 3:
dirDict = {"DIR1" : {
"DIR11" : {
"DIR111" : "Maki111",
"DIR112" : "Maki112"
},
"DIR12" : "Maki12",
"DIR13" : {
"DIR131" : "Maki131"
}
}
}
def recurse(d, prefix=None, sep='/'):
if prefix is None:
prefix = []
for key, value in d.items():
if isinstance(value, dict):
yield from recurse(value, prefix + [key])
else:
yield sep.join(prefix + [key, value])
print(list(recurse(dirDict)))
Output:
['DIR1/DIR13/DIR131/Maki131', 'DIR1/DIR11/DIR111/Maki111', 'DIR1/DIR11/DIR112/Maki112', 'DIR1/DIR12/Maki12']
def walk(d, path):
paths = []
if len(d) == 0:
return path
for k, v in d.iteritems():
child_path = path + k + '/'
if isinstance(v, basestring):
paths.append(child_path + v)
else:
paths.extend(walk(v, child_path))
return paths
THe walk function I posted at https://gist.github.com/nvie/f304caf3b4f1ca4c3884#gistcomment-1597937 can be used as a helper for your problem:
def walk(obj, parent_first=True):
# Top down?
if parent_first:
yield (), obj
# For nested objects, the key is the path component.
if isinstance(obj, dict):
children = obj.items()
# For nested lists, the position is the path component.
elif isinstance(obj, (list, tuple)):
children = enumerate(obj)
# Scalar values have no children.
else:
children = []
# Recurse into children
for key, value in children:
for child_path, child in walk(value, parent_first):
yield (key,) + child_path, child
# Bottom up?
if not parent_first:
yield (), obj
Your problem can be approached using something like this:
for path, value in walk(obj):
if isinstance(value, str): # leaf node
path_with_value = path + (value,)
print("/".join(path_with_value))
A compact solution with a list comprehension:
def f(v):
if isinstance(v, dict):
return dict_to_list(v)
elif isinstance(v, list):
return v
else:
return [v]
def dict_to_list(d):
return ['{}/{}'.format(k, i) for k, v in d.items() for i in f(v)]
lst = dict_to_list(dirDict)
lst.sort()
print('\n'.join(lst))

Overriding a method that yields

I am new to Python 3 concepts.
The base class has following functions:
class DocumentFormatter(object):
def transform_element(self, key, value):
if isinstance(value, list):
for li, lv in enumerate(value):
for inner_k, inner_v in self.transform_element(
"%s.%s" % (key, li), lv):
yield inner_k, inner_v
elif isinstance(value, dict):
formatted = self.format_document(value)
for doc_key in formatted:
yield "%s.%s" % (key, doc_key), formatted[doc_key]
else:
# We assume that transform_value will return a 'flat' value,
# not a list or dict
yield key, self.transform_value(value)
def format_document(self, document):
def flatten(doc, path):
top_level = (len(path) == 0)
if not top_level:
path_string = ".".join(path)
for k in doc:
v = doc[k]
if isinstance(v, dict):
path.append(k)
for inner_k, inner_v in flatten(v, path):
yield inner_k, inner_v
path.pop()
else:
transformed = self.transform_element(k, v)
for new_k, new_v in transformed:
if top_level:
yield new_k, new_v
else:
yield "%s.%s" % (path_string, new_k), new_v
return dict(flatten(document, []))
In the derived class, I only want to change a bit of the transform_element function:
def transform_element(self, key, value):
if key=="cats":
yield key, self.transform_value(value)
else:
yield super().transform_element(key,value)
If the key is "cats", I want to yield using my logic. Else, I want the base class implementation to work. Am I supposed to call yield or should I call return?
you should iterate over the overridden method and yield each item of it, otherwise you will end up with a generator of a generator which is not what you need.
So your method should look like:
def transform_element(self, key, value):
if key=="cats":
yield key, self.transform_value(value)
else:
for item in super().transform_element(key,value):
yield item

Categories