Home > Net >  Clean more pythonic way to loop over nested keys that don't always exist
Clean more pythonic way to loop over nested keys that don't always exist

Time:01-20

I'm trying to clean up this code.

For example

data = [
{
    "version": "11g",
    "edition": "Enterprise",
    "build_number": "11.2.0.4.0",
    "path": "",
    "devices": [
      {
        "host_name": "server1",
        "manufacturer": "HP",
      },
      {
        "host_name": "server2",
        "manufacturer": "HP",
      }
    ]
},
{
    "version": "11g",
    "edition": "Enterprise",
    "build_number": "11.2.0.4.0",
    "path": "",
}
]

for each in data:
    version = each["version"]
    if "devices" in each:
        for server in each["devices"]:
            hostname = server["host_name"]
            print(version, hostname)
    else:
        hostname = None
        print(version, hostname)

This prints out the following which is what I want. Is there a way to do it without using two print statements?

11g server1

11g server2

12g None

CodePudding user response:

Null Object (1 print)

If you want to print (version, None) for each without "devices", then something like this slould do:

for each in data:
    version = each["version"]
    for server in each.get("devices", [{"host_name": None}]):
        hostname = server["host_name"]
        print(version, hostname)

Explanation: if each doesn't have "devices", get() with return a list with one fake device ([{"host_name": None}]), with a hostname that you want to print when there's no device.

For readability, I would save that fake device to a named constant:

NONE_DEVICE = {"host_name": None}
for each in data:
    version = each["version"]
    for server in each.get("devices", [NONE_DEVICE]):
        hostname = server["host_name"]
        print(version, hostname)

This is similar to "Null Object" design pattern, where you return a special "empty" object when there is no object.

My solution (2 prints)

However, it's debatable whether it's appropriate to fake missing devices with some special object instead of just admitting that there's no devices. I personally wouldn't get rid of the if statement and the second print. I would clean up this code in a different area:

# `server` is renamed to `device`,
# so that the same thing doesn't have two different names.
# I'd also change `each` to something meaningful.
for each in data:
    if "devices" not in each:
        print(each["version"], None)
        continue
    for device in each["devices"]:
        print(each["version"], device["host_name"])

CodePudding user response:

Another solution would be to use list comprehensions. Shorter but less readable. Lets start with the simple case: print version if no device present:

[print(row['version']) for row in data if 'devices' not in row]

But with the nested list comprehension things get less readable:

[print(row['version'], [device['host_name'] for device in row['devices']]) for row in data if 'devices' in row]
  •  Tags:  
  • Related