Comments 34
И ни единого слова о датаклассах...
На самом деле с датаклассами огрооомные проблемы, что с наследованием, что с представлением их как обычных словарей. Я некоторое время был влюблен в typing.TypedDict
, но и у них проблемы с тем, что update
требует кастов. В итоге, пришлось велосипедить свой класс, который очень похож на dataclass
, но на самом деле UserDict
В общем, счейчас в питоне нет правильного решения для типизированных словарей.
А если серьёзно – это же два совершенно разных случая (нужно упасть при отсутствии значения или нужно вернуть дефолтное значение). Вернуть дефолт там, где надо упасть – как бы не хуже обратной ошибки: при этом ошибка «заметается под ковёр» и всплывёт в труднопредсказуемом месте.
Естественно, они и вызываются по разному.
Если вы хотите typesafety в этом месте, то надо использовать нормальный Option. В Питон его не завезли, правда, так что страдайте. set_default — замечательный метод словить "молча работает неправильно" там, где этого не ожидают. exception нам обещает либо веселье при тестировании, либо непротестированный код в except-ветке.
Короче, питон и typesafety не совместимы. Насколько безопасными являются утки? А если они при этом выглядят как утки, крякают как утки и плавают как утки? Наверное, это утки, а не ремни безопасности.
Я не очень люблю, когда монады выпячиваются на пользователя (программиста). Как абстрактная идея при написании интерфейсов или синтаксиса языка — да, а вот заставлять людей с этим жить (со словом "монада") — это перебор. Из всех слов в CS, "монада" единственное, не имеющее смысла (и подразумеваемых свойств).
Option в Rust прекрасно реализует всё, что полезно, и не грузит мозги необходимостью думать про монады (мозги пригодятся для раздумий о lifetimes),
Рекомендую так же не использовать обращение к атрибутам через точку (some_object.some_attr). Ведь если атрибут отсутствует, мы получим AttributeError. Используйте функцию getattr или метод __getattribute__().
Вообще рекомендую воздержаться от использования потенциально опасных методов структур данных, которые выбрасывают ошибку, например, remove() или index()
По этим же причинам не стоит использовать срезы. Используйте вместо них циклы.
Ибо ошибки это плохо, а мы хотим быть хорошими программистами
</сарказм>
Какие-то «вредные советы» получаются.
А что делать, если дефолта нет?
А если бизнес-логика должна обрабатывать отсутсвие ключа в словаре?
В статье категорично предлагается get(), хотя он, очевидно, не решает вышеназванные вопросы, а просто переносит «проблему» в другое место, где точно так же нужно будет писать if, но сравнивать уже возвращенное значение со значением по-умолчанию. Как-то не видно преимуществ, если честно.
x = d.get('x', None)
if x is None:
....
То есть, для обработки дефолта — потом все равно делается особая ветка кода. По сути, оба варианта (c get() и с try/except) работают одинаково, только выглядят иначе. Но они по разному выглядят в коде. И тут у подхода через исключения есть огромный плюс — по нему нам проще читать код. Если мы пытаемся понять саму логику кода — то читаем обычный код (и он короткий, простой), и «по диагонали» проглядываем обработку exception, которая прописана в другом месте.
И, как всегда, ни единого слова про defaultdict
Какой-то однобокий пример, если нужно дефолтное значение то это один из вариантов, но иногда нужно чтобы упало как раз в этом месте.
Ключи в словаре не имеют такого явного порядка расположения, который есть у элементов списка
Эта статья уже устарела. В Python 3.6 порядок ключей по добавлению уже был как побочный эффект реализации, в Python 3.7+ расположение ключей в словаре в порядке добавления закреплено на уровне языка. В Python 3.8 появилась поддержка reversed для словарей.
OrderedDict довольно часто бывает полезным. Почему бы не сделать словарь упорядоченным by design, более того если это достигается бесплатно. В Py36 взяли более эффективную реализацию словаря из PyPy, которая имела такое свойство, затем просто закрепили гарантию упорядоченности ключей на уровне языка. Теперь любая реализация Python должна обеспечивать упорядоченность ключей по добавлению. Не вижу ничего плохого.
За что купил, за то продаю.
https://morepypy.blogspot.com/2015/01/faster-more-memory-efficient-and-more.html
Получал и буду продолжать получать значения из словаря через квадратные скобки. Если вы так беспокоитесь за содержимое внутри (а один из самых распространённых кейсов это получение JSON в RestAPI) то используйте JSON schema. Там можно досконально описать структуру словаря без всяких if и try/except
Немного не в тему, но всё-же скажу.
В pandas к колонке можно обратиться как df.column, так и df['column'].
Как они это сделали?
(Поправьте меня если я что-то не так сказал)
Очень не рекомендую этим пользоваться, но как-то так
class UglyDict(dict):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__dict__ = self
my_dict = UglyDict({"key": "value"})
print(my_dict["key"]) # "value"
print(my_dict.key) # "value"
my_dict.key2 = "test"
print(my_dict) # {'key': 'value', 'key2': 'test'}
Просто переопределили методы __getattr__
и __setattr__
, никакой особой магии там нет. Смотрите код: https://github.com/pandas-dev/pandas/blob/6010141a83234652a9a676df38d8d13f183bad2b/pandas/core/generic.py#L5216-L5273
pandas DataFrame — это не словарь, это очень-очень большой класс с кучей барахла, который мимикрирует под NumPy массив в некоторых операциях с векторизацией и предоставляет ещё кучу всяких методов, в том числе реализацию методов __getitem__
и __setitem__
для поддержки []
.
А про магические методы у классов в Python почитайте эту статью если интересно:
https://habr.com/ru/post/186608/
Питонисты, прекратите использовать квадратные скобки для получения значений из словаря