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?
User(24, "Jon Doe", "http://mywebsite.com", "I am an actor", "[email protected]", "M", "+12345678", "johndoe")
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"]