Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set attribute on an object given a dotted path?

Tags:

python

Given an object that can be a nested structure of dict-like or array-like objects. What is the pythonic way to set a value on that object given a dotted path as a string?

Example:

obj = [
    {'a': [1, 2]},
    {'b': {
        'c': [3, 4],
    }},
]

path = '1.b.c.0'

# operation that sets a value at the given path, e.g.
obj[path] = 5

# or
set_value(obj, path, 5)

The above call/assignment should replace the 3 in the example with a 5.

Note: The path can contain list indices as well as keys. Every level of the object can be an array or a dict or something that behaves like that.

The solution should be roughly like what the npm package object-path does in javascript.

like image 466
trixn Avatar asked Aug 16 '18 12:08

trixn


People also ask

How do you fix an object that has no attribute?

The Python "AttributeError: 'list' object has no attribute" occurs when we access an attribute that doesn't exist on a list. To solve the error, access the list element at a specific index or correct the assignment.

How do I add an attribute to a class?

Adding attributes to a Python class is very straight forward, you just use the '. ' operator after an instance of the class with whatever arbitrary name you want the attribute to be called, followed by its value.


2 Answers

A non-recursive version, which also works for numeric dictionary keys:

from collections.abc import MutableMapping

def set_value_at_path(obj, path, value):
    *parts, last = path.split('.')

    for part in parts:
        if isinstance(obj, MutableMapping):
            obj = obj[part]
        else:
            obj = obj[int(part)]

    if isinstance(obj, MutableMapping):
        obj[last] = value
    else:
        obj[int(last)] = value
like image 89
Eugene Yarmash Avatar answered Nov 14 '22 23:11

Eugene Yarmash


Sounds like a job for recursion and str.partition:

def set_value(obj, path, val):
    first, sep, rest = path.partition(".")
    if first.isnumeric():
        first = int(first)
    if rest:
        new_obj = obj[first]
        set_value(new_obj, rest, val)
    else:
        obj[first] = val

Because 1 is not the same as "1", I'm making a compromise here: If something looks like a number, it's treated as a number.

You could get this to behave on custom objects, but not really on builtin types without awful hackery, because Python doesn't have an equivalent to object.prototype.

like image 29
L3viathan Avatar answered Nov 14 '22 23:11

L3viathan