В угоду обратной совместимости, многие вещи в тайпскрипте возвращают any, вместо нормального типа. Если бы Response.json() возвращал unknown, то подобной проблемы бы не возникло и программисту пришлось бы разбираться в чём дело(конечно, многие бы просто забили и скастили бы тип к нужному, но всё же).
Это всё круто, но как ты ошибки не возвращай, всегда будет одна проблема - компилятор не скажет, где ты забыл обработать ошибку. Один раз не проверил что вернула функция, у тебя с большой вероятностью сегфолт, который ещё попробуй исправь. zig такого не позволяет. Хотя, на самом деле, zig тоже не очень безопасный в этом плане. Забыл освободить ресурс через defer и прога падает или память утекает, но в нём хотя бы есть DebugAllocator и стек вызовов, что позволяет ошибки отловить достаточно рано. То есть на корню все проблемы системного программирования zig не решает, но он однозначно делает многие вещи лучше C.
Я просто не понял, что @Serpentine предлагает сделать, чтобы избежать UB непосредственно в функции lower. Какой бы тип не был выбран для строки(указатель, указатель+длина), всё равно пользователь функции сможет передать испорченные данные.
Обожаю zod, использую его во всех проектах, но хочу слегка заступиться за yup. Никогда им не пользовался, но первый пункт(определение схемы и вывод типа объекта) меня очень сильно смутил и после быстрого гуглежа оказалось, что статья вводит в заблуждение. Оказывается, yup в плане схемы мало чем отличается от zod - определяем схему кодом, выводим из неё тип. Валидация данных в yup тоже типобезопасная, по поводу интеграции с React сказать нечего. То есть или документация yup врёт, либо автор статьи неправильно пользовался библиотекой, либо я что-то неправильно понял.
Согласен, что использование Any лучше избегать любыми способами. Но вот обобщённый тип для владельца дескриптора считаю оверкиллом, так как пользы в большинстве случаев он никакой не представляет. В примере из статьи, дескриптор TypedAttribute использует исключительно словарь экземпляра владельца дескриптора, то есть instance: object вполне хватит. Если от instance требуется обладать какой-то определённой логикой, то прямо так и пишем instance: HasSomeBehaviour. И к тому же, если дескрипторы которые выступают в роли декоратора могут получить тип владельца автоматически, то вот для обычных дескрипторов - это будет выглядеть так себе.
# привет из sqlalchemy
class MyEntity(Base):
id: Mapped[int, MyEntity] = mapped_column()
email: Mapped[str, MyEntity] = mapped_column()
# и так далее
Выглядит не прямо ужасно, но если mapped_column никак не будет использовать обобщённый тип владельца, то это просто захламление кода.
С константами есть проблема - IDE/typechecker не станет ругаться, если не покрыть все значения. С перечислениями(Enum/Literal/Union - неважно), ситуация принципиально иная. Например:
Константы без else ветки
FAIL = 1
SUCCESS = 2
PROCESSING = 3
def process(status: int) -> str: # typechecker ругается, что не во всех случаях возвращается строка
if status == FAIL:
return 'fail'
elif status == SUCCESS:
return 'success'
elif status == PROCESSING:
return 'processing'
В этом случае тайпчекер будет ругаться, так как не все возможные значения для status покрыты. Придётся добавить ветку else. Мало того, что нужно будет как-то обработать невозможное значение для status, так ещё и в случае появления новой константы для статуса, мы никак не узнаем, что не обработали её.
В случае же с перечислениями, мы не обязаны иметь ветку else и ещё до запуска узнаем о том, что не обработали одно из возможных значений для статуса. Пример:
Перечисления без else ветки
class Status(enum.Enum):
FAIL = enum.auto()
SUCCESS = enum.auto()
PROCESSING = enum.auto()
def process_status(status: Status) -> str: # всё в порядке, typechecker знает, что мы обработали все возможные значения
if status is Status.FAIL:
return 'fail'
elif status is Status.SUCCESS:
return 'success'
elif status is Status.PROCESSING:
return 'processing'
Во второй раз обнаружил, что документация почти ни в чем не совпадает с первым заходом. Т.е. за два года поменялось почти всё, знания полностью обесценились. Старый код работал, но в документации не было тех способов, которыми всё было сделано в первый раз.
Так бывает, что библиотекам может понадобиться мажорная миграция. Алхимии она определённо пошла на пользу(ИМХО). А при необходимости поправить старый код, документация к легаси версии 1.4 всё ещё доступна.
Нужно было сделать простой листинг записей с разбиением на страницы. В ORM был метод paginate, его и использовал.
После этого к подобным оберткам отношусь скептически.
Возможно вы действительно использовали не голую алхимию, а обёртку над ним? Не припоминаю в sqlalchemy метода paginate да и вообще вспомогательного функционала для пагинации. В этом плане он всегда был близок к нативному sql, заставляю использовать offset/limit или самописные курсоры. Гугл выдаёт что метод paginate есть в flask_sqlalchemy, возможно дело в нём.
Если у вас .NET 8 или выше, то лучше использовать Collection expressions. Хороший баланс производительности и удобства.
// вместо трёх вызовов items.Add, будет один вызов билдера,
// который принимает Span<T> содержащий перечисленные элементы
List<int> items = [1, 2, 3];
TL;DR - если лень, то можно использовать unknown, но не any. any - зло!
Функция глубокого копирования по аналогии с lodash cloneDeep
Клонирование/копирование идеальный случай для обобщённого типа (собственно lodash cloneDeep так и типизирован с использованием generic типа)
блоки catch Это вынужденное обстоятельство в связи с плохим дизайном ошибок в JS/TS
Т.е. речь о ситуациях, когда нам придётся либо обвешиваться type guard, что не гарантирует что все случаи предусмотрели (человеческий фактор / неопытность разработчика), либо как раз создадим головную боль потребителю, которому ts будет ругаться, что его тип не совместим с unknown.
Для новичка простительно использование any, для опытного разработчика - нет. Лучше всего использовать обобщённые типы. Если не очень хочется возится с ними, то используем unknown, который проверяется на месте или же передаётся куда-то в глубь дальше.
Например в случае с глубоким копированием, лично я, не вижу смысла делать эту функцию типобезопасной, потому что туда передать могут реально практически что угодно - в этом смысл, а следовательно и проще / дешевле для разработки использование any будет более чем оправданно
Кто-то ведь копирует данные, значит должен знать как это делать, значит должен располагать информацией о типе копируемого значения, следовательно какая-то информация о типах имеется.
А можно, пожалуйста, пример этого самого "мата в 3 этажа" при использовании unknown? Просто мне сложно представить ситуацию, в которой вместо конкретных или обобщённых типов, приходится городить непонятное нечто через any/unknown.
Думаю это хороший подарок для тех кто обучают языку Java других людей. Объяснять новичку что это за class Main public static void main немного затруднительно, ведь в процессе обучения до классов и методов доходят далеко не сразу.
Почему не выполнилась эта функция? Ой, я забыл сделать для неё await.
У некоторых разработчиков кривые руки, раз они забывают сделать await.
Ошибка в корутине? А ты точно запустил её с нужными параметрами?
Ого, корутину можно запустить с неправильными параметрами... как и обычную функцию... как и всё что угодно.
asyncio не поддерживает асинхронные операции с файловой системой. Даже если файлы открываются с O_NONBLOCK, чтение и запись будут блокироваться.
Асинхронная работа с диском непосредственно в ОС реализована плохо, что каким-то образом является недостатком asyncio по сравнению с синхроным/многопоточным подходом.
В Textual я наблюдаю следующую часто возникающую проблему: разработчики тестируют конкурентность, добавляя вызов a time.sleep(10), чтобы симулировать планируемую ими работу.
У некоторых разработчиков кривые руки, ведь они в асинхронных функциях пишут синхронный time.sleep().
C#, язык, из которого позаимствован синтаксис async/await, имеет более широкую поддержку async в базовых библиотеках ввода-вывода, потому что он реализует Task‑based Asynchronous Pattern (TAP), где задачи диспетчеризируются в управляемом пуле потоков.
Здесь всё верно. Асинхронность в питоне плохо вплетена в язык. Многие модули со временем получают асинхронные альтернативы (например, subprocesses), но этого мало.
Я мейнтейнер пакетов, поэтому могу сказать, что поддержка и синхронных, и асинхронных API — сложная задача.
Это действительно так. Разработчики библиотек в принципе всегда страдают, так как им нужно поддерживать кучу платформ, языков, парадигм разработки. Обычные пользователи языка, коих большинство, обычно пишут либо только синхронный код, либо асинхронный с впиливанием многопоточности/многопроцессорности и не страдают от этой проблемы.
Лично я уже слабо себе представляю разработку на питоне без асинхронности, но как уже написали выше:
Асинхронщину не все понимают. Плюс, она не всем нужна.
# такое форматирование выглядит намного лучше, когда имя не однобуквенное
result = [
x
for x in data
if x.get("enabled") and x["value"] > 10
]
В примере с аннотациями стоит вынести анонимные типы в именованные. Для примитивных типов можно даже использовать NewType, но это совсем необязательно:
type Item = tuple[str, int | float]
type Response = dict[str, float]
def process(items: list[Item]) -> Response:
Это точно понятнее и выразительнее чем нормальный цикл?
Наверное у меня профдеформация после стольких лет использования питона, но я не могу сказать, что в приведённом вами примере такого невыразительного, хотя согласен, что многие используют включения там, где это только вредит. Нужно хорошо понимать где использовать цикл, где включение, где функция-генератор.
А если для logs нужно какую то другую операцию выполнить а не join()?
Не совсем понял, в чём вопрос. Если нужно выполнить другую операцию, то... вы просто выполняете другую операцию.
Zig это нечто промежуточное между C и Rust. Zig имеет множество крутых фич как в Rust (tagged unions, optionals, явные ошибки), но при этом имеет более свободную модель выделения памяти как в C (аллокаторы и defer очень удобны, но всё же полностью не защищают от утечек/двойного освобождения).
Если неявный nullability это ошибка на миллиард долларов, то контекст функций в JS это ошибка где-то на 100000 баксов. Это же нисколько не нормально, что сохранив ссылку на, казалось бы, метод объекта, на самом деле мы получаем ссылку на unbound функцию. Вот пример для иллюстрации:
const arr = [1, 2, 3];
arr.map(v => v * v); // ок
const arrMap = arr.map;
arrMap(v => v * v); // не ок: TypeError: can't convert undefined to object
Благо завезли стрелочные функции которые хоть как-то спасают ситуацию.
При этом к вашей же статье прикреплена png картинка весом в 1МБ.
В угоду обратной совместимости, многие вещи в тайпскрипте возвращают any, вместо нормального типа. Если бы
Response.json()возвращал unknown, то подобной проблемы бы не возникло и программисту пришлось бы разбираться в чём дело(конечно, многие бы просто забили и скастили бы тип к нужному, но всё же).Это всё круто, но как ты ошибки не возвращай, всегда будет одна проблема - компилятор не скажет, где ты забыл обработать ошибку. Один раз не проверил что вернула функция, у тебя с большой вероятностью сегфолт, который ещё попробуй исправь. zig такого не позволяет. Хотя, на самом деле, zig тоже не очень безопасный в этом плане. Забыл освободить ресурс через defer и прога падает или память утекает, но в нём хотя бы есть DebugAllocator и стек вызовов, что позволяет ошибки отловить достаточно рано. То есть на корню все проблемы системного программирования zig не решает, но он однозначно делает многие вещи лучше C.
Я просто не понял, что @Serpentine предлагает сделать, чтобы избежать UB непосредственно в функции lower. Какой бы тип не был выбран для строки(указатель, указатель+длина), всё равно пользователь функции сможет передать испорченные данные.
Хорошо было бы предложить решение, которое бы позволило в данном случае избежать UB. Вопрос только в том, существует ли оно.
Обожаю zod, использую его во всех проектах, но хочу слегка заступиться за yup. Никогда им не пользовался, но первый пункт(определение схемы и вывод типа объекта) меня очень сильно смутил и после быстрого гуглежа оказалось, что статья вводит в заблуждение. Оказывается, yup в плане схемы мало чем отличается от zod - определяем схему кодом, выводим из неё тип. Валидация данных в yup тоже типобезопасная, по поводу интеграции с React сказать нечего.
То есть или документация yup врёт, либо автор статьи неправильно пользовался библиотекой, либо я что-то неправильно понял.
Согласен, что использование
Anyлучше избегать любыми способами. Но вот обобщённый тип для владельца дескриптора считаю оверкиллом, так как пользы в большинстве случаев он никакой не представляет. В примере из статьи, дескрипторTypedAttributeиспользует исключительно словарь экземпляра владельца дескриптора, то естьinstance: objectвполне хватит. Если отinstanceтребуется обладать какой-то определённой логикой, то прямо так и пишемinstance: HasSomeBehaviour.И к тому же, если дескрипторы которые выступают в роли декоратора могут получить тип владельца автоматически, то вот для обычных дескрипторов - это будет выглядеть так себе.
Выглядит не прямо ужасно, но если mapped_column никак не будет использовать обобщённый тип владельца, то это просто захламление кода.
С константами есть проблема - IDE/typechecker не станет ругаться, если не покрыть все значения. С перечислениями(Enum/Literal/Union - неважно), ситуация принципиально иная. Например:
Константы без else ветки
В этом случае тайпчекер будет ругаться, так как не все возможные значения для status покрыты. Придётся добавить ветку else. Мало того, что нужно будет как-то обработать невозможное значение для status, так ещё и в случае появления новой константы для статуса, мы никак не узнаем, что не обработали её.
В случае же с перечислениями, мы не обязаны иметь ветку else и ещё до запуска узнаем о том, что не обработали одно из возможных значений для статуса. Пример:
Перечисления без else ветки
Так бывает, что библиотекам может понадобиться мажорная миграция. Алхимии она определённо пошла на пользу(ИМХО).
А при необходимости поправить старый код, документация к легаси версии 1.4 всё ещё доступна.
Возможно вы действительно использовали не голую алхимию, а обёртку над ним? Не припоминаю в sqlalchemy метода paginate да и вообще вспомогательного функционала для пагинации. В этом плане он всегда был близок к нативному sql, заставляю использовать offset/limit или самописные курсоры. Гугл выдаёт что метод paginate есть в flask_sqlalchemy, возможно дело в нём.
Если у вас .NET 8 или выше, то лучше использовать Collection expressions. Хороший баланс производительности и удобства.
TL;DR - если лень, то можно использовать unknown, но не any. any - зло!
Клонирование/копирование идеальный случай для обобщённого типа (собственно lodash cloneDeep так и типизирован с использованием generic типа)
Для новичка простительно использование any, для опытного разработчика - нет. Лучше всего использовать обобщённые типы. Если не очень хочется возится с ними, то используем unknown, который проверяется на месте или же передаётся куда-то в глубь дальше.
Кто-то ведь копирует данные, значит должен знать как это делать, значит должен располагать информацией о типе копируемого значения, следовательно какая-то информация о типах имеется.
Действительно типобезопасный debounce можно состряпать как-то так:
В этом примере используется unknown, но я вроде как тут он не нарушает безопасность типов.
А можно, пожалуйста, пример этого самого "мата в 3 этажа" при использовании unknown? Просто мне сложно представить ситуацию, в которой вместо конкретных или обобщённых типов, приходится городить непонятное нечто через any/unknown.
Думаю это хороший подарок для тех кто обучают языку Java других людей. Объяснять новичку что это за
class Main public static void mainнемного затруднительно, ведь в процессе обучения до классов и методов доходят далеко не сразу.У некоторых разработчиков кривые руки, раз они забывают сделать await.
Ого, корутину можно запустить с неправильными параметрами... как и обычную функцию... как и всё что угодно.
Асинхронная работа с диском непосредственно в ОС реализована плохо, что каким-то образом является недостатком asyncio по сравнению с синхроным/многопоточным подходом.
У некоторых разработчиков кривые руки, ведь они в асинхронных функциях пишут синхронный time.sleep().
Здесь всё верно. Асинхронность в питоне плохо вплетена в язык. Многие модули со временем получают асинхронные альтернативы (например, subprocesses), но этого мало.
Это действительно так. Разработчики библиотек в принципе всегда страдают, так как им нужно поддерживать кучу платформ, языков, парадигм разработки. Обычные пользователи языка, коих большинство, обычно пишут либо только синхронный код, либо асинхронный с впиливанием многопоточности/многопроцессорности и не страдают от этой проблемы.
Лично я уже слабо себе представляю разработку на питоне без асинхронности, но как уже написали выше:
Если отформатировать, то вполне читабельно:
В примере с аннотациями стоит вынести анонимные типы в именованные. Для примитивных типов можно даже использовать NewType, но это совсем необязательно:
Наверное у меня профдеформация после стольких лет использования питона, но я не могу сказать, что в приведённом вами примере такого невыразительного, хотя согласен, что многие используют включения там, где это только вредит. Нужно хорошо понимать где использовать цикл, где включение, где функция-генератор.
Не совсем понял, в чём вопрос. Если нужно выполнить другую операцию, то... вы просто выполняете другую операцию.
F1 > Hide copilot
Zig это нечто промежуточное между C и Rust. Zig имеет множество крутых фич как в Rust (tagged unions, optionals, явные ошибки), но при этом имеет более свободную модель выделения памяти как в C (аллокаторы и defer очень удобны, но всё же полностью не защищают от утечек/двойного освобождения).
Если неявный nullability это ошибка на миллиард долларов, то контекст функций в JS это ошибка где-то на 100000 баксов. Это же нисколько не нормально, что сохранив ссылку на, казалось бы, метод объекта, на самом деле мы получаем ссылку на unbound функцию. Вот пример для иллюстрации:
Благо завезли стрелочные функции которые хоть как-то спасают ситуацию.
DEL