Comments 168
from enum import Enum
import json
class UserStatus(str, Enum):
PENDING = 'Pending'
ACTIVE = 'Active'
INACTIVE = 'Inactive'
DELETED = 'Deleted'
print(UserStatus.PENDING.value)
print(json.dumps(UserStatus.PENDING))
enum в Python все же более гибкие чем показано в статье
Разве это не присвоит просто каждому члену перечисления по строке?
В Rust подразумевается, что именно к каждому члену можно приделать по пачке полей. Т.е. превратить каждого члена в самостоятельную структуру, являющуюся при этом частью конечного перечисления.
Да, вот этот пример, вроде бы, то что надо
В вариантах перечисления может быть разный набор полей? В Rust можно создать такое перечисление:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(u8, u8, u8),
}
Да, может
from dataclasses import dataclass
from enum import Enum
@dataclass
class Pending:
s: str
@dataclass
class Active:
a: int
b: str
class UserStatus(Pending, Active, Enum):
PENDING = Pending("Pending")
ACTIVE = Active(42, "Active")
Будет ли в такой схеме Pending("Foo")
инстансом UserStatus
?
А почему он должен им быть? Вопрос был в том, можно ли в варианты перечисления засунуть разные структуры (наборы полей).
Потому что в статье было упомянуто, что в Rust можно "связать статус пользователя с дополнительной информацией", а тут утверждается что и в Питоне можно. Но я не вижу этого связывания (в том смысле этого слова, который был в цитате) в ваших примерах.
А почему вы решили что связывание в данном контексте обязательно наследование, а не, например, композиция?
Можете вместо наследования использовать что угодно, лишь бы Pending("Foo")
имело хоть какое-нибудь отношение к UserStatus
.
Связывание в данном контексте означает возможность связать значение перечисления с произвольном значением, а не с константой. То есть Pending("Pending")
, Pending("Foo")
и Pending("Bar")
должны быть полностью равноправны как значения перечисления, чего в коде выше не наблюдается.
Комбинируя увиденное в других комментариях, могу предположить как это должно выглядеть на Питоне на самом деле:
@dataclass
class Quit:
pass
@dataclass
class Move:
x: int
y: int
@dataclass
class Write:
value: str
@dataclass
class ChangeColor:
r: int
g: int
b: int
Message = Union[Quit, Move, Write, ChangeColor]
Если только я не допустил какой-нибудь глупой ошибки — это и есть аналог перечислению из Rust. А вовсе не та ерунда, которую пытались написать cbmw и menstenebris...
Не то чтоб ошибки, но последняя строка скорее бессмысленная, потому что пользоваться ею неудобно будет.
Вот так-то так она более применима
from typing import Union
class SomeClass:
message: Union[Quit, Move, Write, ChangeColor] = None
и обозначает что у поля SomeClass.message ожидаемые(!) типы значений — это перечисленная четвёрка.
Но это вовсе не мешает присвоить туда что угодно
a = SomeClass()
a.message = Write("Test line")
a.message = ("Nope", "still", "can", "assign", "anything", True)
А полного прямого аналога предложенного исходно перечисления я в Питоне вот так и не назову. Всё-таки "подсказка по типам" Питона принципиально отличается от жёсткого задания типов Раста, у них даже задачи разные.
Всё так, Union не делает проверок в runtime.
Если совсем придираться, на мой взгляд, должно быть как-то так:
from typing import Optional, Union
class SomeClass:
message: Optional[Union[Quit, Move, Write, ChangeColor]] = None
Не то чтоб ошибки, но последняя строка скорее бессмысленная, потому что пользоваться ею неудобно будет.
А что именно там неудобно будет?
message: Union[Quit, Move, Write, ChangeColor] = None
Э-э-э, а None-то тут зачем и откуда?
Вот для вас это «Э-э-э», а например для разработчиков Pydantic всё нормально)
Э-э-э, а None-то тут зачем и откуда?
Да просто чтобы выставить значение-по-умолчанию "значение не выставлено". Считать ли это антипаттёрном — вопрос сильно дискуссионный, но именно в таком значение None часто используется.
from dataclasses import dataclass
from enum import Enum
@dataclass
class Status:
s: str
class UserStatus(Status, Enum):
PENDING = Status("Pending")
ACTIVE = Status("Active")
print(UserStatus.PENDING.value)
Проблема в другом. В python перечисление это костыль над системой классов, им даже пришлось отдельную реализацию писать чтобы он не жрал столько памяти. В Rust же перечисление это абстракция нулевой стоимости, можно использовать по поводу и без. Но из за этого кое какие вещи становятся недоступны, например в diesel не могут нормально скрутить вместе enum в rust и enum в postgres.
Могут ли варианты перечисления иметь данные разных типов?
Будет ли в такой схеме Status("Foo")
инстансом UserStatus
?
Простите, а зачем? То есть "для какой цели/задачи"
Потому что я не уверен, что понимаю суть Вашего вопроса. По структуре классов Status является базовым классом для UserStatus и не может быть инстансом унаследованного класса, тем более через множественное наследование.
И мне не совсем понятно зачем там вообще использовано то множественное наследование, enum.Enum этого не требует. С тем же успехом работать будет и "class UserStatus(Enum):"
Потому что перечисления в Rust (которые на самом деле не перечисления) работают именно так, а тут вроде как пытаются построить их аналог (который и не аналог вовсе)...
Хмм.
В этом случае сама идея "сделать из питона раст" видится мне наибольшая ошибка — ибо конечно же результат не будет столько же удобен/гибок/удобочитаем, как исходный Rust! Из раста если питон делать тоже будет непойми что.
Никто аналоги тут не строит, вся эта ветка обсуждения началась с того что мы в статье действительно показали python enum немного однобоко (не такими гибкими).
А к вопросу о том как работают перечисления в расте, вот вам небольшой контрпример:
struct Move(i32, i32);
enum Message {
Quit,
Move(Move)
}
Какое отношение имеет структура Move
к варианту Message::Move
?
Конечно, Рамблер делала ужасные вещи, но разве мы должны ставить минусы под постами, которые вообще никакого отношения к тому инциденту? Ведь бизнес хотели отжать одни люди, а писали статью другие. Так значит мы должны принижать труд вторых?
Какое дело все эти рассуждения имеют к содержанию и качеству статьи, за которые собственно плюсы и получаются?
Возможно, музыкант был грубияном, наркоманом и мерзким чуваком, но если музыка у него хорошая — разве нельзя признать, что она хорошая?
Рамблер большой, он делал плохие вещи, делал хорошие вещи, в нем работает куча людей.
Какой уровень взаимодействия с Рабмлером делает тебя соучастником наезда на автора NGinx?
Только наивный будет считать, что бизнес будет всегда чистеньким, особенно больших размеров — везде есть люди со своими интересами, которые не всегда благие для остальных. Но судить по паре десятков людей, решивших попиариться или что-то хуже сделать о тысячах людей, которые любят свою работу и/или просто пишут тут статьи — как воспринимать мир черно-белым. Принцип коллективной отвественности не стоит тут навязывать, да и никто не предложит ВСЕМ этим тысячам сотрудников достойное место чисто из солидарности, а у кого-то ипотека, дети…
Удобно быть моралистом, когда лично тебя это никак не касается — можно встать в атакующую позу и не считаться с людьми, потому что вы правы в своем праведном гневе) И далеко не все сотрудники Рамблера были в восторге — поищите публичные письма их сотрудников, вроде были как раз тут, на хабре.
Все равно мы будем пользоваться Android, писать на Go и прочее-прочее).
Имидж компании состоит не только из негативных поступков, но и из положительных. Лично я не одобряю заведение уголовного дела, но и продолжать бессмысленное макание людей (которых причисляют косвенно к виновникам) в грязь не вижу смысла. Имиджевые потери для Рамблера и так были достаточно высокие — пусть отрабатывают, как могут. Даже если это простые статьи тут, которые кому-то пригодятся.
И если капнуть чуть глубже в те события: подавляющее в тот момент было именно пожелание «уходите из Рамблера».
И давайте размышлять абстрактно — кто-то обрушил своим недальновидным поведением имидж фирмы, причем осудил другого человека. Что будет дальше? Фирма должна развалиться из-за того, что теперь ее имидж плохой? А чем это отличается от «уходите из Рамблера» по причине того, что он перестанет существовать?)) Вариант «не чинить репутацию» равен «уйти из Рамблера» на длинной дистанции, просто не по своему желанию)
Вариант «не чинить репутацию» равен «уйти из Рамблера» на длинной дистанцииРазработчику важно работать на свою репутацию, тогда ничего не страшно. К тому же на длинной дистанции все уходят.
Согласен про работу на свою репутацию. Про уход — все хотят уходить из компаний с нормальной репутацией, а не оставаться мечеными. Пускай исправляются полезными для общества делами.
Осуждать программиста за его статью по вине какого-то менеджера (которого он и никогда не видел/слышал скорее всего в наших реалиях) есть дело не самое адекватное. Какой срок давности у данного преступления? Или Рамблер уже никогда не отмоется?))
Минус и за просто вопросы или цитирование исторических фактов могут поставить. Молча ибо "сам догадайся чем ты мимопроходившему сообществу не мил". Особенно когда кроме минуса ответить то и нечего ибо документальный факт, а чью-то мозоль отдавил или картину мира пошатнул.
Так что — остаётся относиться к хабру как к просто месту общения просто случайных людей. Ничем, по факту, айтишники не отличаются от кого угодно стереотипно другого. И хабр тут не исключение.
Попытка дискредитации через сопричастность к фирме (а не к решению по данной проблеме) — это «сильный» аргумент. Напишешь хоть слово — сразу враг?) С подобной нетерпимостью даже диалог не выстроишь.
Я не просил поддерживать Рамблер. Не писал о том, что невиновата организация — только люди, которые управляют (которые уже почти все свалили или будут заменены). Взваливать ответственность на сотрудников — как вспоминать немцев из ВОВ и вешать их вину на современников; как запрещать олимпийцам выступать под флагом страны, в которой они родились и любят (страну, а не государство). У всех должен быть шанс исправиться, да и не отвечают сыны за отцов.
И если мы перешли на личности — у вас у самого карма далеко внизу, что намекает на не самый адекватный способ ведения диалога самим обществом. Да и классификация про клоунов заочно в профиле — это вряд ли нормально. С чужим мнением принято считаться, даже если оно вас не устраивает — априори. Вы сами об этом просите в профиле) Более того — вы пытаетесь задавить не конкретные действия, а чувства других людей: кому было все равно, тот уже ушел на волне «как ты можешь там работать».
Это лишь обозначение вашей ангажированности.
Я не стал скрывать свою личность, хотя и мог.
Ваш пример изначально некорректный.
Потому что вы не уловили суть примера — сейчас никто из рядовых сотрудников не совершает преступление. Да и не принимали рядовые сотрудники решение (а уж тем более какое-либо действие) в отношении данного дела, в отличие от тех же рядовых нацистов.
Моя карма свидетельствует лишь о том, что я не стесняюсь в выражениях.
И зря — слово режет сильнее ножа. Для себя вы просите корректности в выборе слов.
Не у всех, а только у тех кто признал ошибку.
Где вы увидели НЕ признание ошибки?) Ошибка есть — это факт, о чем мы уже писали ранее.
Вы читали чем кончилось дело? Дело передали в компанию, которая продолжила судебное преследование уже в международном формате. Заслуживает ли права на исправление тот, кто только делает вид что исправился?
Если вы настолько в курсе, то стоит углубиться еще больше в последние новости — Мамут больше не у руля, виновник самоудалился (при этом попортив кровь Сберу, который ни сном-ни духом об этом был). Да, разбирательство продолжается, и всем очевидно, что оно закончится в пользу автора — проще в суде заткнуть одну российскую компанию по неправомерной претензии, чем заставить весь мир платить за софт с оглядкой на 15 лет использования.
проще в суде заткнуть одну российскую компанию по неправомерной претензии, чем заставить весь мир платить за софт с оглядкой на 15 лет использованияА-а… так вы считаете, что весь мир должен платить Рамблеру???
неправомерной претензии
В итоге недальновидной и жадной (и несколько жалкой на фоне сделки F5 Networks по Nginx) попытки нагреться высшего руководства перед уходом сильно пострадал имидж фирмы — а также был причинен ущерб автору. О какой выплате по авторским правам может идти речь, если все понимают, что доказательств нет?
Так как указано в статье в Python обычно не пишут. Python-разработчик написал бы что-то такое:
def serialize(user_status: UserStatus) -> str:
mapping = {
UserStatus.PENDING: 'Pending',
UserStatus.ACTIVE: 'Active',
UserStatus.INACTIVE: 'Inactive',
UserStatus.DELETED: 'Deleted',
}
return mapping[user_status]
Соотвественно если вдруг в функцию будет передано значение, которого нет в UserStatus — будет исключение.
Сейчас в python 3.10 pattern matching подвезут — станет полегче.
Чем перечисление лучше словаря?
Экземпляр перечисления является самим перечислением. В результате чего нормально начинает работать типизация и исчезают ошибки с опечатками в ключах словаря, просто потому что IDE наконец понимает что там должно быть, а чего нет. Плюс перечисление невозможно случайно изменить в процессе работы программы и не нужно инициализировать.
Нужно ясно понимать области применения и преимущества каждого типа языков и:
— Выбирать язык сознательно, ориентируясь на его плюсы.
— Стиль программирования должен быть тоже разный.
А то я насмотрелся на то, как люди приходят в питон из джавы и начинают лепить скажем геттеры и сеттеры, ну или посмотрят на модные веяния и давай тащить в питон статическую типизацию.
def get_user_by_uuid(user_uuid):
pass
def get_user_by_uuid(user_uuid: UUID):
pass
UUID здесь спокойно мог бы быть и обычной строкой.
Когда мне кажется это полезным и нужным, я указываю тип аргумента в комментариях. Но в большинстве случаев это вообще не нужно. Тем более, что не обязательно всегда передавать один и тот же тип аргумента в функцию.
Пример простой, а на деле часто бывают более интересные варианты, где объект будет dataclass'ом, где один из аттрибутов будет enum'ом и так далее. IDE на каждый уровень будет показывать подсказки и типы. Я понимаю, если у вас какой-то pet-проект, где кроме вас никого, но если это библиотека и у нее есть публичный api, то почему бы не облегчить жизнь тем, кто будет ею пользоваться?
Мне, например, очень не нравится кадый раз обращаться к документации (при условии что она есть) чтобы понять какие типы данных я могу передать в функцию. Это скорее дело привычки и определяется личными приоритетами разработчика.
Так то и вынесение куска метода в другой метод можно обозвать как "ненужный шум в тексте".
use std::collections::HashMap;
fn main() {
let mut d = HashMap::new();
d.insert("key", "value");
let v = vec![1, 2, 3];
let t = (d, "world");
}
Тогда лучше уж C# рекомендуйте
Zero cost abstractions — это про стоимость для производительности, а не для мозга.
4 типа указателей, 2 строки (Я про срез), на первых порах — войны с borrow checker-ом, операции над срезами
Нужно ясно понимать области применения и преимущества каждого типа языков и:
— Выбирать язык сознательно, ориентируясь на его плюсы.
А какие, по вашему, плюсы у динамической типизации?
Скорость написания скрипта, например. Если объект поддерживает некое производимое над ним действие — скрипт просто работает; если нет — выбрасывается исключение.
И если нужно — то в Питоне объекту возможность нужное действие производить можно и на лету добавить.
Или переиспользовать одно имя переменной под разные типы объектов в рамках одного метода.
dict((v: k) for k, v in some_entities.items()) без разницы что за объекты в k и v до тех пор, пока у каждого из попавших в цикле итерирования объектов можно получить хэш. И вот уже словарь перевёрнут, значения стали ключами.
Или переиспользовать одно имя переменной под разные типы объектов в рамках одного метода.
Это не только с динамической типизацией возможно:
let a = "some string";
let a = 10;
let a = Some(2.0);
С остальным спорить не буду, хотя я предпочту получать ошибку компиляции, а не исключение в рантайме.
Скорость написания скрипта, например
Возможно, я что-то делаю не так, но на статически типизированных языках я пишу как-то быстрее.
Если объект поддерживает некое производимое над ним действие — скрипт просто работает; если нет и управление заходит в ту ветку кода, в которой пытаются вызвать неподерживаемое действие — выбрасывается исключение.
Поправил.
Или переиспользовать одно имя переменной под разные типы объектов в рамках одного метода.
А это вообще ортогонально типизации.
dict((v: k) for k, v in some_entities.items()) без разницы что за объекты в k и v до тех пор, пока у каждого из попавших в цикле итерирования объектов можно получить хэш.
let flipped: HashMap<_, _> = dict.into_iter().map(|(k, v)| (v, k)).collect();
Причём в этом коде я узнаю о том, что значений не поддерживают хэширование, до запуска, а не после.
Скорость написания скрипта, например.Вывод типов в современных языках довольно развит. Посмотрите на тот же Crystal, там почти не требуется указывать тип.
Или переиспользовать одно имя переменной под разные типы объектов в рамках одного метода.Это уже реализовано как в Rust, так и в Crystal
А разве репл только для динамических языков есть?
REPL
Есть и для статически типизированных ЯП.
манки патчинг
Не вижу в этом ничего хорошего.
duck typing
А, чудесная возможность принять жёлтый снег за лимонное мороженое.
Не вижу в этом ничего хорошего.
…
А, чудесная возможность принять жёлтый снег за лимонное мороженое.
Ну так бы и сказали сразу что "хорошего(по версии AnthonyMikh)".
То, что лично Вы и ещё кто-то не хочет считать плюс плюсом — он не становится минусом. Он просто не нравится лично Вам и/или не подходит Вашим задачам/требованиям/руководству/феншую. Не более того.
Однако согласитесь, что прослеживается тренд на разворот в сторону статической проверки типов у многих популярных динамических языков.
Не могу, как не могу сказать и обратного. Недостаточно знаком со всеми "многими популярными динамическими языками" для такого вывода.
php8
python
ruby -> sorbet
Не совсем понял, конкретно где в питоне "разворот в сторону статической типизации" происходит. В Питоне есть "аннотация типов"(то, что обсуждается в статье и комментариях), которая подсказка для пользователей библиотек/API, для создания документации и для подсветки возможных ошибок IDE.
Ни на что большее, нежели аннотация типов, она и не претендует, хотя её и можно задействовать для квази-статической проверки.
Поэтому я и написал: в сторону "статической проверки типов", а не "статической типизации". Полноценную статическую типизацию в интерпретируемых языках сделать не просто, но даже такие огрызки оказываются полезными.
duck typingСойдёт? Crystal
class A
def initialize()
@v = 0
end
def set(v)
@v = v
end
def get()
@v
end
end
class B
def initialize()
@v = ""
end
def set(v)
@v = v
end
def get()
@v
end
end
a = A.new()
a.set(5)
b = B.new()
b.set("5")
[ a, b ].map { |i| puts i.get() }
Я выберу читаемость над эффективностью если это не bottle neck.
Если не хочется читать эту статью или невтерпеж ждать второй части материала, можно посмотреть видео нашего выступления.
А можно сделать ссылку на таймкод, на котором начинается часть выступления, не покрытая в статье?
пока опциональная статическая типизация в Python выглядит недостаточно мощным инструментом, позволяющим избавиться от всех ошибок, связанных с несоответствием типов
и вероятно никогда и не станет. Начнём с того, что это не "статическая типизация", а статическая проверка типов. Если в язык занесут типизацию времени исполнения, то возможно это будет шагом к "избавлению от всех ошибок", а возможно — началом конца питона. А пока что вам ничто не мешает обойти эти проверки в случае необходимости.
Во-вторых, mypy это всего лишь один из инструментов проверки, который не реализует на 100% весь спектр возможных проверок (он не является референсом и постоянно дополняется). Есть и другие тулзы.
а возможно — началом конца питона.
Имхо, конец уже начался, ну или по крайней мере ухудшения того, за что так здорово питон выдвинулся. По моему мнению ошибки, если не сказать хуже это:
— 3-я версия питона с поломанной совместимостью.
— Внедрение подсказок статической типизации.
— И вообще затаскивание в ядро языка вещей типа асинхронщины.
Идем по пути превращения питона в подбие С++, ужасного монстра, на полном подмножестве которого никто толком не умеет писать.
Хмм. Готов поспорить )))
— 3-я версия питона с поломанной совместимостью.
Обратная совместимость — это зло, что можно наблюдать на примере жаваскрипта )) Но согласен, можно было получше организовать. 3.0, 3.1, 3.2 — неюзабельные версии. Что ж, на ошибках учатся. С другой стороны проблему unicode vs str без поломки совместимости было не решить, а надо было решать. Или поддержка "старых" классов...
— Внедрение подсказок статической типизации.
Крутая вещь. В больших проектах очень помогает. Главное, никто не форсирует, не нужно — не используй, нет проблем.
— И вообще затаскивание в ядро языка вещей типа асинхронщины.
Асинк — это киллер фича и то, что вернуло довольно большой маркет бэкендного веба, который заметно стал утекать на ноду. Причём асинк в питоне реализован на мой взгляд почти идеально с точки зрения пользователя. Конечно, сама концепция сложна и требует тщательного планирования и понимания нюансов, но АПИ прекрасен. И опять же, никто не форсирует.
А вцелом по развитию питона, так оно как раз, кажется, сильно замедлилось после асинка.
Последнее значительное нововведение — f-строки в 3.6.
В последнее время, с 3.7, особо ничего нового и не появилось, так по мелочи.
А насчёт ухудшений… было бы интересно услышать ваше мнение.
def last(array: List[int]) -> Optional[int]:
if len(array) == 0:
return None
return array.pop()
Такой код будет корректным и не вызовет исключений, однако многочисленные проверки очень сильно увеличивают кодовую базу и сводят на нет всю простоту и лаконичность, которой славится Python.
Философия питона вообще-то Better ask forgiveness, than permission. Согласно этому, следовало бы использовать
def last(array: List[int]) -> Optional[int]:
try:
return array.pop()
except IndexError:
return None
К тому же, это исключение не обязательно ловить в месте, где оно возникает. Этот "минус" с точки зрения типобезопасности существенно экономит время разработки, потому что не нужно перечислять все возможные исключения только ради их перечисления. Но в теории для питона ничто не мешает принять какой-нибудь новый PEP, в котором было бы описано поведение статической проверки исключений.
В целом, как у любого инструмента, у статической типизации есть свои плюсы и минусы. Для компилируемого языка статическая типизация — это мастхэв, для интерпретируемого — это просто удавка на шее.
И совершенно не обязательно заниматься этим:
давайте перепишем Python-код по аналогии с Rust, не вызывая исключения.
Лучше писать на питоне как на питоне, а не по аналогии с Rust. Ну и наоборот :)
Да, согласен. С другой стороны, я написал,
в теории для питона ничто не мешает принять какой-нибудь новый PEP, в котором было бы описано поведение статической проверки исключений.
Если представить, что тайпхинтингу в питоне столько же лет, сколько самому Расту (который ещё сколько-то лет до релиза пилился), то в общем уже и тот результат, что имеем, неплохой. И я уверен, он будет развиваться.
Питон и вообще интерпретируемые языки пожалуй никогда не сравнятся с компилируемыми по возможности тайпчекинга. И таки да, там, где тебе помог бы компилятор, ты вынужден помогать себе сам. Но где делать эту проверку, ты тоже решаешь сам, и в этом плюс. :)
Всегда будут 2 мнения. Одним солёное, другим сладкое. Код на питоне — лучший код из всех возможных императивных языков, имхо. И всё вами перечисленное без сомнения гениально. Даже если кому-то это ошибка, то по крайней мере https://www.northeastern.edu/graduate/blog/most-popular-programming-languages/ вот это говорит о том, что солидной части разработчиков она может приносить 120К баксов в год ))) а на вашем ЯП без None и исключений сколько?
None — это не null, не путайте, к главной ошибке программирования не относится. None — это нормальное значение для нормального типа.
А вот исключения, да, неприятненько.
None — это не null, не путайте, к главной ошибке программирования не относится. None — это нормальное значение для нормального типа.
У вас никогда прод не падал с ошибкой "Ой, у NoneType
не такого метода"?
None в Python — это просто переименованный null, ничем от него не отличается.
Option::None в Rust это уже совсем другое дело, и, действительно, более безопасная концепция.
Основная разница None и Null состоит в том, что None не вызывает undefined behavior. А Null вызывает. [null + 100500] куда запишет? Никто не знает.
Надо различать сишный низкоуровневый NULL и null в ЯВУ типа java/sql.
Нельзя запретить struct {int a;int b;} * x = NULL
потому что железо разрешает такое (а если вам до лампочки железо, то не пишите на си). Но при этом, написать struct {int a;int b;} x = NULL
(не указатель!) в си нельзя. А во многих ЯВУ можно и лично меня это шокирует.
Это в каком-таком языке (кроме питона с его "хинтами") можно иметь указанный тип и его нарушать в той же строке?
Python любим как раз за низкий порог вхождения, в отличии от rust. Начните писать на питоне в манере rust, и вы получите другой язык. Который возможно найдет своих поклонников, но будет ли их так же много, сколько сейчас есть у питона? Сомневаюсь.
Пример perl6 должен заставить задуматься. Язык, в конце — то концов сделали, но успех пятерки ему даже и не сниться. Непродуманные изменения, не поддержанные сообществом, могут толкнуть питон на ту же дорожку.
Есть nim, который достаточно похож на питон, не один к одному, но все таки. Компилируемый, строго типизированный, с достаточно лаконичным синтаксисом. Сколько человек могут похвастаться его знанием?
В самом питоне есть многим, если не всем, известный пример не очень удачного заимствования — это django. Я пробовал писать на рельсах и джанго. Сравнения не в пользу последнего. Хотя отдаю должное, django появился к месту и ко времени, и не мало способствовал росту python — сообщества. Только, на мой взгляд, сейчас уже устарел и предпочтение лучше отдавать тому же flask, например.
Да и rust ни куда не денется, и питон так и останется подражателем.
зачем в питон тащить чуждые концепции.
Очень просто объясню.
Низкий порог входжения никуда не денется от того, что в ЯП есть разные фичи. Но как только тебе надо шагнуть за порог, если за ним пустота, тебе придётся менять ЯП на другой, с более высоким порогом. Питон же как раз тем и хорош, что его не надо менять как перчатки.
Дополнительные фичи, если нужны, должны вноситься в виде внешних библиотек.
Если же нужно то, что питон не может обеспечить, скажем быстродействие, нужно испольовать или внешние библиотеки, либо переписывать критические части кода на другом языке.
Насчет же статических типов и/или статических подсказок типов, то вроде как эта фишка должна помогать бороться с ошибками в сложных программах. Но, в моем личном опыте, ошибки свяазнные с типами или стоят на одном из последних мест по проблемности, или вызывают исключение, что стопроцентно укладывается в концепцию раннего падения.
Мне кажется, что если у кого-то ошибки, связанные с типами вызывает очень много трудностей, настолько, что нужно менять язык, то вероятно, что-то не так с подходом к написанию программ на питоне.
Для питона лично я бы хотел, чтобы ядро языка сохранялось бы как можно более простым и компактным.
А все писать только на питоне — это невозможно да и не нужно.
базовый набор фич должен быть ограничен
Если вы говорите о синтаксисе, то в питоне он и так довольно сильно ограничен. И расширяется крайне редко, в отличие от ЖС, в котором каждый год пол-языка добавляется. Вот гляньте в https://habr.com/ru/post/533672/ если интересно. И там мои комментарии, зачем всё это туда тащить, получили минусов больше, чем за все предыдущие 10 лет на хабре =) Каждому своё.
Дополнительные фичи, если нужны, должны вноситься в виде внешних библиотек.
В идеале — да. Но когда это невозможно, приходится синтаксис расширять. В случае с async например — @coroutine и yield / yield from очень сильно запутывали, и безусловно async/await на порядок проще. Или f-string, тоже значительно упростило многие вещи.
вроде как эта фишка должна помогать бороться с ошибками в сложных программах. Но, в моем личном опыте,
Ну опыт у всех разный. Лично мне (и со мной согласны остальные 15 человек в моей компании) оно сильно помогает работать с PyCharm, подсказки, переход к определению, подстановка атрибутов, в общем куча плюшек. Может, это даже важнее гипотетических ошибок, которые у опытных программистов и так нечасто бывают. Да и не нравится — не используй, в чём проблема, никто ж не форсирует.
А все писать только на питоне — это невозможно да и не нужно.
не всё. Но если у вас проект в 100000 строк, то чем богаче язык, тем проще писать.
Почему вы так против? В конце концов, вас кто-то заставляет учить все фичи? Вы можете использовать столько, сколько хотите. Можете даже форкнуть и удалить лишнее =)
Почему вы так против? В конце концов, вас кто-то заставляет учить все фичи? Вы можете использовать столько, сколько хотите. Можете даже форкнуть и удалить лишнее =)
Потому, что мне нужно решать производственные задачи, а для этого, мне нужно быстро читать исходники, написанные другими. И когда фич очень много, получается, что каждый пишет по своему. И приходится вместо того, чтобы просто читать то, что делается, причем простые вещи, разбираться с тем, что наворочена куча откровенно не нужных в этом месте вещей.
Если вы ограничите область использования языка, то пользоваться им будут меньше и контрибьютить в опенсорс тоже будут меньше. Такими темпами может и читать вскоре станет особо нечего.
Если же нужно то, что питон не может обеспечить, скажем быстродействие, нужно испольовать или внешние библиотеки, либо переписывать критические части кода на другом языке.
Проще уж тогда отказаться от Python в пользу этого другого языка, если он еще и высокоуровневый код позволяет нормально писать.
Насчет же статических типов и/или статических подсказок типов, то вроде как эта фишка должна помогать бороться с ошибками в сложных программах.
Ага. Поэтому давайте большие и сложные программы писать на Python не будем, раз мы не хотим, чтобы он становился комфортным выбором для них. Может тогда вообще лучше отказаться от Питона и осваивать язык, который подойдет и для больших, и для малых проектов? Пусть и с не столь быстрым стартом в обучении.
Я просто хочу сказать, что ваша позиция на самом деле противоречит вектору развития языка (любого языка, претендующего на звание языка общего назначения). Конкуренция в среде ЯП пока только усиливается и у того, кто сознательно станет сдавать позиции, ограничивать возможности своего языка, шансов победить в борьбе будет сильно меньше. Но отчасти быть может вы правы, и Питону стоит подвинуться, вернуться в сферу одностаничных скриптов и пропустить более подходящие языки общего назначения на занятые им места.
Я просто хочу сказать, что ваша позиция на самом деле противоречит вектору развития языка (любого языка, претендующего на звание языка общего назначения).
Да, я против превращения всех ЯП в монстров типа C++. А тенденцию я вижу именно такую.
ИМХО, питон выбез наверх именно в силу своих плюсов, таких как простота ядра языка, хорошая читабельность исходных кодов написанных на нем, REPL, duck typing, жестких требовани1 к оформлению (отступы).
ИМХО статическая типизация в чилсло этих козырных фишек не входила и для меня она не важна, так как в моей практике количество ошибок связанных с типами ничтожно. А вот возможность подсунуть мок объект в любое место — неоценима.
ИМХО статическая типизация вчилслочисло этих козырных фишек не входила и для меня она не важна, так как в моей практике количество ошибок связанных с типами ничтожно
У вас просто типов нормальных не было.
А вот возможность подсунуть мок объект в любое место — неоценима.
Тривиально реализуется на любом языке с интерфейсами или его аналогами.
так как в моей практике количество ошибок связанных с типами ничтожно.А если mypy --strict? )
Если вы говорите о синтаксисе, то в питоне он и так довольно сильно ограничен. И расширяется крайне редко, в отличие от ЖС, в котором каждый год пол-языка добавляется.
В моем представлении есть два ужасных ужаса, C++ и JS где наворочены горы ненужного.
Ну и еще в какой-то степени PHP, куда тоже тащат все, что увидят в других языках. Питон сравнительно простой и хотелось бы сохранить эту ситуацию как можно дольше.
С питоном такой финт не работает: питон компилирует модули по мере обращения к ним. Да, можно проверять модуль перед релизом, но это не то, потому что это не сработает. Стоит поменять api пакета, оставив иерархию классов прежней (это можете быть необязательно вы, за вас это может сделать один из авторов, творение которых вы помянули в депсах), и питон снова кувыркнется только при попытке вызова «поломанного» метода (Проверка typehint-а недостаточна).
Чем подход rust в обработке ошибок, например, отличается от питона, что его нужно тянуть в язык отдельно? Там на невосстановимые ошибки паникуем, на остальные возвращаем соответствующий результат, в питоне ни что не мешает делать так же, кидать эксепшн или возвращать результат, который не обязан быть простым по своему устройству.
Даже если rust подобный синтаксис подтянут в питон, его реализация должна будет нормально соседствовать с прежним подходом, учитывая размер существующей кодовой базы, смешивание 2 подходов, как минимум, не должно порождать ошибок, а то, что оно будет — это объективная реальность, а как максимум, данные изменения не должны приводить к просадке скорости существующего кода. Не факт, что это просто будет сделать.
В чем я наверное готов согласится с автором публикации — это в том, что, возможно, к инструментам проверки качества кода на питоне стоит присмотреться еще раз, в контексте идей привнесенных rust и изменений в самом python.
Дело в том, что тип Result
в Rust, во-первых, является тип-суммой, работать с которым нельзя без того, чтобы явно не рассмотреть оба возможных варианта. А во-вторых, он помечен атрибутом #[must_use]
, что не позволяет просто забыть его обработать. И проверки этих случаев обеспечивает компилятор статически. Python не сможет такого by design.
Я пробовал писать на рельсах и джанго. Сравнения не в пользу последнего.
джанго — это всё-таки не питон, и вряд ли похвастается первым местом среди веб-фреймворков даже на питоне, не говоря уж о вообще. Плюс разрабы джанго — ребята крайне консервативные и некоторые баги и фичереквесты не закрывают по 10 лет. Насчёт устарел, отнюдь. Он вполне неплохо со своими обязанностями справляется. А сравнивать джанго с фласком, это как сравнивать грузовик с самолётом.
Да и rust ни куда не денется, и питон так и останется подражателем.
Это ещё кто кому подражатель, интересно. 5-летний Раст или 30-летний питон?
Непродуманные изменения, не поддержанные сообществом
это намёк на оператор моржа :=
? :=) не в восторге от него, но пока что не особо и встречаю. Возможно, пройдёт пара лет, все попривыкнут.
Есть nim… Компилируемый, строго типизированный
так может в этом то и проблема?
Нужен компилируемый язык? — Жава, С, С#, тра-та-та… пара сотен таких есть, навскидку. Нужен интерпретируемый? Ну не РНР же, или ЖС с 900-страничным мануалом по синтаксису и встроенным объектам (без библиотек), где одним лишь описанием того, как работает конверсия типов, 10 страниц заняты, и где фичи можно (то есть нужно) включать флагом в файле настроек или вообще использовать внешний транслятор? Или перл, при всех его достоинствах пригодный разве что статью на хабре распарсить. Да, ещё руби, но по близости к человеческому языку и по читаемости питон всё равно намного впереди.
Нужен язык со строгой типизацией времени выполнения? GOTO предыдущий пункт. Не нужна такая типизация? Опять же, см. выше.
Так что пока не вижу причин беспокоиться за судьбу питона.
Во-первых в Rust'е можно иметь динамические типы (dyn
).
Во-вторых, типизация питона ужасающа. Типы в кавычках, отсутствие Self (хотя, казалось бы, что мешало завести вместе с остальными типами?)
class X:
@classmethod
def from_int(cls: 'X', i: int) -> 'X'
....
Если вам в этом месте не захотелось плюнуть на типизацию в Питоне, то вы мало писали на Rust.
Вообще, если сравнивать питон с Rust'ом, надо начинать с вот таких вот штук:
>>> a=([],)
>>> a[0].append(0)
А вот место, в котором надо ругать Rust — это за отсутствие yield. Нет ни одной причины, почему нельзя было не завести yield в язык. Но нет.
from __future__ import annotations
Да, придется дописывать лишнюю строчку (или прописать её добавление в isort через add-import), но тогда кавычки использовать не нужно.
А вот место, в котором надо ругать Rust — это за отсутствие yield. Нет ни одной причины, почему нельзя было не завести yield в язык. Но нет.
Согласен. Постоянно писать руками итераторы и ухудшать читаемость кода довольно сильно напрягает, особенно когда знаешь что в других языках проблему решает простая замена return на yield.
Однако, эту функциональность явно вскоре добавят. Есть даже экспериментальные крейт, но он требует включения фич.
Выше дали прелюбопытнейшую rfc: https://doc.rust-lang.org/beta/unstable-book/language-features/generators.html
Если вам в этом месте не захотелось плюнуть на типизацию в Питоне
Расшифруйте. Отсылки вида "на-раст-смотрите" говорят примерно ни о чём.
from __future__ import annotations
from dataclasses import dataclass
@dataclass
class User:
name: str
def change_name(self, new_name: str) -> None:
self.name = new_name
@classmethod
def new(cls, name: str) -> User:
return cls(name=name)
user = User.new("Тест")
user.change_name("Тест2")
struct User {
name: String
}
impl User {
fn change_name(&mut self, new_name: String) {
self.name = new_name;
}
fn new(name: String) -> Self {
Self{name}
}
}
fn main() {
let mut user = User::new("Тест".to_string());
user.change_name("Тест2".to_string());
}
В rust сразу видно три интересные концепции.
1) ключевое слово Self для указания как на тип так и на саму структуру для которой реализуется метод. В python тоже известно что за тип будет подставляться в cls но использовать его для типизации нельзя.
2) Все аргументы в структуру подставляются по именам. но если имя переменной и имя поля структуры совпадают то можно указывать в упрощенном варианте
3) В rust нет разделения на staticmethod и на classmethod. А различие между method и classmethod компилятор сам понимает в зависимости от того передается в функцию self или нет. И доступ до метода будет разный через "." или через "::"
Я так и не увидел, где же тут "после раст захотите плюнуть на питонскую типизацию" начинать. Разные языки с разной историей по-разному подошли к вопросу. Нихил нови.
Кроме того, истовое желание писать на питоне как на расте(или наоборот) зло куда большее, нежели ненравящаяся типизация.
Я не могу просто взять и пройти мимо данного комментария.
От типизации, что вы указали, код выглядит грязным. Быстрее питон работать не будет, зачем она нужна да ещё и так некрасиво?
class User:
def __init__(self, init_name):
self.name = init_name
def change_name(self, new_name):
self.name = new_name
user = User("Тест")
user.change_name("Тест2")
Вам действительно неизвестно, какой инстанс класса вы создадите, вызывая User
? Или Вам неизвестно, как работает конструктор класса в python?
К самой статье у меня тоже много вопросов, но все они решаются данной строкой:
Это, в свою очередь, позволяет более безопасно с точки зрения типов описывать какую-то изолированную или бизнес-логику, что само по себе уже является огромным плюсом.
Что такое можно писать, и каким образом, чтобы потребовалась типизация на питоне, я реквестирую примеры.
Неужели кто-то достаёт из чужих продуктов обфусцированный код и пытается его пихать в свой продакшен, и ему сложно понять, что же такое var2314 = randomCls2149(a1, a2, a3)
?
Зато совершенно непонятно, какого типа должен быть name. Сегодня вроде бы вполне нормально работает строка, а через некоторое время уже на продакшене выясняется, что автор подразумевал какую-нибудь специальную структуру UserName и забыл отметить это в документации, и в итоге всё падает с ошибкой AttributeError: 'str' object has no attribute 'first_name', ведь в коде этого случайно никто не заметил и автоматические тесты случайно не покрыли этот кейс.
Осмелюсь предположить, что такая ситуация происходит, очевидно, на стадии разработки продукта, не на продакшене.
И эта ситуация весьма обычная, если нету документации или её лень прочитать.
А вот ставить ли продукт без документации и тестов на свой боевой сервер — это вопрос уже лично разработчика самому себе, готов ли он встречать эти фокусы или нет, и в общем не относится к типизации. Код без документации хоть на Раст, хоть на плюсах, будет сложным, и типизация не сильно скрасит особенности его поддержки.
Ну вот, как можно видеть прямо в этом посте, как минимум у Рамблера подобные штуки успешно доезжают до продакшена
Зачем документировать то, что можно указать типами? Документация — это информация для человека и она может не соответствовать реальным данным в коде, потому что происходит дублирование. Типы же понятны не только человеку, но и машине. И машина может проверить автоматически их соответствие. Там, где вводятся типы, специально документировать их наличие не нужно вовсе.
Я именно это же самое и имел в виду в своём ответе, что тип инстанса класса User будет очевидным. И вообще питон настолько очевидный и простой язык, что если где-то написано сложно или грязно, значит разработчик, скажем мягко, не старался.
А что до явной типизации — было бы весьма странно требовать коньяк в макдональдс, не находите? Когда я хочу типизацию, я пишу на любимом языке, в котором есть типизация.
И я буду первым, кто навсегда забросит питон, как только в нём (в чём я сильно сомневаюсь) будет требоваться явная типизация.
Ну так для очевидных вещей писать аннотации типов никто и не заставляет. Вот такой код содержит все нужные аннотации типов и успешно пройдёт проверку mypy --strict
:
class User:
pass
user = User()
Некоторые менее очевидные, но достаточно простые ситуации mypy способен додумать самостоятельно, например вот здесь никакие аннотации для списка не требуются, всё и так понятно по первому вызову append и mypy --strict
тоже успешно проходится:
class User:
pass
users = []
users.append(User())
А вот name из вашего примера выше — совершенно не очевиден и требует пояснений. И между документацией и аннотациями типов я выберу аннотации, потому что они, в отличие от документации, проверяются автоматическими инструментами и, как следствие, не могут врать. А вот с врущей документацией я сталкиваюсь регулярно.
Что такое можно писать, и каким образом, чтобы потребовалась типизация на питоне, я реквестирую примеры.
Blender с версии 2.80 и далее стал требовать от аддонов явно задавать поля для ряда структур, которые потом в сохранённый файл пойдут.
Но они используют свою сборку питона.
Расшифровываю: В методе класса нельзя написать сигнатуру, которая переживёт наследование.
class X:
def combine (self, other: 'X') -> 'X':
return other
Что станет с сигнатурой combine при наследовании X?
class Y(X):
pass
Какой возвращаемый тип у Y().combine()
?
TypeError: combine() missing 1 required positional argument: 'other' :)
Но если не занудствовать, то можно эмулировать Self костылями
from typing import TypeVar
Self = TypeVar("Self")
class X:
def combine (self: Self, other: Self) -> Self:
return other
class Y(X):
pass
Y().combine(Y()) # Revealed type is 'Y*'
Ох, всё ещё хуже. Я написал что-то странное. Я ожидал вернуть инстанс класса, а получился монстр, которого я просто разобрать не могу. Попробуйте сами поиграться с вашим кодом… Почему combine возвращает класс, а не инстанс?
В расте всё так просто было… self
— инстанс, Self
— его тип. А тут какая-то интроспективная жуть.
type(Y().combine(Y()))
<class '__main__.Y'>
Какой возвращаемый тип у Y().combine()?
Тип TypeError. Как и при X().combine()
И, поскольку эта типизация подсказка программисту и среде, в среде будет подсветка ошибки для любого переданного не-экземпляра класса(X или Y) или наследник от оных. Если среда поддерживает, конечно. В PyCharm будет.
А так — во всех случаях "то, что туда передаст программист". Эта типизация совсем не мешает передать что-то совсем другое, даже не унаследованное от ожидаемого типа… и получить валидный результат, если передающий программист озаботился совместимостью данных/протоколов с тем, что ожидал программист метода.
В плюсах именно что возможность передать что-то другого типа, но совместимое по протоколам/форматам, не борясь с правильным указанием "ожидаем такой вот тип объекта, и такой, и такой, и вот на такой надо перегрузку тоже указать" для компилятора.
В минусах — исключения времени исполнения и отсутствие гарантии что придёт именно ожидаемый тип объекта.
Кому что больше перевешивает.
class A
def a
puts "a"
return self
end
end
class B < A
def b
puts "b"
end
end
B.new().a.b
Да есть там причина. yield создаёт самоссылающуюся структуру данных, со всеми проблемами await. А await и появился-то совсем недавно...
И типизация замечательно работает: github.com/dry-python/returns
dyn Trait
не обязательно боксить:
fn validate(documents: Vec<&dyn Review>) -> Vec<&dyn Review> {
documents
.into_iter()
.filter(|document| document.approved())
.collect()
}
fn main() {
let documents: Vec<&dyn Review> = vec![
&Article,
&PhotoGallery,
&Test,
];
let approved = validate(documents);
assert_eq!(approved.len(), 3);
}
В статье мы расскажем о том, что заставило нас отойти от привычного стека технологий
Вот скажите, вы такие сидели писали на питоне много лет, много-много кода.
Вам говорят, сделайте новый проект. И вы «мы его будем писать на Раст, ничего что у нас нет в этом опыта, да и Раст мы первый раз видим, но этот новый и важный проект для бизнеса мы напишем только на Раст», и вам отвечают «ОК! Вот вам сундук с золотом»
Не стеб, правда интересно, как это происходит.
И еще, вот если ваша команда уволится, кто будет поддерживать одновременно проекты на питон и раст? Неужели таких спецов на рынке вагон?
насколько сложно python-разработчику начать писать на Rust
Вот здесь по-подробнее пожалуйста
if len(array) == 0:
А вы точно python программист? Python программист напишет тут
if not array:
Я люблю Rust но вы сравниваете тёплое с мягким. Если вы хотите находить ошибки локально вам не mypy писать надо а тесты.
Мне вот интересно, а что проще для изучения? C или Rust? А C++ vs Rust? Было бы хорошо изучить быстрый язык уровня С. Но отпугивает его монструозность, постоянное траханье с памятью, соответствующие баги, сложность изучения и использования. Rust сильно облегчает работу с памятью, не даёт сделать бОльшую часть багов, и вроде бы сильно меньше склонен усложнять жизнь, чем C\C++. Глянул я парочку crash cours-ов по Rust-у. Даже после питона, выглядит на удивление не сложно и дружелюбно.
Вот мне и стало очень интересно. Для новичка в системных языках, C\С++ сложнее в изучении и использовании чем Rust?
Rust глазами Python-разработчика