Tishka17 @Tishka17
Пользователь
Information
- Rating
- 2,437-th
- Location
- Москва, Москва и Московская обл., Россия
- Date of birth
- Registered
- Activity
Specialization
Backend Developer, Mobile Application Developer
Lead
Python
Docker
Linux
SQL
Git
Golang
Android SDK
Но второе может перестать работать, если вместо строки у вас что-то совместимое со строкой. Для строк может не так критично, а в других случаях - может выстрелить. В питоне все таки утиная типизация.
Даже лучше соответствует определению интерфейса, так как не заставляет указывать что вы его реализуете (в отличие от джавы)
Если вы не используете пути как объекты, а везде вам нужны строки, то все возжности покрываются
os.path
.pathlib
- это неплохой способ работы с путями, при условии что вам действительно нужна объектная модель. Во многих случаях его использование приводит к лишним конвертациям из строк в объекты и обратно. Я бы предложил в большинстве случае использоватьos.path
В рамках данной статьи был константа с прямым слэшом
/
, это теоретически может привести к некорректному поведению, когда его будут комбинировать дальше с путями в ОС Windows, содержащими обратный слэш\
Всё ещё не понимаю, почему её форсят некоторые люди:
у нее достаточно сложная минимальная настройка из-за необходимости интеграции с logging
у нее не очень высокая гибкость (коллеги жаловались на сложности кастомизации хэндлеров)
у нее ужасно многословные трейсы по дефолту
Кажется, всё таки стоит написать большой комментарий.
1) Используйте генераторы
Как уже обсуждали выше это может замедлить программу или внести дополнительные негативные эффекты ввиду того, что это итератор. При этом генератор действительно позволяет сэкономить память и иногда код с ним получается проще, в других местах же они не подходят.
3) Увеличивайте производительность вашего кода
Увеличивайте, если это дает что-то бизнесу. Ваше время не бесплатное. Вы можете потратить больше времени и денег, чем сэкономите за счет ускорения кода. Иногда скорость выполнения критчна, иногда нет. Стоит понимать в какой вы ситуации.
Кроме того, прежде чем заниматься оптимизацией неплохо бы понять что мы ускоряем и есть ли тут потенциал. Если у вас 99% времени занимает расчет на numpy или ожидании времени ответа СУБД, то никакие слоты и сабпроцессы не помогут.
Профилируйте код, ищите узкие места, оптимизируйте то, что имеет смысл оптимизировать. При этом, само собой, при наличии двух вариантов реализации не стоит выбирать просто так тот, который медленнее. Если оптимизация дается вам бесплатно (без значительного ухудшнения поддерживаемости кода и сроков), стыдно ей не воспользоваться
3.1) Многопроцессность
Использование нескольких процессов по сравнению с потоками может привести к дополнительным накладным расходам на пересылку данных, невозможности использовать общие данные.
При использовании же алгоритмов, реализованных в нативных библиотеках, которые отпускают GIL это просто не имеет смысл.
Зачастую наши приложения деплоятся в одноядерном окружении и масштабируются горизонтально, не увеличивая число ядер на отдельном инстансе.
При выборе подхода учитывайте алгоритмы, реализацию, окружение и обязательно профилируйте код!
3.3) Другие интерпретаторы
Это опция интересная, но доступная далеко не всегда. Плюс, как уже выше сказали, надо понимать, что использование альтернативного интерпрrтатора может ограничить вас в использовании библиотек, значительно усложнить процесс деплоя или нарушить стабильность кода.
Иногда так же использование другого интерпретатора приводит к повышению требований к ОЗУ.
5) Используйте срезы
Используйте срезы там где вам нужен срез. Не забывайте, что взятие среза - копирование части списка, это легко может превратить ваш алгоритм с O(n) в O(n^2).
Стоит напомнить про существование
islice
.6) Храните данные правильно
Используйте константы на уровне модуля там где вам нужна просто константа.
Используйте датаклассы, там где вам нужна структура данных, потенциально имеющаяся в нескольких экземплярах.
Используйте словари там, где вам нужны произвольные гомогенные ключи, а классы там где у вас поля имеют разный смысл или тип.
Используйте Enum там где у вас выбор значений из нескольких вариантов и других быть не может.
Не забывайте про
\
и/
в путях.7) Используйте максимум возможностей ООП
7.1) Используйте утиную типизацию, но выделяйте абстракции
У нас всё таки Python. Если необходимо - декларируйте абстрактные классы или Protocol
7.2) Используйте наследование и композицию.
Запомните, что наследование делает структуру кода более хрупкой и должно применяться с осторожностью. Это полезный инстурмент, но зачастую композиция дает более стабильное и гибкое решение. Почитайте про различные паттеры проектирования, в частности декоратор и стратегию
7.3) Не используйте вложенные классы.
Вложенные классы дают примерно ничего, но лишь мешают их импортировать отдельно. В Python вкачестве неймспейсов используются именно модули, а классы нужны чтобы у них создавать экземпляры. Побочным эффектом вложенных классов является доступ к ним и переопределение при наследовании, это потенциально может быть полезно, но скорее всего наоборот навредит.
8) Попробуйте кусочек функционального программирования
В целом в Python удобнее НЕ писать в ФП стиле, но есть оговорки.
map
удобен когда у вас уже есть функция, но иногда его пытаются использовать с лямбдой, что только замедляет код. Используйте comprehension. Кроме list comprehension есть set comprehension и dict comprehension.Используйте частичное применение функций, это позволит вам избавиться от вложенных функций и повысит тестируемость кода. Но иногда вместо этого стоит создать callable-объект.
Пользуясь случаем, хочу поделиться своим каналом в telegram, где я рассказываю как лучше делать некоторые вещи: https://t.me/advice17
не исправил
Генераторы хорошая вещь, но обращение к ним действительно имеет дополнительные накладные расходы по сравнению со списком. Кроме того, генераторы одноразовые, не могут быть развернуты в обратным порядке. Но они экономят нам память.
Я встречал код, где вовсю использовали генераторы, но затем делали itertools.tee, для создания копий и который в результате разворачивал их в памяти.
Хочется сказать - разберитесь что в вашем случае важно и используйте те конструкции и типы данных, которые действительно помогают это достичь. Либо используйте тот код, который проще для понимания и имеет минимум побочных эффектов
Использование датакласса для хранения константы - очень плохое и бессмысленное действие.
Во-первых, классы нужны для того чтобы создавать экземпляры. У вас они не создаются
Во-вторых, датакласс нужен для того чтобы сгенерировать классу такие методы как
__init__
и__eq__
, что снова не используется.В-третьих, вы упоминаете frozen, но он влияет именно на проведение экземпляра класса, которого нет. Да и создать можно много экземпляров.
В указаннлм примере константа на уровне модуля самый очевидный и хороший способ. При необходимости можно туда и аннотацию типа добавить.
Дополнение про возможность добавить методы имеет смысл, но это совершенно другая ситуация, не относящаяся к строковой константе.
"Слайсы" слышал только применительно к языку го, где это отдельный тип объекта. Всегда встречал именно "срезы".
Вложенные функции имеют смысл только для использования закмыканий при реализации декораторов, колбэков и т.п. (Частичное применение делается через functools.partial). В остальных случаях это ошибка: их использование как минимум затрудняет тестирование.
Вложенные классы имеют смысл когда этого требует фреймворк, в остальных случаях их использование скорее всего не имеет смысла. Как минимум это усложняет испорты
Обычно используют venv, так как он в коробке в отличие от virtualenv.
Если вы пропущенный импорт находите только через пару месяцев, это означает что вы не тестируете новую фичу перед выкладкой. Тут ничего не поможет
Непонятно, почему нельзя сделать дистрибутив из вашего пакета, установить и не заниматься всем этим?
За сколько секунд вы находите и исправляете потерянный импорт? Как часто это происходит?
Сколько времени уходит на поддержку этой магии и объяснения новым разработчикам как она работает? Покрыта ли она тестами?
Спасибо большое, но я не знаю синтаксис Haskel, мы в треде по питону. Попробую разобраться, поправляйте если я ошибся.
Давайте вернемся к определениям. Что такое композиция функция F(x) и G(x)? Это функция вида F(G(x)). Есть ли у нас такое выше? Нет.
Тут нет композиции, тут есть передача функции в функцию и частичное применение для уменьшения количества аргументов. У нас етсь функция sort_by(list, cmp) и мы её преобразовали в функцию sort(list). Да, в функциональном программировании это есть изначально и есть своя терминология для этого. Но можно так же сказать, что функция sort_by зависит от cmp. Соответсвенно, когда мы её каррируем, мы внедряем эту зависимость.
Вот вы делаете маленькие функции, но некоторые из них должны вызывать другие. Вы можете либо передавать их как параметр, либо жестко указывать в коде. В первом случае у нас работает принцип инверсии зависимости (функция не знает что она вызовет, но знает абстракцию), во втором случае у нас невозможно повторное использование функции в чуть других условиях.
Допустим, вы хотите сортировать список по ключи. Либо вы делаете конкретную функцию сортировки по каждому ключу, либо передаете (инжектируете) функцию ключа как параметр.
Вопрос, это все ещё костыль и анахронизм?
Погодите. Вот допустим у вас есть функция foo, которая вызывает функцию bar. Вы можете сделать это напрямую или передавать bar как аргумент. Тогда вы можете сделать foo1 = partial(foo, bar) - и вот у вас внедрение зависимости в функциональном стиле.
На мой вопрос вы так и не ответили: чем DI мешает или не помогает? Его задача одна - сделать сцепление кода ниже и тем самым изолировать слои абстракции и улучшить гибкость архитектуры. Концепция универсальная и относится не только к ООП, но и к ФП (даже если называется там по другому) и даже проектированию сервисов
И все таки раскройте мысль почему внедрение зависимостей в Python не приносить пользы и атипаттерн по вашему мнению.
А разговаривая про ФП, я не понимаю вашего противопоставления каррирования и DI, по факту это тот же механизм, но примененный функциям, а не объектам.
Подскажите плиз, а как в opiod с кастомными скоупами? Допустим я делаю небольшой планировщик (или веб фреймворк или ещё что) и хочу чтобы на каждое событие у меня создавался один новый экземпляр соединения с БД. То есть это привязано не к тредам, а имеет конкретный огранчиенный жизненный цикл, которым будет управлять фреймворк. Можно это реалзиовать?