Обновить
2

Пользователь

0,1
Рейтинг
5
Подписчики
Отправить сообщение

Откажитесь от JPEG/PNG в пользу современных форматов AVIF или WebP.

При этом к вашей же статье прикреплена 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.
И к тому же, если дескрипторы которые выступают в роли декоратора могут получить тип владельца автоматически, то вот для обычных дескрипторов - это будет выглядеть так себе.

# привет из 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 будет более чем оправданно

Кто-то ведь копирует данные, значит должен знать как это делать, значит должен располагать информацией о типе копируемого значения, следовательно какая-то информация о типах имеется.

Действительно типобезопасный debounce можно состряпать как-то так:

declare function debounce<P extends unknown[]>(fn: (...args: P) => void, timeoutMS: number): (...args: P) => void;

const debouncedAlert = debounce(alert, 1000);

В этом примере используется unknown, но я вроде как тут он не нарушает безопасность типов.

А можно, пожалуйста, пример этого самого "мата в 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

Благо завезли стрелочные функции которые хоть как-то спасают ситуацию.

Информация

В рейтинге
3 670-й
Зарегистрирован
Активность