Destructuring dicts and objects in Python

You can use operator module from standard library as follows:

from operator import attrgetter
id, email, gender, username = attrgetter('id', 'email', 'gender', 'username')(current_user)
print(id, email, gender, username)

In case you have a dict like from your example

currentUser = {
  "id": 24,
  "name": "John Doe",
  "website": "http://mywebsite.com",
  "description": "I am an actor",
  "email": "[email protected]",
  "gender": "M",
  "phone_number": "+12345678",
  "username": "johndoe",
  "birth_date": "1991-02-23",
  "followers": 46263,
  "following": 345,
  "like": 204,
  "comments": 9
}

just use itemgetter instead of attrgetter:

from operator import itemgetter
id, email, gender, username = itemgetter('id', 'email', 'gender', 'username')(currentUser)
print(id, email, gender, username)

In Python 3.10 you can do it using match:

match current_user:
    case User(id=id, username=username):
        # In this block, id = current_user.id, username = current_user.username

See https://docs.python.org/3.10/tutorial/controlflow.html#match-statements


Don't flatten the arguments in the first place. When you write a 8-ary function like you did with User, you're bound to make mistakes like passing arguments in the wrong order.

Which of the following will produce User you intend?

  1. User(24, "Jon Doe", "http://mywebsite.com", "I am an actor", "[email protected]", "M", "+12345678", "johndoe")
  2. User(24, "Jon Doe", "http://mywebsite.com", "I am an actor", "[email protected]", "+12345678", "M", "johndoe")

Impossible to know! If your function takes a descriptor, you do not have this problem -

class User:
  def __init__ (self, desc = {}):
    self.desc = desc # whitelist items, if necessary
  
  def __str__ (self):
    # invent our own "destructuring" syntax
    [ name, age, gender ] = \
      destructure(self.desc, 'name', 'age', 'gender')

    return f"{name} ({gender}) is {age} years old"

# create users with a "descriptor"
u = User({ 'age': 2, 'gender': 'M' })
v = User({ 'gender': 'F', 'age': 3 })
x = User({ 'gender': 'F', 'name': 'Alice', 'age': 4 })

print(u) # None (M) is 2 years old
print(v) # None (F) is 3 years old
print(x) # Alice (F) is 4 years old

We can define our own destructure as -

def destructure (d, *keys):
  return [ d[k] if k in d else None for k in keys ]

This still could result in long chains, but the order is dependent on the caller, therefore it's not fragile like the 8-ary function in the original question -

[ name, age, gender ] = \
  destructure(self.desc, 'name', 'age', 'gender')

# works the same as

[ gender, name, age ] = \
  destructure(self.desc, 'gender', 'name', 'age')

Another option is to use keyword arguments -

class User:
  def __init__ (self, **desc):
    self.desc = desc # whitelist items, if necessary

  def __str__ (self):
    [ name, age, gender ] = \
      destructure(self.desc, 'name', 'age', 'gender')

    return f"{name} ({gender}) is {age} years old"

# create users with keyword arguments
u = User(age = 2, gender = 'M')
v = User(gender = 'F', age = 3)
x = User(gender = 'F', name = 'Alice', age = 4)

print(u) # None (M) is 2 years old
print(v) # None (F) is 3 years old
print(x) # Alice (F) is 4 years old

Building off of other answers, I would recommend also using Python's dataclasses and use __getitem__ to get specific fields:

from dataclasses import astuple, dataclass

@dataclass
class User:
    id: int
    name: str
    website: str
    description: str
    email: str
    gender: str
    phone_number: str
    username: str
    
    def __iter__(self):
        return iter(astuple(self))
    
    def __getitem__(self, keys):
        return iter(getattr(self, k) for k in keys)
        

current_user = User(id=24, name="Jon Doe", website="http://mywebsite.com", description="I am an actor", email="[email protected]", gender="M", phone_number="+12345678", username="johndoe")

# Access fields sequentially:
id, _, email, *_ = current_user
# Access fields out of order:
id, email, gender, username = current_user["id", "email", "gender", "username"]

Tags:

Python