Все, о чем вы говорите - лишь синтаксический сахар.
И этот синтаксический сахар кратно уменьшает сложность кода.
Можно десятки способов реализовать для чтения файла. Вплоть до освобождения при выходе из блока. Это не проблема и не сложность.
Было бы славно, если бы в ответ на примеры кода @Andrey_Solomatin вы бы привели идентичные примеры на паскале. Тогда бы уже и можно было бы рассуждать о том, избыточен ли синтаксис питона или нет.
И нет, у Питона нет локаничности. Есть переизбыток механик, которые позволяют сильно кратко что-то описать, ухудшив понимание кода. Одна механика в коде - нормально, множество друг в друге - органы проблема. Просто мешанина из операторов, в которой нельзя просто разобраться, не отделив одно от другого.
В любом языке можно намешать всё в кучу и получить нечитаемое месиво. Это не проблема языка. Хороший программист не должен преследовать цели "сильно кратно что-то описать", но должен стремиться сохранить баланс между длиной кода и его читаемостью. Питон позволяет писать и подробно, и кратко, что не может быть минусом.
Не надо забывать, что Паскаль - язык с ручным управлением памяти. И в нем реализован механизм подсчёта ссылок, которого достаточно.
Ручное управление памятью, как правило не способствует читаемости кода. Наверное, только Rust сумел выделиться на этом поприще.
Вы же сами приводите доказательства того, что питон выразительнее и читабельнее паскаля.
Просто мимо. На каждую манипуляцию с файлом нужно искать или писать утилитарный метод? Неужели, в паскале нет менеджера контекста? Или какого-нибудь оператора, который закрывал ресурс при выходе из блока. Видел недавно язык(zig, кажется), где такой оператор назывался defer.
Создание списка(хотя у вас почему-то числа просто выводятся на экран) занимает на порядок больше строк чем в питоне, что уже является большим минусом, так как ухудшает читаемость кода. Анонимная функция, кстати, тоже читабельности не способствует. Даже в жабе, которая славится своей многословностью, завезли Stream API с лямбдами и если чего-то подобного нет в паскале, то это явно минус.
Опять же, распаковка выглядит удобнее нежели постоянное обращение к Item. Такая же болячка есть у жабы, а вот C# удалось её побороть, добавив возможность деконструкции у KeyValuePair.
Все способы которые я знаю, приведут к ошибкам статического анализа. Придётся подавить ошибку линтера, если интерфейс требует возвращать именно генератор.
Но как правило, асинхронного итератора вполне достаточно. Встроенного способа создания пустого асинхронного класса-итератора, я не нашёл, но его с лёгкостью можно написать самому:
from typing import AsyncIterator
class EmptyGenerator(AsyncIterator):
__slots__ = ()
def __aiter__(self):
return self
async def __anext__(self):
raise StopAsyncIteration
Причём, если стремиться сохранить обратную совместимость, текущую иерархию уже никак не расширить. Впихнуть ReadableIterator, ReadableIterable, ReadableCollection, ReadableList, Readable* то можно, но вот что делать с утилитарными классами и методами(Collections, List::of), которые возвращают экземпляры List, Set, Map и т. д. это большой вопрос.
Да, я компилировал под net6, а в статье описывается как это всё компилируется под net5. А в заключении ещё и описано то, какой код будет сгенерирован под net6 Не знаю, как так вышло, что я всё это проглядел. Прочитать статью внимательнее я времени не нашёл, но зато проверить всё самому — это запросто. Дурак я, в общем.
Круто, что вы популяризируете Godot, но ваша реализация явно нуждается в улучшении.
Вместо хардкода путей и неявного формирования массивов с изображениями, можно пометить переменные которые вы наполняете с помощью скана файлов аннотацией @export, тем самым обеспечив возможность редактировать список прямо из редактора. Количество кода уменьшится, а удобство его поддержки, наоборот, увеличится.
Всё это:
if eye_number == eye_array.size():
eye_number = 0
if eye_number == -1:
eye_number = eye_array.size() - 1
можно переписать в одну строчку:
eye_number = eye_number % eye_array.size():
Читабельность повышается, вероятность ошибки при копипасте снижается. 3) Синглтон всегда здесь избыточен. У вас есть три простых компонента, которые делают одно и то же — лишь переключают значения типа int. Самое лучшее, что можно сделать, это вынести эту логику в отдельный скрипт, а кнопки и лейбл, вынести в отдельную сцену. В скрипте объявляется сигнал, который сигнализирует о том, что значение поменялось(нажали на кнопку). Тем кому нужно, подписываются на этот сигнал. Эти самые значения могут храниться в сцене, которая отвечает за настройку персонажа и при необходимости, пробрасывать эти данные вниз по иерархии.
И ещё, пускай это уже не относится к качеству реализации, но вместо скинов с заранее заданными цветами, можно навешивать на спрайт tint, который позволит игроку выбрать любой цвет. Если необходимо, чтобы некоторые места на скине сохраняли свой цвет, то можно для скина нарисовать маску, которая будет это всё дело регулировать.
По логике, у нас намечается многомерный массив ячеек, но я счёл, что массив, хранимый в виде линейного списка, будет проще в обращении, его и использовал
Не намного то проще, если у вас дальше идёт куча расчётов, лишь только для того, чтобы вычислить индекс по координатам. Если говорить о производительности, то --лучше использовать битборды-- в таком случае больше подойдёт array.array из-за особенностей хранения элементов(список хранит ссылки, array.array значения).
Ещё я не понял, почему класс TTTBoard вместо хранения информации о поле, реализует всю логику игры. Логичнее было бы, если бы какой-нибудь TicTacToe содержал бы в себе Board, который использовал бы только для хранения состояния о поле.
И раз уж используется типизация, то Board можно было бы сделать generic типом, чтобы не гадать, какие значения он хранит:
T = TypeVar('T')
class Board(Generic[T]):
def __getitem__(self, pos: tuple[int, int]) -> T:
...
Причём, это довольно странное поведение. Это что-то на уровне PHP/JS, которые когда парсят целое число, отбрасывают символы отличные от чисел в конце строки. Скорее всего, это следствие обратной совместимости, также как и, например, метод boolean Boolean::getBoolean(Strng name). getBoolean считывает значение из системных свойств и парсит его, хотя казалось бы, почему обёртка примитивного типа должна этим заниматься?
Имхо, есть случаи, когда one-to-one вполне себе уместно использовать. Условно, есть список файлов. Каждый файл можно опубликовать отдельным постом. У поста могут быть теги, превью, комментарии и т. д. Если держать всё в одной таблице, то получим странного виду таблицу для файлов со множеством nullable полей, которые нужно было бы каждый раз исключать при работе с ORM. А если бы понадобился поиск по постам, то пришлось бы в каждый такой запрос добавлять not is_posted. Также, после удаления поста(но не удаления файла), необходимо было бы подчищать таблицы связей(post_tags, post_comments и т.д.).
Пример комплексной таблицы file
CREATE TABLE file (
id INT PRIMARY KEY,
path VARCHAR(256) NOT NULL,
is_posted BOOL NOT NULL DEFAULT false,
thumbnail VARCHAR(256),
posted_by int REFERENCES(user)
)
Конкретно в этом случае, не лучше ли было бы иметь таблицу с постами, которая бы имела ссылку на запись в таблице с файлами?
Примечание: я установил начальное значение в 4. Но в консоль всё равно выводится 0. То есть, добавленный атрибут не инициализируется в 4. Почему – я не знаю.
This parameter is only used for static fields. Its value is ignored for non static fields, which must be initialized through bytecode instructions in constructors or methods.
А толку от фабрики, которая возвращает экземпляры классов не реализующие единый интерфейс? Имхо, фабрики приведённые в статье больше похожи на ServiceLocator'ы.
А как её можно научить говорить только правду? Что для неё станет источником истины во время обучения, если она обучается используя весь интернет? Или я чего-то не понимаю?
И нажимаем на c, появится кружок (если покрутить колесико мыши его размер изменится). И этим кружком выделяем голову. Внимательно смотрим чтобы не осталось не выделенных граней. У меня получилась вот такая голова.
Можно просто скрыть выделенную полосу и затем выбрать получившийся островок(голову) клавишей L(по умолчанию).
И этот синтаксический сахар кратно уменьшает сложность кода.
Было бы славно, если бы в ответ на примеры кода @Andrey_Solomatin вы бы привели идентичные примеры на паскале. Тогда бы уже и можно было бы рассуждать о том, избыточен ли синтаксис питона или нет.
В любом языке можно намешать всё в кучу и получить нечитаемое месиво. Это не проблема языка. Хороший программист не должен преследовать цели "сильно кратно что-то описать", но должен стремиться сохранить баланс между длиной кода и его читаемостью. Питон позволяет писать и подробно, и кратко, что не может быть минусом.
Ручное управление памятью, как правило не способствует читаемости кода. Наверное, только Rust сумел выделиться на этом поприще.
Вы же сами приводите доказательства того, что питон выразительнее и читабельнее паскаля.
Просто мимо. На каждую манипуляцию с файлом нужно искать или писать утилитарный метод? Неужели, в паскале нет менеджера контекста? Или какого-нибудь оператора, который закрывал ресурс при выходе из блока. Видел недавно язык(zig, кажется), где такой оператор назывался defer.
Создание списка(хотя у вас почему-то числа просто выводятся на экран) занимает на порядок больше строк чем в питоне, что уже является большим минусом, так как ухудшает читаемость кода. Анонимная функция, кстати, тоже читабельности не способствует. Даже в жабе, которая славится своей многословностью, завезли Stream API с лямбдами и если чего-то подобного нет в паскале, то это явно минус.
Опять же, распаковка выглядит удобнее нежели постоянное обращение к Item. Такая же болячка есть у жабы, а вот C# удалось её побороть, добавив возможность деконструкции у KeyValuePair.
Все способы которые я знаю, приведут к ошибкам статического анализа. Придётся подавить ошибку линтера, если интерфейс требует возвращать именно генератор.
Но как правило, асинхронного итератора вполне достаточно. Встроенного способа создания пустого асинхронного класса-итератора, я не нашёл, но его с лёгкостью можно написать самому:
Зачем?
async def empty_agenerator(): return; yield
Причём, если стремиться сохранить обратную совместимость, текущую иерархию уже никак не расширить. Впихнуть ReadableIterator, ReadableIterable, ReadableCollection, ReadableList, Readable* то можно, но вот что делать с утилитарными классами и методами(Collections, List::of), которые возвращают экземпляры List, Set, Map и т. д. это большой вопрос.
Да, я компилировал под net6, а в статье описывается как это всё компилируется под net5. А в заключении ещё и описано то, какой код будет сгенерирован под net6
Не знаю, как так вышло, что я всё это проглядел. Прочитать статью внимательнее я времени не нашёл, но зато проверить всё самому — это запросто. Дурак я, в общем.
Либо статья вводит в заблуждение, либо я делаю что-то не так, но у меня Visual Studio 2022 выдаёт совершенно другой IL код для
_ = $"{str} {num}"
.IL код большой и его я закинул под спойлер(ибо он слишком большой), но вот как бы это выглядело, если перевести обратно в C#:
IL код
Удивляет то, что интерполяция строки иногда компилируется в вызов String.Format. Почему так происходит, если конкатенация строк была бы куда выгоднее?
Да, тут я ошибся и насоветовал фигни. Перепутал с питоном, где остаток от деления вычисляется по другому.
Круто, что вы популяризируете Godot, но ваша реализация явно нуждается в улучшении.
Вместо хардкода путей и неявного формирования массивов с изображениями, можно пометить переменные которые вы наполняете с помощью скана файлов аннотацией
@export
, тем самым обеспечив возможность редактировать список прямо из редактора. Количество кода уменьшится, а удобство его поддержки, наоборот, увеличится.Всё это:
можно переписать в одну строчку:
Читабельность повышается, вероятность ошибки при копипасте снижается.
3) Синглтон
всегдаздесь избыточен. У вас есть три простых компонента, которые делают одно и то же — лишь переключают значения типа int. Самое лучшее, что можно сделать, это вынести эту логику в отдельный скрипт, а кнопки и лейбл, вынести в отдельную сцену. В скрипте объявляется сигнал, который сигнализирует о том, что значение поменялось(нажали на кнопку). Тем кому нужно, подписываются на этот сигнал. Эти самые значения могут храниться в сцене, которая отвечает за настройку персонажа и при необходимости, пробрасывать эти данные вниз по иерархии.И ещё, пускай это уже не относится к качеству реализации, но вместо скинов с заранее заданными цветами, можно навешивать на спрайт tint, который позволит игроку выбрать любой цвет. Если необходимо, чтобы некоторые места на скине сохраняли свой цвет, то можно для скина нарисовать маску, которая будет это всё дело регулировать.
Не намного то проще, если у вас дальше идёт куча расчётов, лишь только для того, чтобы вычислить индекс по координатам.
Если говорить о производительности, то --лучше использовать битборды-- в таком случае больше подойдёт
array.array
из-за особенностей хранения элементов(список хранит ссылки,array.array
значения).Ещё я не понял, почему класс
TTTBoard
вместо хранения информации о поле, реализует всю логику игры. Логичнее было бы, если бы какой-нибудьTicTacToe
содержал бы в себеBoard
, который использовал бы только для хранения состояния о поле.И раз уж используется типизация, то
Board
можно было бы сделать generic типом, чтобы не гадать, какие значения он хранит:Причём, это довольно странное поведение. Это что-то на уровне PHP/JS, которые когда парсят целое число, отбрасывают символы отличные от чисел в конце строки. Скорее всего, это следствие обратной совместимости, также как и, например, метод
boolean Boolean::getBoolean(Strng name)
.getBoolean
считывает значение из системных свойств и парсит его, хотя казалось бы, почему обёртка примитивного типа должна этим заниматься?Имхо, есть случаи, когда one-to-one вполне себе уместно использовать. Условно, есть список файлов. Каждый файл можно опубликовать отдельным постом. У поста могут быть теги, превью, комментарии и т. д. Если держать всё в одной таблице, то получим странного виду таблицу для файлов со множеством nullable полей, которые нужно было бы каждый раз исключать при работе с ORM. А если бы понадобился поиск по постам, то пришлось бы в каждый такой запрос добавлять
not is_posted
. Также, после удаления поста(но не удаления файла), необходимо было бы подчищать таблицы связей(post_tags, post_comments и т.д.).Пример комплексной таблицы file
Конкретно в этом случае, не лучше ли было бы иметь таблицу с постами, которая бы имела ссылку на запись в таблице с файлами?
Ответ есть в документации.
А толку от фабрики, которая возвращает экземпляры классов не реализующие единый интерфейс? Имхо, фабрики приведённые в статье больше похожи на ServiceLocator'ы.
А как её можно научить говорить только правду? Что для неё станет источником истины во время обучения, если она обучается используя весь интернет? Или я чего-то не понимаю?
Гарантия останется, так как компилятор станет ругаться на код, где switch не покрывает всех наследников sealed классов
Так аннотации java и декораторы python же не имеют вообще ничего общего
Посмотрел исходный код и у меня сразу возникло несколько вопросов.
Почему Try не интерфейс?
Почему Try наследуется от TryAutoClosable, который в свою очередь реализует AutoClosable, а не наоборот?
И ещё, я не уверен, что этот код нормальный:
Можно просто скрыть выделенную полосу и затем выбрать получившийся островок(голову) клавишей L(по умолчанию).