Object destructuring in Python

Hello guys,

I would love to suggest fairly popular in JS world feature for Object Destructuring.

Usually it is implemented like

const person = {
    name: "John Doe",
    age: 31
}
const {name, age} = person;
// now we can reference name or age like
// const name = person.name

Python has similar syntax for lists and tuples. There is also itemgetter but it is kinda ugly.

Ideally it should work like this:

class Person:
   name: str
   age: int
   # constructor omitted for brevity

person = Person(name="John Doe", age=31)

# here we perform destructuring
# because interpreter already knows that person is an instance of a class it takes name as literal and can do something like getattr(person, 'name', None) under the hood
name, age = person


print(name, age)

This approach can be 100% compatible with tuples and lists.

Also it would make named tuples destructuring absolute breeze because it can rely on name instead of positional argument.

People are certainly interested, e.g.: Destructuring dicts and objects in Python - Stack Overflow - the question has been viewed 77000 times.

This approach can be scaled to dicts too.

1 Like

You can do this with dataclasses:

from dataclasses import dataclass, astuple

@dataclass
class Person:
    name: str
    age: int

person = Person(name="John Doe", age=31)

name, age = astuple(person)

print(name, age)
1 Like

As alternative to @tstefan , you could do

match person:
    case Person(name, age):
        pass
print(name, age)

Link to tutorial PEP 636 – Structural Pattern Matching: Tutorial | peps.python.org

4 Likes

The way it works in javascript is that the variable names are matched to key names so that

const {age, name} = person;  // order of age and name swapped

still assigns person.age to age but

const {name2, age2} = person;

sets both variables to null.

It’s not common in Python for a variable’s name to have such significance in behaviour.

6 Likes

This exactly what I don’t want to do.

The idea behind is to use names of variables as attribute’s names.

I don’t need another tuple.

What if I decide that I need only age? What if there is 20 other attributes?

For example:

gender, = person 

This is totally normal behavior.

Python doesn’t have declaration syntax, so it always be like

name = None

Javascript is simply using undefined instead of None. Nothing really changes.

Worst case we can model behavior of getattr(obj, ‘name’) which will throw if name attribute is not defined.

This is something I would love, but I much prefer Rust’s syntax over JavaScript’s (notice how Rust’s version has the object name on the left-hand side):

struct Person {
    name: String,
    age: u8,
}

let person = Person { name: "John Doe".to_string(), age: 31 };
let Person { name: name, age: age } = person;
println!("{name}: {age}");
@dataclass
class Person:
    name: str
    age: int


person = Person(name="John Doe", age=31)
Person(name=name, age=age) = person
print("{name}: {age}")
3 Likes

If you swap person and Person(…) and sprinkle with match, case and pass, Python already supports exactly that:

match person:
    case Person(name=n, age=a):
        pass

(using n and a to emphasise that those are the assigned variables, and name and age the field names)

2 Likes

@Dutcho I don’t think the OP is happy with writing all the names twice.

If they were, there are many possibilities. The easierst would be name=person.name; age=person.age. That would be shorter than the match statement.

1 Like

It actually already works :slightly_smiling_face:

from dataclasses import dataclass

@dataclass
class Person:
    name: str
    age: int

    def __iter__(self):
        # iterate through all fields in the order declared
        for field in self.__dataclass_fields__:
            yield getattr(self, field)


person = Person("John Doe", 31)
name, age = person
print(name, age)  # John Doe 31
1 Like

Rust syntax is explicit but bloated. This is exactly what I want to avoid.

Good syntax is concise, easy to interpret and intuitive.

attr_a, attr_b = some_object

Is easy to write, easy to understand, it is compact and already exists.

If we do a simple expansion to dicts and objects this will make code leaner.

The programming languages exist to make work of engineers easier. All of them take different routes and trying to address various concerns.

Rust designed to build system software, software that built to run forever. Hence, it must have explicit syntax to avoid memory leaks and accidental memory corruption. It is complicated for a purpose.

Python is designed to write a simple code that easy to interpret once you know a few basic constructs. This is why it has garbage collection and all relevant consequences. And this is a reason to keep the language lean.

And what is going to happen if I want to do this:

age, = person

?

Or this:

gender, name, age = person

I am not suggesting something out of whack.

I suggest simple, flexible solution with familiar syntax that would go along with language naturally without adding some hacks.

I think that some new way of object destruction would make sense, but it should not only work for named tuples and dataclasses, but also for dicts. This, however, makes the following syntax unsuitable:

because we can already write a, b = {'b':1, 'a':2}, which sets a=1 and b=2.

2 Likes

Which, of course, is why it’s completely impossible to run Python as your operating system… hmm.

I agree about that!

It does make sense to apply it to anything that has named attributes: dicts, objects, namedtuples.

I don’t see the benefit compared to simply doing this:

age = person.age

I meant to emphasize that it’s already valid syntax. It could (and likely would) break a lot of code.

Elis, imagine you have a class, or, worse, the class hierarchy where some parts are out of your control.

For example, you are using somebody’s else library and subclassing something to be able to access protected attributes.

Then your code requires use of 5-6 attributes where instance of this class becomes buried a few nested layers down.

So, according to your approach this simple thing

name, age, husband, children, parents = attorney.mother_in_law.husband.mother

would turn into:

name = attorney.mother_in_law.husband.mother.name
age = attorney.mother_in_law.husband.mother.age
husband = attorney.mother_in_law.husband.mother.husband
# the list goes on

or even with some extra step it is still scales poorly:

mother = attorney.mother_in_law.husband.mother
name = mother.name
age = mother.age
# ..and it continues to be repetitive

Good engineers aren’t paid by number of lines. And it is our choice to select what type of engineers we want to be.

My teacher always said that good engineers are lazy engineers. They don’t like repetitive work, they don’t do anything twice.

I am not trying to cajole anyone into my way of thinking. I am trying to simply show that some ways are easier when we apply ourselves to learn them.

This won’t break anything except namedtuples or generally bad code like it was shown with dict.

This will never work the way you want in python.

This is such an enormous backwards compatible break that it will not happen.

If you want exactly this syntax, give up. No need for further discussion.

You need to find a different syntax variation, a options for which have already been suggested.

Python 4 with breaking changes? It is absolutely normal to have them. PHP broke a ton of things and evolved into a much better language.