Как сделать словарь к которому можно обращаться через точку
Необходимо написать словарь к которому можно обращаться как к словарю aiogram'a.
Пример, есть словарь genMessage - это самый обычный словарь:
genMessage = {
'from_user': {
'id': userId,
'username': username,
'full_name': fullName
}
}
И к этому словарю можно обращаться только так:
genMessage['from_user']['id']
А мне необходимо обращаться к словарю через точку, как к aiogram словарю, пример:
genMessage.from_user.id
Ответы (2 шт):
Накидал пример класса-обертки:
- Суть в магическом методе
__getattr__, который вызывается при запросе поля-атрибута у объекта - Например, при
x.from_userв__getattr__будет передана строкаfrom_user - Чтобы можно было продолжать цепочку вызова понадобится возвращать объект
WrapperMapс новым значением
Пример:
class WrapperMap:
def __init__(self, d: dict):
self.d = d
def get_value(self):
return self.d
def __getattr__(self, item: str) -> 'WrapperMap':
return self.__class__(self.d.get(item))
def __repr__(self):
return repr(self.d)
genMessage = {
'from_user': {
'id': 123,
'username': "username",
'full_name': "fullName"
}
}
x = WrapperMap(genMessage)
print(x.from_user.id, type(x.from_user.id))
# 123 <class '__main__.WrapperMap'>
print(x.from_user.username.get_value())
# username
Можно немного дописать __getattr__, чтобы возвращать обертку только для словарей:
def __getattr__(self, item: str):
value = self.d.get(item)
if isinstance(value, dict):
return self.__class__(value)
return value
Тогда для получения значений остальных типов не придется вызывать метод get_value:
x = WrapperMap(genMessage)
print(x.from_user, type(x.from_user))
# {'id': 123, 'username': 'username', 'full_name': 'fullName'} <class '__main__.WrapperMap'>
print(x.from_user.id, type(x.from_user.id))
# 123 <class 'int'>
print(x.from_user.username, type(x.from_user.username))
# username <class 'str'>
"Энтерпрайзный" вариант: описываем "схему" словаря/json, т.е. все объекты, которые встречаются внутри словаря, и отношения между ними, потом парсим словарь в эти объекты, и потом уже обращаемся к полям этих объектов через точку.
Один из плюсов такого варианта - IDE будет знать, какие поля у этих объектов есть, и сможет подсказать поле через автодополнение. Дополнительно, в классах можно прикрутить валидацию значений (через тот же pydantic), и, например, если с клиента передаются некорректные данные, сервер сможет это проконтролировать, и вернуть на клиент ошибку с конкретным описанием проблемы.
Пример на pydantic (Python 3.6+, pip install pydantic):
from pydantic import BaseModel
class User(BaseModel):
id: int
username: str
full_name: str
class Message(BaseModel):
from_user: User
genMessage = {
'from_user': {
'id': 1,
'username': 'johndoe',
'full_name': 'John Doe'
}
}
message = Message.parse_obj(genMessage)
print(message.from_user.id)
Пример на dataclasses ("из коробки" в Python 3.7+ или установить через pip install dataclasses на Python 3.6) + dacite (pip install dacite):
from dataclasses import dataclass
from dacite import from_dict
@dataclass
class User:
id: int
username: str
full_name: str
@dataclass
class Message:
from_user: User
genMessage = {
'from_user': {
'id': 1,
'username': 'johndoe',
'full_name': 'John Doe'
}
}
message = from_dict(data_class=Message, data=genMessage)
print(message.from_user.id)