Минутка просвещения

Читать в телеге. Когда-то там были посты не только от меня.

История ввода в python

Представьте себе, еще есть люди, которые пишут программы с консольным вводом. И иногда хочется иметь историю, например, когда надо загрузить какой-нибудь жирный граф из файла, чтобы к нему потом запросики делать.

В питоне для этого можно использовать readline. Просто написать import readline и стандартный input() будет иметь историю (в рамках этого запуска), по которой можно ходить стрелочками вверх-вниз.

СсылкаКомментировать

Эмулятор андроида

Захотел я тут одно мобильное приложение запустить на компе. Думаю — ну, в 2020 живем, сейчас минут за 10 скачаю эмулятор какой-нибудь и запущу. Смотрим на варианты эмуляторов: этот платный, этот облачный, для этого виртуалку качай, этот — только для винды и мака, один так вообще плагин к браузеру, этот — порт системы под x86, который ставить отдельно с загрузочной флешки. В общем, "богатый" выбор. В итоге остановился на Anbox.

Поставить его проблем особых нет:

snap install --devmode --beta anbox
sudo apt install android-tools-adb

Все довольно быстро, можно потыкать в стандартные приложения, вроде как работает. Потом качаю apk через apkcombo или evozi, ставлю через

adb install apk_name.apk

И, разумеется, наступаю на грабли INSTALL_FAILED_NO_MATCHING_ABIS. Ну ок, проблема в архитектуре проца. Пробую для x86 скачать apk — те же грабли. Но можно поставить слой совместимости и после этого приложения ставятся и даже запускаются. Однако целевое приложение мне выдало какую-то свою ошибку. Решил проверить на чем-нибудь поинтереснее: ночная сборка Firefox запустилась, но вместо сайта показывает белый экран. А рандомная игруля (Plants vs Zombies) вообще не запустилась. Пару раз при этом эмулятор падал. Думаю ­— ну ладно, кривая фигня, попробую лучше "официальный" эмулятор — Android SDK.

Надо скачать Android Studio, через нее скачать пару миллиардов гигов зависимостей, потом найти, где собственно закопан эмулятор, и запустить его через командную строку (впечатляющий UX). Система запустилась, тыкать в нее по-прикольнее, чем в Anbox, но как домашний экран провертеть до приложений, я так и не разобрался, запускал все через настройки. Установка приложений тоже закончилась весело: я попробовал 4 комбинации apk и архитектуры (x86/arm) и ни одна не сработала. Два варианта ошибки: Segmentation fault (core dumped) и Couldn't parse error message (еще один впечатляющий UX). Понятия не имею, как люди могут использовать этот эмулятор для разработки.

Возможно, у этой истории будет продолжение (я уверен, что это случай "информация была дана, вы не разобрались"), но сейчас у меня от нее пригорело, и пробовать что-то еще сил уже нет.

СсылкаКомментировать

Грабли генерации id в Hibernate

За использование фреймворков приходится платить. Пусть есть Entity с простым полем

@Id
val id: UUID = UUID.randomUUID()

Вроде все просто: при создании сущности ей сгенерируется id, потом запишем его в базу одним запросом. Однако добрый EntityManager сделает тут один дополнительный запрос — а вдруг это не новая сущность? Поэтому приходится писать так:

@Id
@GeneratedValue
@Column(name = "id", updatable = false, nullable = false)
val id: UUID? = null

Тут мы информируем EntityManager, что это поле он должен сгенерировать сам (по умолчанию генерируется UUID), и что лезть в базу не надо, если id отсутствует. А потом защищаемся от самих себя — запрещаем писать в базу сущность с отсутствующим id, ведь компилятор это уже проверить не может. Зато он не знает ничего про генерацию этого поля и вполне справедливо считает, что раз оно может отсутствовать, то изволь переписать почти весь код его использующий, как минимум equals и hashCode.

(Скоро у меня кончатся идеи для постов и канал превратится в нытье про то, как я не люблю Spring и стандартный стек вокруг него).

СсылкаКомментировать

Ускорение ввода-вывода в С++

Если вдруг пришлось залезть в такие дебри, что нужно это ускорять (например, чтобы сделать лабу по алгоритмам:)), то помогут две магические строчки:

ios_base::sync_with_stdio(false);
cin.tie(NULL);

Первая говорит о том, что нам плевать на C, его средствами мы читать/выводить ничего не будем, поэтому не будем синхронизировать буферы стандартных потоков ввода-вывода от С и C++. Вторая говорит о том, что не надо синхронизировать cin и cout (по умолчанию при любой операции над одним автоматически очищается буфер другого). Это можно себе позволить, если, например, программа однопоточная.

СсылкаКомментировать

Чек-листы для кода и архитектуры микросервисов

https://kislayverma.com/programming/code-review-checklist-for-distributed-systems/ https://kislayverma.com/programming/design-review-checklist-for-distributed-systems/

Местами спорно и рассказаны довольно базовые вещи, но в целом годно — статьи дают неплохое представление, откуда начать.

СсылкаКомментировать

Импорты в питоне

Python весьма неплох для стряпанья скриптиков. Однако если однотипных скриптиков становится много и появляется общий код, то тем или иным образом возникнет проблема с организацией папочек так, чтобы импорты работали нормально. Классический пример — импорт из "соседней" папки.

Проблема связана в основном с тем, что питон может быть запущен в непонятно каком окружении, в котором может даже не быть иерархической структуры модулей. Поэтому импортировать можно только из sys.path, в котором не всегда будет путь к нужному модулю. Основные способы решения проблемы сводятся к следующим вариантам:

  1. Вы все делаете неправильно, так писать код нельзя, срочно поменяйте свою структуру папок. Гвидо проклинает таких, как вы.
  2. Вы должны правильно настроить virtualenv, сделать одну из ваших папок пакетом и установить ее (как и любой пользователь ваших скриптов).
  3. Вы можете запускать свои скрипты как модули с флагом -m. Успехов вам в добавлении этого в мутные инструкции по запуску и в слежении за тем, чтобы при автоимпорте всех скриптов из папки ничего не сломалось.
  4. Вы можете грязным хаком добавить нужный путь в sys.path, например, так:
    sys.path.append(str(pathlib.Path(__file__).resolve().parent.parent))
    
    Это самый простой и прямолинейный способ, но за него вы, разумеется, будете гореть в питоновском аду.

Подробнее про импорты можно прочитать тут и тут.

Я знаю, что среди немногочисленных читателей канала есть сеньор-питонисты — напишите пожалуйста в ЛС, как решаются подобные проблемы у вас.

СсылкаКомментировать

Сборник просвещающих игр

Отличный сайт для изучения разных социальных и не очень проблем в игровом формате: https://ncase.me/.

Самая популярная — игра про доверие (базовая теория игр included!).

Еще можно отметить игры про системы голосования и сегрегацию.

В игровом формате можно узнать не только про что-то связанное с математикой, но и, например, про тревожность или про распространение ненависти.

СсылкаКомментировать

Главный метод в Kotlin jar

Если написать такую программу

fun main(args: Array<String>) {
    println("Hello, dudes!")
}

и скомпилировать ее через kotlinc 1.kt -include-runtime -d hello1.jar, то при запуске через java -jar hello1.jar получим приветствие. Однако если то же самое сделаем с эквивалентным кодом

object Main {
  @JvmStatic
  fun main(args: Array<String>) {
    println("Hello, dudes!")
  }
}

то результат будет уныл:

no main manifest attribute, in ./hello2.jar

Почему не генерируется манифест можно посмотреть в исходниках компилятора. Через пару прыжков выходим на MainFunctionDetector, где, продравшись через условия, можно понять, что главными считаются только top-level функции, и в манифест Main-Class добавляется только в первом случае. Хотя казалось бы, раз уж есть код для поиска главного метода, то ничто не мешает проставлять Main-Class во всех случаях. Звучит довольно низкоуровнево, ведь у всех есть система сборки или IntelliJ на худой конец, но это все-таки баг.

СсылкаКомментировать

Насколько быстро комп делает "типовые" операции?

Занимательный тест: https://computers-are-fast.github.io

Который вдобавок еще и забавный с учетом сегодняшнего индустриального стандарта, где на железе виртуалки, в виртуалках кубер, в кубере докер, в докере виртуальная машина языка, в ней — абстракции фреймворка, потом уже собственно код (причем это еще не самый страшный кейс), и все это еще и по HTTP.

СсылкаКомментировать

Сериализация DTO python в JSON

В продолжение предыдущего поста. Чтобы иметь возможность сериализовать dataclass в JSON, можно сделать что-то вроде трейта и примешать его к датаклассам.

from dataclasses import dataclass, asdict

class Serializable:
    def _asdict(self):
        return asdict(self)

@dataclass(frozen=True)
class Person(Serializable):
  name: str
  age: int

p = Person("Jeshua", 33)

requests.get("http://httpbin.org/anything", json=p).json()

Эта штука вам вернет...

TypeError: Object of type Person is not JSON serializable

Но если поставить модуль simplejson, который можно считать девелоперской версией встроенного json, то ответ будет нормальным:

{... 'json': {'age': 33, 'name': 'Jeshua'}, ...}

Как же это работает? Во-первых, в модуле requests есть развилочка:

try:
    import simplejson as json
except ImportError:
    import json

Во-вторых, в модуле simplejson есть встроенная поддержка namedtuple: если у класса есть метод _asdict, то он будет вызван для получения словаря, из которого потом будет сделан JSON. Т.е. с simplejson namedtuple можно просто передавать как параметр в dumps без лишних телодвижений. А благодаря Serializable датакласс маскируется под namedtuple.

СсылкаКомментировать