Комментарии 49
Типизируйте как можно раньше.
А лучше сразу пишите на статически типизированных языках
Как статически типизированный язык поможет в случае джейсона из стороннего источника, стесняюсь спросить?
Кроме того, ценность статической типизации не абсолютна, в очень многих случаях она просто мешает.
В домене (в бизнес логике) не должно быть словарей.
Разве что локально внутри одной функции: видно глазами его структуру.
JSON должен быть представлен в типизированный формат и валидирован (или тип должен принимать только валидные значения) чтобы быть передан в домен.
Иногда динамическая типизация мешает. Но с учётом вышесказанного
В домене не должно быть словарей.
Ну расскажите это создателям эрланга. Там кроме словарей — других составных типов данных всего два: кортеж и связный список.
Я по неопытности первое время в эликсире тащил повсюду структуры (которые суть словари с ограниченным набором ключей), а потом понял, что это вообще никому не нужно.
Правда, эрланг и эликсир полностью иммутабельны, но ведь даже если в корку питона никак не могут затащить freeze
, как в руби, всегда есть возможность самому написать десять строчек кода.
403 ОШИБКА
Просьба не может быть удовлетворена.
Дистрибутив Amazon CloudFront настроен на блокировку доступа из вашей страны
Ну ок, прочитал
immutable_types = set (( int , str ))
класс Frozen ( объект )
: def __init__ ( self , value )
: self._value = value
def __getattribute__ ( self , name ):
если name == '_value' : return super ( Frozen , self ) . __getattribute__ ( name )
v = getattr ( self . _value , name )
return v если v . __class__ в immutable_types иначе freeze ( v )
def __setattr__ ( self , name , value ):
if name == ' _value ' : super ( Frozen , self ) . __setattr__ ( name , value )
else : raise Exception ( "Невозможно изменить замороженный объект {0}" . format ( self._value ))
def freeze ( value ):
return Frozen ( value )
Словари только на чтение и чтобы IDE понимал из структуру было бы неплохо.
Вроде, есть библиотеки, но всё равно надо определить структуру отдельно
from types import MappingProxyType
from typing import TypedDict
class UserData(TypedDict):
name: str
age: int
active: bool
_data: UserData = {
'name': 'John Doe',
'age': 30,
'active': True
}
USER_DATA = MappingProxyType(_data)
А ещё лучше
// JavaScript - работает без отдельного объявления
const CONFIG = { name: "Vasya", is_active: true, level: 3 };
console.log(CONFIG.name);
чтобы IDE понимал из структуру
Зачем?
Но прям если очень надо, то вот PEP-589, вот Language Server, а вот инструкция, как прикрутить в neovim
.
Мешает чему? Возможности писать не типобезопасный код? Лени разработчика?
Поддерживаемости и расширяемости. Хорошему разработчику типы не нужны, а плохому — не помогут.
аннотации типов (в том же пайтоне) позволяют сделать код более явным и прозрачным и значительно упрощают жизнь программисту, который большую часть времени читает чужой код
использование аннотаций в связке с тайп-чекерами типа mypy позволяют избежать множества ошибок типизации.
Даже хорошие программисты могут допускать ошибки :) а Ваш тезис что-то сродни «хорошие программисты пишут код без багов»
Я уже молчу про тормознутость буквально всех динамически-типизированных языков (надеюсь, не придется объяснять как именно динамическая типизация негативно аффектит производительность)
P.S. За последние лет так 5 я видел ровно 0 проектов на Пайтоне, где type-annotations были бы опциональными/не использовались вовсе: везде они были обязательными + везде mypy / pyright. Другое дело, что огромное множество Python-программеров не умеют в типы и попросту не хотят с этим разбираться, что вырождается в конечном итоге в г*вно-код c повсеместными Any и т.д. Но тут, опять же, есть решение: хорошо настроенный тайп-чекер бьет за такое по рукам :)
Вы ведь в курсе, да, что питон — динамически типизирован, а аннотации типов прикручены сбоку и вообще никак в результате на исполняемый код не влияют, да?
не придется объяснять как именно динамическая типизация негативно аффектит производительность
Я не знаю, что такое «аффектить», но если имелось в виду «влиять на» — то никак. В рантайме хаскеля (и подавляющего большинства строго типизированных языков) — никаких типов нет из-за type erasure, которую придумали как раз ради повышения производительности.
Ну и по мелочи:
аннотации типов (в том же пайтоне) позволяют сделать код более явным и прозрачным
По сравнению с чем? С питоном без аннотаций? — Возможно. При этом скала насквозь типизирована, но всё еще порождает анекдоты типа такого:
Скрытый текст
Докладчик на конференции показывает с экрана код на скале, спрашивает: «Достаточно ясно, или увеличить шрифт?»
В аудитории неразборчивый ропот. Докладчик увеличивает шрифт.
— Так понятнее?
Голос из зала:
— Нет, это по-прежнему код на скале.
использование аннотаций в связке с тайп-чекерами типа mypy позволяют избежать множества ошибок типизации
Паттерн-матчинг как в эрланге позволяет это сделать гораздо более внятно. Отсутствие типов, кроме самых простейших (список, мап, кортеж), и гарды — позволяют это сделать еще надежнее.
Даже хорошие программисты могут допускать ошибки
Конечно. Но обычно не с типами (я что-то никогда не видел вызов функции store_user(product)
или типа того за всю свою 30-летнюю карьеру), зато ошибку четности, или off-by-one, или плюс вместо минуса — а именно такие ошибки самые частые — никакие типы, кроме зависимых, не отловят (но завтипов нет даже в хаскеле, а идрис — наверное никогда уже, к сожалению, не станет production-ready.)
На скольких языках, кроме питона, вам доводилось писать продакшн-код? Потому что если сравнивать просто питон и питон с mypy — то это не про типы, а про общую импотенцию языка.
Вы ведь в курсе, да, что питон — динамически типизирован, а аннотации типов прикручены сбоку и вообще никак в результате на исполняемый код не влияют, да?
в курсе, конечно. Мне даже как-то обидно, что вы подумали, что я могу быть не в курсе :)
Я не знаю, что такое «аффектить», но если имелось в виду «влиять на» — то никак. В рантайме хаскеля (и подавляющего большинства строго типизированных языков) — никаких типов нет из-за type erasure, которую придумали как раз ради повышения производительности.
Я про то, что вычисление типа переменной в рантайме и выделение места на стеке под ее хранение - это runtime-overhead.
По сравнению с чем? С питоном без аннотаций? — Возможно. При этом скала насквозь типизирована, но всё еще порождает анекдоты типа такого:
да, по сравнению с Питоном без аннотаций. Код на Скале (лично для меня) - "темный лес", но связано это с тем, что я не силен в ФП, а не с тем, что Скала - статически типизированный язык
Конечно. Но обычно не с типами
Одна из достаточно частных проблем в Python - это когда функция возвращает, например, Optional[T]
, а программист использует ее результат, закладываясь на то, что там будет T
и только T
.
Но проблема Пайтона в том, что эта потенциальная проблема в некорректном использовании такой функции может быть идентифицирована только через тайп-чекер, либо же, она проявит себя через N лет и попросту свалит прод :)
Возможно, я изначально не совсем верно понял Ваш посыл, но лично я говорил сугубо в контексте Пайтона, и да, я подсознательно сравнивал Пайтон с тайп-аннотациями и без оных.
В целом, если сравнивать различные ЯП, то мой опыт позволяет лишь сравнить пайтон с растом, но не более того
Я про то, что вычисление типа переменной в рантайме и выделение места на стеке под ее хранение — это runtime-overhead.
Еще раз: с тайпчекером, или без, — в рантайме код одинаковый. Ну и весь мой опыт показывает, что бутылочное горлышко всегда закопано в пользовательском коде, а не в оверхеде из-за неоптимального выделения места на стеке языком. Всегда.
функция возвращает, например,
Optional[T]
, а программист использует ее результат, закладываясь на то, что там будетT
и толькоT
.
Ну не знаю, я и без типов проверю на None
практически всегда.
Еще раз: с тайпчекером, или без, — в рантайме код одинаковый.
не совсем понятно, причем тут "тайпчекер", если речь про статическую / динамическую типизацию в целом? В статически-типизированных языках информация о типах переменных (соответственно и об их размере на стэке) известна на этапе компиляции, в динамически-типизированных языках - эта информация вычисляется в рантайме.
Аргумент про "бутылочное горлышко" мне тоже не очень понятен: я могу сколько угодно г*вно-кодить на расте или писать около-идеальный, идеоматически верный код на пайтоне с кучей оптимизаций, в 99.9 % код на расте будет во множество раз быстрее
Аргумент про бутылочное горлышко, очевидно, работает только в рамках одного языка.
Но не могу не напомнить, что создатели языка Раст не смогли на собственноручном созданном языке написать проект, ради которого язык и был создан. Так что физзбазз на нем — юудет, очевидно — быстрее, а для чего-то серьезного я бы поостерегся его выбирать.
Аргумент про бутылочное горлышко, очевидно, работает только в рамках одного языка.
Но не могу не напомнить, что создатели языка Раст не смогли на собственноручном созданном языке написать проект, ради которого язык и был создан. Так что физзбазз на нем — будет, очевидно — быстрее, но для чего-то серьезного я бы поостерегся его выбирать.
Я не знаю, что такое «аффектить», но если имелось в виду «влиять на» — то никак. В рантайме хаскеля (и подавляющего большинства строго типизированных языков) — никаких типов нет из-за type erasure, которую придумали как раз ради повышения производительности.
Совершенно очевидно, что типизация влияет на производительность, потому что если типы не проверены в компилтайме, то эквивалентные им рантайм-метки придётся проверять, собственно, в рантайме (если ваш язык собирается давать хотя бы минимальную дружественность к разработчику).
Если у вас написано a + b
, и в вашем языке +
может означать сложение и int
ов, и double
ов, и строк, и short
ов каких-нибудь, и тензоров из вашей любимой библиотеки, то вам надо будет в рантайме проверять конкретную метку a
, конкретную метку b
, иметь if
с логикой для конвертации в какой-то общий тип, и только потом суммировать. Компиляция с типами же всё это делает в компилтайме, и в рантайме остаётся только условный ассемблерный addword
или call stringConcat
. Более того, если это происходит в компилтайме, то открываются возможности для оптимизации после такого инлайнинга.
И именно поэтому когда в хаскеле типы таки не стираются (например, компилятор не мономорфизирует полиморфную функцию с типом Num a =>
, вместо этого таская набор указателей на функции, коим является словарь Num a
под капотом, или у вас там экзистенциальный тип затесался где), то всё это может работать медленнее на порядок-полтора.
В моём языке a + b
может обозначать только арифметическое сложение, а тип «тензор» невозможен, как и любые другие пользовательские типы.
А числа в вашем языке тоже всего одного типа рантайм-представления, как в JS, или там есть отдельно целые, отдельно с плавающей точкой, и так далее?
И что будет, если в a
окажется, скажем, строка? Рантайм пойдёт, возьмёт первые 4-8 байт её представления, проинтерпретирует как число, и сложит с b
? Или произойдёт что-то другое?
Ну и да, тут речь изначально о питоне шла, а там такой рантайм-диспатч по меткам есть точно, и тут уже надо уточнять, обсуждаем мы все динамически типизированные языки, или некоторые.
Не позволяйте Питоном портить ваш код!
Словари это лучшее, что есть в питоне. Просто нужно уметь их готовить.
Словари должны существовать локально. В пределах одного экрана словарь заполнили и использовали.
А если ХЗ где искать кто его заполнил в 10 изменил и что там вообще может быть то это караул
Мне всегда смешно читать про предложения обернуть нормальный тип данных, типа словаря, в триста хрупких слоёв абстракции, при том, что в эрланге (и в эликсире), например, типов данных сложнее словаря (map) просто нет, и это вообще никому не мешает писать код, про который во внешнем мире известно обычно то, что он — fault tolerant по определению.
Ну это уже демагогия какая-то. Как именно используются мапы в проектах, что вы упоминаете? За счет чего достигается fault tolerant упоминаемого вами кода? Пока что вы просто выражаете несогласие, но не пытаетесь его аргументировать
Как именно используются мапы в проектах, что вы упоминаете?
Наравне с кортежами и связными списками — как единственный способ представления данных.
За счет чего достигается fault tolerancy упоминаемого вами кода?
Я наивно полагал, что диссертацию Джо Армстронга прочитали все люди, мнящие себя разработчиками (ну, или хотя бы слышали о ней краем уха). https://nodramadevops.com/2019/04/joe-armstrong-and-fault-tolerant-concurrent-systems/
Используем mypy
Не используем cast
Кривые диеты больше не получается использовать
И все, проблема решена. Точнее её больше нет ибо ей неоткуда взяться
Проекты на python в целом не очень поддерживаются, да и среднее качество кода оставляет желать лучшего. Если вы пишите что то больше чем на 1000 строк кода, просто не делайте это на питоне. Это язык для студентов и дев опсов. В остальных случаях лучше его не использовать. Да на чем угодно можно писать качественный код но на python это гораздо сложнее.
Ну так дело не в Python. Я сейчас в один чужой проект влился - там на C# тоже самое: json и dictionary. Иногда даже вложенные.
Бред полный, я работаю с проектом где множество сервисов, десятки тысяч строк кода на питоне. И все прекрасно поддерживается, потому что команда нормальная.
В хранилищах пар ключ-значение аннотируйте словари как мэппинги
этот совет выглядит скорее вредным, потому что typing `dict[str, str]` буквально и описывает hashmap неопределенной длины, но однородного содержания.
Подводные камни прячутся в гибкости. Мы можем описать коллекцию книг как:
all_books: list[Book] = []
А можем как хэшмапу с быстрым доступом по выбранному свойству:
books_from_names: dict[str, Book] = {book.name: book for book in all_books}
Но гибкость нам позволяет и сам Book описать как словарь:
dict_book = {
"name": "Azbuka",
"pages": 33,
"weight": 444.4,
"category": Category.HORROR,
}
Тем не менее легко выделить критерии, по которым легко признать, что у вас на руках Object:
Значения, вероятно, неоднородны (хотя легко придумать описание книги, подходящее под `dict[str, str]`)
Ключи - это не свойство значения, доступного по ключу, это свойство Object.
Длина словаря определена набором свойств описываемого Object
Таким образом, два последних пункта буквально сигнализируют о том что мы объект и его поля запихали в хэшмапу.
И такое можно делать - но желательно осознанно. Иначе по коду могут начать летать словари словарей, примерно с таким тайпингом:
dict[str | int, dict[str, dict | str] | float | str | int]
В качестве же компенсации за критику присоединюсь к рекомендации причинять pydantic. Начиная с V2 он вырос на голову относительно себя:
Написан на rust (В смысле, прибавил в производительности)
Через Annotated можно делать крутяцкие валидации-сериализации и переиспользовать их
Дженерики начали работать как дженерики (в отличие от батьки-питона) - в том числе и с предыдущим пунктом.
Умеет включать в свои модели данных датаклассы и восстанавливать их при парсинге json
Имеет TypeAdapter, который позволить спарсить из json в том числе чистый dataclass, такое вот содружество.
Наверняка я что-то ещё интересного упустил, но и так вроде неплохо звучит.
Одна проблема остаётся:
Словари, которые можно сформировать/ изменить в разных частях программы это write only code. То есть MyPy может что-то отловить, но человек не может.
Дикты инварианты, потому что мутабельны. Тип Mapping же ковариантный. К тому же, с аннотацией Mapping я могу подсунуть как dict, так и frozendict.
Может кто знает, появилось ли что-то кроссплатформенное, чтобы dto между сервисами в принципе не превращать в json? Что-то вроде protobuf+grpc, но не так ублюдочно сделанное, как последний под python, где вместо фичей языка используется доступ к гландам через ж, что пользоваться этим куском неприятно и хочется мыть руки постоянно. Чтобы какой-то объект из api доставать и сразу с ним работать, без десереализаций и маппингов.
Ну и естественно, чтобы между Java/C#/Go/Python/Rust/JS как влитое интеропалось
Есть такое?
Подскажите плз. Если мне из сети прилетает от 500 json/sec до 5000 json/sec и нет времени на сериализацию в объект и обратно. И актуальность данных примерно 0.5 секунды(если из очереди данные придут с задержкой 1 сек то это уже тухлые данные) что делать? Переходить на C? :) есть подозрения что мой код не успевает за данными
У меня кластер из трёх машин на эликсире хавает до 80К/сек в пике, десериализует, трансформирует с некоторой [несложной] математикой и сериализует обратно с latency не выходящей за рамки 100ms примерно.
Я python с трудом освоил, посмотрел в вики на elixir - выглядит не читабельно, максимально интуитивно не понятно :))) . Если знаете какую-то хорошую документацию по elixir, буду благодарен ссылке. Вряд-ли освою но хотя бы представление буду иметь.
У эликсира одна из лучших документаций.
— https://hexdocs.pm/elixir/introduction.html
— https://elixir-lang.org/learning.html
— лучшая книжка (переведена на русский) — «Эликсир в действии» Саши Юрича
Ещё уточню, 100 ms это на обработку(десериализация, математика, сериализация) примерно 5к-8к json сообщений? Для одного сообщения это явно многовато просто :)
Переходить на C?
Для такого Golang есть. Прост (слишком), оптимизируем, компилируем. Python из-за своей специфики уж очень сложно оптимизировать.
Промах. Этот комент надо удалить :)
Словарь - это (англ)Map? Я не прикалываюсь, просто не попадалось такое употребление. Обычно (у нас) говорят "карта", "мэпа", изредка - "отображение".
По поводу изменяемости словарей: существует immutable аналог dict - frozendict. К слову, pydantic-модели, как и дэйтаклассы, по умолчанию тоже мутабельные.
Если вам действительно нужна валидация - pydantic-модели это решение. Но, в реальности, валидация не всегда нужна, и тогда pydantic-модели лишь привносят overhead на доп. сериализацию/десериализацию данных (да-да, даже в версии 2 pydantic достаточно тормознутый)
Не позволяйте словарям портить ваш код