我在www.gpcontract.co.uk
使用公共API来填充代表英国健康组织层次结构的大型可变嵌套字典。
一些背景信息
最高层次的是四个英国国家(英格兰,苏格兰,威尔士和北爱尔兰),然后是区域组织,一直到个人诊所。每个国家/地区的层次结构深度不同,可能会根据年份而变化。每个组织都有一个名称,orgcode和列出其子组织的字典。
不幸的是,API中没有完整的嵌套层次结构,但是对http://www.gpcontract.co.uk/api/children/[organisation code]/[year]
的调用将返回任何其他子组织的直接子组织。
为了能够在我的应用程序中轻松导航层次结构,我想生成一个完整层次结构的离线词典(每年一次),该词典将使用pickle
保存并与应用程序捆绑在一起。< / p>
获取这个意味着大量的API调用,而且我将返回的JSON转换为我需要的字典对象时遇到了麻烦。
以下是层次结构中一小部分的示例(我仅以单个子组织为例)。
JSON层次结构示例
{
"eng": {
"name": "England",
"orgcode": "eng",
"children": {}
},
"sco": {
"name": "Scotland",
"orgcode": "sco",
"children": {}
},
"wal": {
"name": "Wales",
"orgcode": "wal",
"children": {}
},
"nir": {
"name": "Northern Ireland",
"orgcode": "nir",
"children": {
"blcg": {
"name": "Belfast Local Commissioning Group",
"orgcode": "blcg",
"children": {
"abc": {
"name": "Random Clinic",
"orgcode": "abc",
"children": {}
}
}
}
}
}
}
这是我用来进行API调用并填充字典的脚本:
我的剧本
import json, pickle, urllib.request, urllib.error, urllib.parse
# Organisation hierarchy may vary between years. Set the year here.
year = 2017
# This function returns a list containing a dictionary for each child organisation with keys for name and orgcode
def get_child_orgs(orgcode, year):
orgcode = str(orgcode)
year = str(year)
# Correct 4-digit year to 2-digit
if len(year) > 2:
year = year[2:]
try:
child_data = json.loads(urllib.request.urlopen('http://www.gpcontract.co.uk/api/children/' + str(orgcode) + '/' + year).read())
output = []
if child_data != []:
for item in child_data['children']:
output.append({'name' : item['name'], 'orgcode' : str(item['orgcode']).lower(), 'children' : {}})
return output
except urllib.error.HTTPError:
print('HTTP error!')
except:
print('Other error!')
# I start with a template of the top level of the hierarchy and then populate it
hierarchy = {'eng' : {'name' : 'England', 'orgcode' : 'eng', 'children' : {}}, 'nir' : {'name' : 'Northern Ireland', 'orgcode' : 'nir', 'children' : {}}, 'sco' : {'name' : 'Scotland', 'orgcode' : 'sco', 'children' : {}}, 'wal' : {'name' : 'Wales', 'orgcode' : 'wal', 'children' : {}}}
print('Loading data...\n')
# Here I use nested for loops to make API calls and populate the dictionary down the levels of the hierarchy. The bottom level contains the most items.
for country in ('eng', 'nir', 'sco', 'wal'):
for item1 in get_child_orgs(country, year):
hierarchy[country]['children'][item1['orgcode']] = item1
for item2 in get_child_orgs(item1['orgcode'], year):
hierarchy[country]['children'][item1['orgcode']]['children'][item2['orgcode']] = item2
# Only England and Wales hierarchies go deeper than this
if country in ('eng', 'wal'):
level3 = get_child_orgs(item2['orgcode'], year)
# Check not empty array
if level3 != []:
for item3 in level3:
hierarchy[country]['children'][item1['orgcode']]['children'][item2['orgcode']]['children'][item3['orgcode']] = item3
level4 = get_child_orgs(item3['orgcode'], year)
# Check not empty array
if level4 != []:
for item4 in level4:
hierarchy[country]['children'][item1['orgcode']]['children'][item2['orgcode']]['children'][item3['orgcode']]['children'][item4['orgcode']] = item4
# Save the completed hierarchy with pickle
file_name = 'hierarchy_' + str(year) + '.dat'
with open(file_name, 'wb') as out_file:
pickle.dump(hierarchy, out_file)
print('Success!')
问题
这似乎在大多数时候都有效,但是当嵌套for循环返回“NoneType不可迭代错误”时,它会感觉很乱并且有时会崩溃。我意识到这会产生大量的API调用,并且需要花费几分钟才能运行,但我无法看到解决方法,因为我希望已完成的层次结构可供离线使用,以便快速搜索数据。然后,我将以稍微不同的方式使用API来获取所选组织的实际医疗保健数据。
我的问题
是否有更简洁,更灵活的方法来实现组织层次结构的变量嵌套?
有没有办法更快地完成这项工作?
我对JSON相对缺乏经验,所以任何帮助都会受到赞赏。
答案 0 :(得分:1)
我认为这个问题可能更适合代码审查堆栈交换,但是当你提到你的代码有时崩溃并返回NoneType
错误时,我会给它带来怀疑的好处。
看看你的描述,这就是我的特色
每个组织都有一个列出其子组织的名称,组织代码和字典。 [API调用]将返回任何其他组织的直接子组织。
所以,这对我(以及它在样本数据中的表现)的意义在于,所有数据都是完全等效的;层次结构仅由于数据的嵌套而存在,并且不由任何特定节点的格式强制执行。
因此,这意味着您应该能够拥有一段代码来处理无限(或任意,如果您愿意)深树的嵌套。显然,你为API调用本身(get_child_orgs()
)执行此操作,因此只需复制它以构建树。
def populate_hierarchy(organization,year):
""" Recursively Populate the Organization Hierarchy
organization should be a dict with an "orgcode" key with a string value
and "children" key with a dict value.
year should be a 2-4 character string representing a year.
"""
orgcode = organization['orgcode']
## get_child_orgs returns a list of organizations
children = get_child_orgs(orgcode,year)
## get_child_orgs returns None on Errors
if children:
for child in children:
## Add child to the current organization's children, using
## orgcode as its key
organization['children'][child['orgcode']] = child
## Recursively populate the child's sub-hierarchy
populate_hierarchy(child,year)
## Technically, the way this is written, returning organization is
## pointless because we're modifying organization in place, but I'm
## doing it anyway to explicitly denote the end of the function
return organization
for country in hierarchy.values():
populate_hierarchy(country,year)
值得注意的是(因为您在原始代码中进行迭代之前检查空列表)for x in y
如果y
是空列表,NoneType
仍能正常运行,因此您无需检查
可能会出现get_child_orgs
错误,因为您在None
中捕获了错误,然后隐式返回level3 = get_child_orgs[etc...]
。因此,例如level3 = None
会产生if None != []:
;这导致下一行中的None
为True,然后您尝试使用for item3 in None:
迭代children
,这会引发错误。如上面的代码所述,这就是我检查threading/multiprocessing
的真实性的原因。
至于是否可以更快地完成此操作,您可以尝试使用pickle
模块。我只是不知道其中任何一个有多少利润有三个原因:
最后,我只是质疑json.dump/load
是否是存储信息的合适方法,或者如果你不仅仅更好地使用json
(对于记录,.dat
如果您将扩展名更改为s1r1=[[1,2,3,4,5,6],[2,4,6,8]]
uniq = [[j for j in k if not all(j in i for i in s1r1)] for k in s1r1]
,则模块无关紧。