company_banner

Rust глазами Python-разработчика


    Привет! Мы – часть команды разработки «Рамблер/Медиа» (портал «Рамблер»). На протяжении трех лет мы поддерживаем и развиваем несколько больших python-приложений. Чуть больше года назад перед нами встала задача написать еще одно большое приложение – API к основному хранилищу новостей, и мы сделали это на Rust.


    В статье мы расскажем о том, что заставило нас отойти от привычного стека технологий, и покажем, какие плюсы по сравнению с Python есть у Rust.


    Мы не ответим на вопрос, почему выбор пал именно на Rust, а не Go, например, или на какой-либо другой язык. Также мы не будем сравнивать производительность Python- и Rust-приложений – эти темы достойны отдельного обсуждения.


    Этот материал написали cbmw и AndreyErmilov


    Содержание:


    1. Первая часть (типы, пользовательские типы и полиморфизм, перечисления, Option и Result, паттерн-матчинг, трейты и протоколы, обобщенное программирование)
    2. Вторая часть (многопоточность, асинхронность, функциональная парадигма и заключение – «Зачем же питонисту Rust»)

    Если не хочется читать эту статью, то можно посмотреть видео нашего выступления.



    Типы


    Первое различие, с которым сталкиваются разработчики, Rust – язык со статической типизацией.


    Можно по-разному смотреть на динамическую и статическую типизацию, но, на наш взгляд, основное отличие демонстрирует изображение ниже:



    В случае с Python множество ошибок типизации мы видим уже на проде – в интерфейсе Sentry. В Rust такие ошибки отлавливаются еще на этапе сборки, и это просходит, как правило, локально или в CI.


    Учитывая, что ошибки, связанные с несоответствием типов в наших приложениях составляют подавляющее большинство, статическая типизация Rust выглядит как достаточно весомый плюс. Можно было бы тут и остановиться, но многие, думаю, слышали, что в последнее время в Python активно развивается опциональная статическая типизация. Почему бы не попробовать проверить такие проблемы еще до их попадания в прод?


    Тут на сцену выходит mypy как самое зрелое решение в этой области. Сам создатель языка Python активно принимает участие в разработке mypy. И это замечательный инструмент, позволяющий проанализировать код и найти те самые проблемы с типизацией. Давайте рассмотрим его детально.


    Начнем с крайне простого примера:


    from typing import List
    
    def last(items: List[int]) -> int:
        return items.pop()

    Этот код делает тривиальную штуку – забирает крайний правый элемент из списка и передает его в качестве возвращаемого значения функции.


    С точки зрения mypy и нотации типов, этот код является вполне корректным:


    ➜ mypy --strict types-01.py
    Success: no issues found in 1 source file

    А теперь давайте рассмотрим аналогичный код в Rust:


    fn last(mut items: Vec<i32>) -> i32 {
        items.pop()
    }

    И посмотрим, к чему приведет попытка его скомпилировать:


    ➜  types git:(master) ✗ cargo run
    error[E0308]: mismatched types
      |
    1 | fn last(mut items: Vec<i32>) -> i32 { 
      |     items.pop()
      |     ^^^^^^^^^^^ expected `i32`, 
      |                 found enum `std::option::Option`
      |
      = note: expected type `i32`
                 found enum `std::option::Option<i32>`

    Ошибка компиляции явно говорит о том, что метод .pop() в каких-то случаях может вернуть None. И действительно, если мы в качестве аргумента передадим пустой вектор, так и произойдет.


    Но почему mypy не предупредил нас о потенциальной ошибке? Дело в том, что в Python при пустом списке произойдёт Exception, который никак не учитывается и не отражается в нотации типов. Это кажется достаточно большой проблемой, которая не позволяет использовать возможности статической типизации в полной мере. В целом существование исключений и их игнорирование в системе нотации типов перекладывает ответственность за корректность кода на разработчика.


    Отлично, давайте перепишем Python-код по аналогии с Rust, не вызывая исключения:


    from typing import List, Optional
    
    def last(array: List[int]) -> Optional[int]:
        if len(array) == 0:
            return None
        return array.pop()

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


    Да, безусловно, есть попытки осуществить это. Хороший пример – библиотека returns.
    В целом она выглядит как хорошая попытка реализовать использующийся в Rust подход путем отказа от вызовов исключений. Это, в свою очередь, позволяет более безопасно с точки зрения типов описывать какую-то изолированную или бизнес-логику, что само по себе уже является огромным плюсом.


    Пользовательские типы и полиморфизм


    Типы являются не только способом избежать ошибок, но и удобными строительными блоками, которые помогают писать красивый и понятный код. Давайте посмотрим, как это работает в Rust.


    Рассмотрим задачу. У нас есть разные сущности – расстояние, которое измеряется в километрах и метрах, и время, которое измеряется в часах и секундах. Мы хотим уметь получать скорость. Опишем структуры:


    /// Distance, km
    struct Kilometer(f64);
    
    /// Distance, m
    struct Meter(f64);
    
    /// Time, h
    struct Hour(f64);
    
    /// Time, s
    struct Second(f64);
    
    /// Speed, km/h
    struct KmPerHour(f64);
    
    /// Speed, km/s
    struct KmPerSecond(f64);
    
    /// Speed, m/h
    struct MeterPerHour(f64);
    
    /// Speed, m/s
    struct MeterPerSecond(f64);

    Реализуем операцию деления для километров и метров и в каждом случае будем получать свой тип:


    /// Speed, km/h
    impl Div<Hour> for Kilometer {
        type Output = KmPerHour;
        fn div(self, rhs: Hour) -> Self::Output {
            KmPerHour(self.0 / rhs.0)
        }
    }
    /// Speed, km/s
    impl Div<Second> for Kilometer {
        type Output = KmPerSecond;
        fn div(self, rhs: Second) -> Self::Output {
            KmPerSecond(self.0 / rhs.0)
        }
    }
    /// Speed, m/h
    impl Div<Hour> for Meter {
        type Output = MeterPerHour;
        fn div(self, rhs: Hour) -> Self::Output {
            MeterPerHour(self.0 / rhs.0)
        }
    }
    /// Speed, m/s
    impl Div<Second> for Meter {
        type Output = MeterPerSecond;
        fn div(self, rhs: Second) -> Self::Output {
            MeterPerSecond(self.0 / rhs.0)
        }
    }

    Проверим, что наш код работает. Rust в зависимости от типов, которые мы делим и на которые мы делим, определит, какого типа будет скорость.


    fn main() {
        let distance = Meter(100.);
        let duration = Second(50.);
        let speed = distance / duration;  // MeterPerSecond
        assert_eq!(speed.0, 2.);
    
        let distance = Kilometer(180.);
        let duration = Hour(3.);
        let speed = distance / duration;  // KmPerHour
        assert_eq!(speed.0, 60.);
    }

    Реализуем тоже самое на Python.


    Опишем структуры:


    @dataclass
    class Hour:
        """Time, h."""
        value: float
    
    @dataclass
    class Second:
        """Second, s."""
        value: float
    
    @dataclass
    class KmPerHour:
        """Speed, km/h."""
        value: float
    
    @dataclass
    class KmPerSecond:
        """Speed, km/s."""
        value: float
    
    @dataclass
    class MeterPerHour:
        """Speed, m/h."""
        value: float
    
    @dataclass
    class MeterPerSecond:
        """Speed, m/s."""
        value: float

    Сделаем реализацию деления только для километров и представим, что сделали так же и для метров. Нам нужно использовать overload, чтобы показать, как в зависимости от типа входного параметра меняется тип результата:


    from typing import overload
    
    @dataclass
    class Kilometer:
        value: float
    
        @overload
        def __truediv__(self, other: Hour) -> KmPerHour: ...
    
        @overload
        def __truediv__(self, other: Second) -> KmPerSecond: ...
    
        def __truediv__(self, 
                        other: Union[Hour, Second]
                       ) -> Union[KmPerHour, KmPerSecond]:
            if isinstance(other, Hour):
                return KmPerHour(self.value / other.value)
            elif isinstance(other, Second):
                return KmPerSecond(self.value / other.value)
    
    ...

    Проверим код, используя mypy:


    ➜  01-types poetry run mypy --strict typing-02-2.py
    Success: no issues found in 1 source file

    А теперь случайно ошибемся в возвращаемом типе: при делении на секунды будем возвращать километры в час:


    if isinstance(other, Hour):
        return KmPerHour(self.value / other.value)
    elif isinstance(other, Second):
        return KmPerHour(self.value / other.value)

    Запустим mypy:


    ➜  01-types poetry run mypy --strict typing-02-2.py
    Success: no issues found in 1 source file

    Mypy не видит в коде с ошибкой никакой проблемы, потому что мы по-прежнему возвращаем одно из корректных значений, описанных в Union[KmPerHour, KmPerSecond].


    Явно укажем, что ожидаем получить при делении на секунды именно км/с, и снова запустим mypy.


    speed: KmPerSecond = Kilometer(1.0) / Second(1.0)
    assert isinstance(speed, KmPerHour)

    ➜  01-types poetry run mypy --strict typing-02-2.py
    Success: no issues found in 1 source file

    Понятно, почему это происходит, но не понятно, как избежать подобных ошибок с mypy.


    Перечисления


    Перечисления существуют во многих языках. Посмотрим, как в Python и Rust происходит работа с ними.


    Создадим перечисление, описывающее возможные состояния пользователя:


    from enum import Enum, auto
    
    class UserStatus(Enum):
        PENDING = auto()
        ACTIVE = auto()
        INACTIVE = auto()
        DELETED = auto()

    Сделаем тоже самое в Rust:


    enum UserStatus {
        Pending,
        Active,
        Inactive,
        Deleted,
    }

    В это простом примере оба варианта выглядят одинаково. Но в Rust мы можем связать статус пользователя с дополнительной информацией.


    enum UserStatus {
        Pending(DateTime<Utc>),
        Active(i32),
        Inactive(i32),
        Deleted,
    }

    В примере для статуса Pending мы храним информацию о том, как долго мы ожидаем подтверждения от пользователя; для активного и неактивного пользователей храним их идентификаторы.


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


    Возможность внутри вариантов перечислений хранить значения сильно влияет на то, как Rust-разработчики пишут код – перечисления являются одним из наиболее часто используемых возвращаемых типов. На их основе возникли типы Option и Result, про которые мы сейчас поговорим.


    Option и Result


    Мы уже встречались с типом Option, когда доставали из вектора с числами крайне правый элемент. Result похож на Option, но может содержать в себе два типа, а не один: успешный результат выполнения операции или ошибку.


    pub enum Option<T> {
        None,
        Some(T),
    }
    
    pub enum Result<T, E> {
        Ok(T),
        Err(E),
    }

    Давайте на примере разберем, как использование Option влияет на корректность работы приложения.


    let mut vec = vec![1, 2, 3];
    
    let last_element = vec.pop();
    assert_eq!(last_element, Some(3));

    Когда мы достали из вектора правый элемент, то получили не число, а значение типа Option, содержащее в варианте Some нужное число. Мы не сможем его сложить с другим числом, т.к. в этом случае мы потерям информацию о возможном варианте None.


    let mut vec = vec![1, 2, 3];
    
    let last_element = vec.pop();
    assert_eq!(last_element, Some(3));
    
    let four = last_element + 1; 
    // Cannot add `std::option::Option<{integer}>` to `{integer}`

    Чтобы использовать полученное из вектора число, мы можем прибегнуть к паттерн-матчингу, который рассмотрим еще ниже. А сейчас проверим, как аналогичный код работает в Python. Мы используем написанную нами функцию last(), чтобы возвращаемый тип был Optional.


    from typing import List, Optional
    
    def last(array: List[int]) -> Optional[int]:
        if len(array) == 0:
            return None
        return array.pop()
    
    numbers = [1, 2, 3]
    last_element = last(numbers)
    four = last_element + 1

    ➜  01-types poetry run mypy --strict typing-04-1.py
    typing-04-1.py:12: error: Unsupported operand types for + ("None" and "int")
    typing-04-1.py:12: note: Left operand is of type "Optional[int]"

    Mypy, как и комплиятор Rust, не позволит нам сложить опциональное значение с числом. Но для этого программисту нужно будет самостоятельно указать, что возвращаемое значение Optional.


    Паттерн-матчинг


    Раз уж мы упомянули pattern-matching, давайте, наконец, раскроем эту концепцию чуть подробнее.


    Для начала рассмотрим следующий Python-код:


    class UserStatus(Enum):
        PENDING = auto() 
        ACTIVE = auto()
        INACTIVE = auto()
        DELETED = auto()
    
    def serialize(user_status: UserStatus) -> str:
        if user_status == UserStatus.PENDING:
            return 'Pending'
        elif user_status == UserStatus.ACTIVE:
            return 'Active'
        elif user_status == UserStatus.INACTIVE:
            return 'Inactive'
        elif user_status == UserStatus.DELETED:
            return 'Deleted'

    Все, что этот код делает, – преобразует элементы перечисления UserStatus в строковое представление. Выглядит это достаточно просто.


    А теперь рассмотрим аналогичный вариант на Rust:


    enum UserStatus {
        Pending,
        Active,
        Inactive,
        Deleted,
    }
    
    fn serialize(user_status: UserStatus) -> &'static str {
        match user_status {
            UserStatus::Pending => "Pending",
            UserStatus::Active => "Active",
            UserStatus::Inactive => "Inactive",
            UserStatus::Deleted => "Deleted",
        }
    }

    Разница в том, что в случае, когда разработчик по какой-то причине (например, если добавляется новый статус пользователя при рефакторинге) не опишет один из исходных вариантов перечисления в функции serialize, Rust ему об этом скажет:


    fn serialize(user_status: UserStatus) -> &'static str {
        match user_status {
            UserStatus::Pending => "Pending",
            UserStatus::Active => "Active",
        }
    }
    
    // Error: non-exhaustive patterns: `Inactive` and `Deleted` not covered

    Это и есть одно из отличительных свойств pattern-matching в Rust. При его использовании в коде компилятор заставляет рассмотреть все варианты.
    И возвращаясь к функции last, которую мы приводили в начале: при обработке Option, являющегося результатом вызова функции, компилятор не даст забыть обработать ситуацию, при которой результатом выполнения станет None.

    Соответственно, аналогичное правило касается и типа Result:


    let number = "5";
    let parsed: Result<i32, ParseIntError> = number.parse();
    
    let message = match parsed {
        Ok(value) => format!("Number parsed successfully: {}", value),
        Err(error) => format!("Can't parse a number. Error: {}", error),
    };
    assert_eq!(message, "Number parsed successfully: 5");

    В случае если нам нужно определить некоторое дефолтное поведение, Rust предоставляет следующую конструкцию:


    fn fibonacci(n: u32) -> u32 {
        match n {
            0 => 1,
            1 => 1,
            _ => fibonacci(n - 1) + fibonacci(n - 2),
        }
    }

    В этом примере мы видим, что описаны только два конкретных значения, а для всех остальных рекурсивно вызывается функция fibbonacci.


    Трейты и протоколы


    В этом разделе мы сравним возможности недавно появившихся в Python протоколов и трейтов Rust. Возможно, не все используют протоколы, и чтобы сравнение было полезным, сделаем краткий обзор основных идей протоколов.


    Представим, что нам нужно написать функцию-валидатор, которая принимает список экземпляров класса Image и возвращает список из булевых значений. True будет обозначать, что изображение валидное и весит не больше, чем MAX_SIZE, False – невалидное. Напишем код:


    from typing import List
    
    MAX_SIZE = 512_000
    
    class Image:
        def __init__(self, image: bytes) -> None:
            self.image = image
    
    def validate(images: List[Image]) -> List[bool]:
        return [len(image) <= MAX_SIZE for image in images]

    Если мы запустим mypy, то увидим следующую ошибку:


    ➜  01-types poetry run mypy --strict p-01-2.py
    p-01-2.py:8: error: Argument 1 to "len" has incompatible type "Image"; expected "Sized"
    Found 1 error in 1 file (checked 1 source file)

    Mypy сообщает, что ожидается класс типа Sized, а мы вместо этого передали Image. Из документации становится понятно: все, что реализует магический метод __len__, является Sized.



    В Python мы давно привыкли к утиной типизации, и требование реализовать метод __len__ кажется вполне понятным. Сделаем это.


    from typing import List
    
    MAX_SIZE = 512_000
    
    class Image:
        def __init__(self, image: bytes) -> None:
            self.image = image
    
        def __len__(self) -> int:
            return len(self.image)
    
    def validate(images: List[Image]) -> List[bool]:
        return [len(image) <= MAX_SIZE for image in images]

    После добавления __len__ mypy определит код как корректный.


    Итого – Sized это и есть протокол, а про наш класс Image можно сказать, что он реализует протокол Sized.


    Но давайте рассмотрим тему протоколов немного подробнее и усложним задачу – будем валидировать различные документы по их статусу – были ли они проверены и можно ли их публиковать. Функция validate будет возвращать только те документы, которые прошли проверку.


    from abc import abstractmethod
    from typing import List, Protocol
    
    class SupportsReview(Protocol):
        @abstractmethod
        def approved(self) -> bool: ...
    
    class Article(SupportsReview):
        def approved(self) -> bool:
            return True
    
    class PhotoGallery(SupportsReview):
        def approved(self) -> bool:
            return True
    
    class Test(SupportsReview):
        def approved(self) -> bool:
            return True
    
    def validate(documents: List[SupportsReview]) -> List[SupportsReview]:
        return [
            document for document in documents
            if document.approved()
        ]
    
    documents = [Article(), PhotoGallery(), Test()]
    approved_documents = validate(documents)
    assert len(approved_documents) == 3

    В этом коде мы описываем протокол SupportsReview, и валидатор работает со всеми классами, реализующими этот протокол. Если бы один из классов не поддерживал SupportsReview, то mypy сообщил бы, что в documents у нас есть значение неподходящего типа.


    Сравнивая протоколы в Python с трейтами в Rust, мы увидим, что они очень похожи. Давайте напишем тоже самое на Rust.


    Начнем с создания трейта Review:


    trait Review {
        fn approved(&self) -> bool;
    }

    Создадим структуры и реализуем для них трейт Review:


    struct Article;
    
    impl Review for Article {
        fn approved(&self) -> bool {
            true
        }
    }
    
    struct PhotoGallery;
    
    impl Review for PhotoGallery {
        fn approved(&self) -> bool {
            true
        }
    }
    
    struct Test;
    
    impl Review for Test {
        fn approved(&self) -> bool {
            true
        }
    }

    Опишем функцию validate и запустим код:


    fn validate(documents: Vec<Box<dyn Review>>) -> Vec<Box<dyn Review>> {
        documents
            .into_iter()
            .filter(|document| document.approved())
            .collect::<Vec<_>>()
    }
    
    fn main() {
        let documents: Vec<Box<dyn Review>> = vec![
            Box::new(Article),
            Box::new(PhotoGallery),
            Box::new(Test),
        ];
        let approved = validate(documents);
        assert_eq!(approved.len(), 3);
    }

    Код на Rust выглядит менее понятно, чем код на Python за счет появления типов Box и описания поддержки трейта Review, как dyn Review. Это важный момент – за все приходится платить, и это плата за статическую типизацию.


    Обобщенное программирование


    Мы обсудили протоколы и выяснили, что с их помощью мы можем накладывать ограничения на типы, с которым работаем. Но что делать, если нам нужно описать для типа более одного ограничения и указать, что при этом везде должен быть один и тот же тип? На помощь нам приходят дженерики. Рассмотрим, как строится работа с ними в Python и сравним с Rust.


    Реализуем узел бинарного дерева поиска:


    from typing import Generic, TypeVar, Optional
    
    T = TypeVar('T')
    
    class Node(Generic[T]):
        def __init__(self, value: T,
                     left: Optional['Node'[T]] = None,
                     right: Optional['Node'[T]] = None,
                     ) -> None:
            self.value = value
            self.left = left
            self.right = right
    
    if __name__ == '__main__':
        root = Node(2)
        root.left = Node(1)
        root.right = Node(3)

    Мы описали обобщенный тип T, который может храниться внутри узла. Запустим mypy и убедимся, что все корректно описано.


    ➜  01-types poetry run mypy --strict generics-01-1.py
    Success: no issues found in 1 source file

    Ошибемся в одном значении внутри узла и посмотрим, как mypy отловит эту ошибку:


    root = Node(2)
    root.left = Node(1)
    root.right = Node('Hello!')  # Тут ошибка

    При создании корня дерева mypy определил тип T как int и не должен позволить нам создать другой узел с типом str.


    generics-01-1.py:18: error: Argument 1 to "Node" has incompatible type "str"; expected "int"
    Found 1 error in 1 file (checked 1 source file)

    Mypy верно поймал ошибку.


    Но достаточно ли нам для описания узла дерева текущего определения? На данный момент мы наложили только одно ограничение – все типы внутри дерева должны быть одинаковыми. Но чтобы реализовать бинарное дерево поиска, необходимо уметь сравнивать значения внутри. Например, сейчас мы можем в узлы положить None и при этом код будет определяться как корректный.


    Давайте наложим на тип T дополнительное ограничение – T должен реализовывать протокол сравнения. Поищем протокол Comparable.



    К сожалению, разговоры про этот протокол шли еще в 2015 году, но он так и не появился. Реализуем его самостоятельно:


    C = TypeVar('C')
    
    class Comparable(Protocol):
        def __lt__(self: C, other: C) -> bool: ...
    
        def __gt__(self: C, other: C) -> bool: ...
    
        def __le__(self: C, other: C) -> bool: ...
    
        def __ge__(self: C, other: C) -> bool: ...

    И добавим в бинарное дерево поиска:


    ...
    T = TypeVar('T', bound=Comparable)
    
    class Node(Generic[T]):
        def __init__(self, value: T,
                     left: Optional['Node'[T]] = None,
                     right: Optional['Node'[T]] = None,
                     ) -> None:
            self.value = value
            self.left = left
            self.right = right
    
        def add(self, node: 'Node'[T]) -> None:
            if node.value <= self.value:
                self.left = node
            else:
                self.right = node
    
    if __name__ == '__main__':
        root = Node(2)
        root.add(Node(1))
        root.add(Node(3))

    Mypy проверяет код и подтверждает, что все корретно. Попробуем ошибиться и проверим, как mypy отловит ошибку:


    root = Node(None)
    root.add(Node(None))
    root.add(Node(None))

    ➜  01-types poetry run mypy --strict generics-01-4.py
    generics-01-4.py:35: error: Value of type variable "T" of "Node" cannot be "None"
    generics-01-4.py:36: error: Value of type variable "T" of "Node" cannot be "None"
    generics-01-4.py:37: error: Value of type variable "T" of "Node" cannot be "None"
    Found 3 errors in 1 file (checked 1 source file)

    Ошибка поймана, все работает.


    Теперь реализуем тоже самое на Rust.


    struct Node<T> 
        where T: Ord
    {
        pub value: T,
        pub left: Option<Box<Node<T>>>,
        pub right: Option<Box<Node<T>>>,
    }
    
    impl<T> Node<T> 
        where T: Ord 
    {
        pub fn add(&mut self, node: Node<T>) {
            if node.value <= self.value {
                self.left = Some(Box::new(node))
            } else {
                self.right = Some(Box::new(node))
            }
        }
    }
    
    fn main() {
        let mut root = Node { value: 2, left: None, right: None };
        let node_1 = Node { value: 1, left: None, right: None };
        let node_3 = Node { value: 3, left: None, right: None };
        root.add(node_1);
        root.add(node_3);
    }

    Код похож на тот, который мы делали в Python, но трейт сравнения нам не нужно писать самостоятельно. Он уже есть, и мы просто описываем его where T: Ord.


    Это отличие не кажется принципиальным, и можно сделать вывод, что протоколы и дженерики в Python не уступают Rust.


    К сожалению, это не так.


    from typing import TypeVar, Generic, Sized, Hashable
    
    T = TypeVar('T', Hashable, Sized)
    
    class Base(Generic[T]):
        def __init__(self, bar: T):
            self.bar: T = bar
    
    class Child(Base[T]):
        def __init__(self, bar: T):
            super().__init__(bar)

    На этот код mypy выведет:


    ➜  01-types poetry run mypy --strict generics-01-6.py
    generics-01-6.py:13: error: Argument 1 to "__init__" 
        of "Base" has incompatible type "Hashable"; expected "T"
    generics-01-6.py:13: error: Argument 1 to "__init__" 
        of "Base" has incompatible type "Sized"; expected "T"
    Found 2 errors in 1 file (checked 1 source file)

    Этот пример скопирован из issue mypy на гитхабе и висит там уже достаточно давно.


    Mypy – прекрасный проект, и работа, которая ведется над ним, достойна уважения и восхищения. Но пока опциональная статическая типизация в Python выглядит недостаточно мощным инструментом, позволяющим избавиться от всех ошибок, связанных с несоответствием типов. Rust же позволяет сделать это.


    Продолжение следует

    Comments 167

      +2
      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 все же более гибкие чем показано в статье
        +1

        Разве это не присвоит просто каждому члену перечисления по строке?


        В Rust подразумевается, что именно к каждому члену можно приделать по пачке полей. Т.е. превратить каждого члена в самостоятельную структуру, являющуюся при этом частью конечного перечисления.

          +1
            0

            Да, вот этот пример, вроде бы, то что надо

              +1

              В вариантах перечисления может быть разный набор полей? В Rust можно создать такое перечисление:


              enum Message {
                  Quit,
                  Move { x: i32, y: i32 },
                  Write(String),
                  ChangeColor(u8, u8, u8),
              }
                –2

                Да, может


                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")
                  +1

                  Будет ли в такой схеме Pending("Foo") инстансом UserStatus?

                    0

                    А почему он должен им быть? Вопрос был в том, можно ли в варианты перечисления засунуть разные структуры (наборы полей).

                      0

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

                        –2

                        А почему вы решили что связывание в данном контексте обязательно наследование, а не, например, композиция?

                          +3

                          Можете вместо наследования использовать что угодно, лишь бы Pending("Foo") имело хоть какое-нибудь отношение к UserStatus.


                          Связывание в данном контексте означает возможность связать значение перечисления с произвольном значением, а не с константой. То есть Pending("Pending"), Pending("Foo") и Pending("Bar") должны быть полностью равноправны как значения перечисления, чего в коде выше не наблюдается.

                  0

                  Комбинируя увиденное в других комментариях, могу предположить как это должно выглядеть на Питоне на самом деле:


                  @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...

                    +1

                    Не то чтоб ошибки, но последняя строка скорее бессмысленная, потому что пользоваться ею неудобно будет.
                    Вот так-то так она более применима


                    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)

                    А полного прямого аналога предложенного исходно перечисления я в Питоне вот так и не назову. Всё-таки "подсказка по типам" Питона принципиально отличается от жёсткого задания типов Раста, у них даже задачи разные.

                      0

                      Всё так, Union не делает проверок в runtime.
                      Если совсем придираться, на мой взгляд, должно быть как-то так:


                      from typing import Optional, Union
                      class SomeClass:
                          message: Optional[Union[Quit, Move, Write, ChangeColor]] = None
                        0

                        Да, как вариант.

                        +1
                        Не то чтоб ошибки, но последняя строка скорее бессмысленная, потому что пользоваться ею неудобно будет.

                        А что именно там неудобно будет?


                        message: Union[Quit, Move, Write, ChangeColor] = None

                        Э-э-э, а None-то тут зачем и откуда?

                          0

                          Вот для вас это «Э-э-э», а например для разработчиков Pydantic всё нормально)

                            0
                            Э-э-э, а None-то тут зачем и откуда?

                            Да просто чтобы выставить значение-по-умолчанию "значение не выставлено". Считать ли это антипаттёрном — вопрос сильно дискуссионный, но именно в таком значение None часто используется.

                    +5
                    В python я могу перечислению присвоить не только строку, но и dataclass с полями. Таким образом каждый член перечисления становится структурой с полям.
                    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.
                      0

                      Могут ли варианты перечисления иметь данные разных типов?

                        +1

                        Будет ли в такой схеме Status("Foo") инстансом UserStatus?

                          0

                          Простите, а зачем? То есть "для какой цели/задачи"
                          Потому что я не уверен, что понимаю суть Вашего вопроса. По структуре классов Status является базовым классом для UserStatus и не может быть инстансом унаследованного класса, тем более через множественное наследование.
                          И мне не совсем понятно зачем там вообще использовано то множественное наследование, enum.Enum этого не требует. С тем же успехом работать будет и "class UserStatus(Enum):"

                            +1

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

                              0

                              Хмм.
                              В этом случае сама идея "сделать из питона раст" видится мне наибольшая ошибка — ибо конечно же результат не будет столько же удобен/гибок/удобочитаем, как исходный Rust! Из раста если питон делать тоже будет непойми что.

                                –2

                                Никто аналоги тут не строит, вся эта ветка обсуждения началась с того что мы в статье действительно показали python enum немного однобоко (не такими гибкими).
                                А к вопросу о том как работают перечисления в расте, вот вам небольшой контрпример:


                                struct Move(i32, i32);
                                
                                enum Message {
                                    Quit,
                                    Move(Move)
                                }

                                Какое отношение имеет структура Move к варианту Message::Move?

                                  +3

                                  Она является типом первого и единственного поля Message::Move, а что?

                      • UFO just landed and posted this here
                          +5

                          А ещё не забывайте, что вы сидите на ресурсе, который через пару-тройку месяцев после начала этой истории в самый её разгар сам активно сотрудничал с рамблер групп для "марафона удалёнки".

                            +3

                            Конечно, Рамблер делала ужасные вещи, но разве мы должны ставить минусы под постами, которые вообще никакого отношения к тому инциденту? Ведь бизнес хотели отжать одни люди, а писали статью другие. Так значит мы должны принижать труд вторых?

                            • UFO just landed and posted this here
                                +1
                                А еще хабр корпоративный блог не закрыл. Границы ответсвенности то должны быть.
                                  0

                                  Какое дело все эти рассуждения имеют к содержанию и качеству статьи, за которые собственно плюсы и получаются?

                                    0
                                    Гитлер_хороший_художник?
                                      +1
                                      Ну тащемта да.
                                      Возможно, музыкант был грубияном, наркоманом и мерзким чуваком, но если музыка у него хорошая — разве нельзя признать, что она хорошая?
                                      Рамблер большой, он делал плохие вещи, делал хорошие вещи, в нем работает куча людей.
                                      Какой уровень взаимодействия с Рабмлером делает тебя соучастником наезда на автора NGinx?
                                      • UFO just landed and posted this here
                                          +2
                                          Тогда вам стоит пройти по всем площадкам, где публикуются работы Google, Microsoft и прочих гигантов тоже с подобными заявлениями — и не забудьте заминусить ВСЕ их проекты и статьи, а то воротят бог весть что. Минусы под статьи ставят к статье, а не к людям или компаниям — иначе нарушается объективное восприятие информации, которая была в статье подана.
                                          Только наивный будет считать, что бизнес будет всегда чистеньким, особенно больших размеров — везде есть люди со своими интересами, которые не всегда благие для остальных. Но судить по паре десятков людей, решивших попиариться или что-то хуже сделать о тысячах людей, которые любят свою работу и/или просто пишут тут статьи — как воспринимать мир черно-белым. Принцип коллективной отвественности не стоит тут навязывать, да и никто не предложит ВСЕМ этим тысячам сотрудников достойное место чисто из солидарности, а у кого-то ипотека, дети…
                                          Удобно быть моралистом, когда лично тебя это никак не касается — можно встать в атакующую позу и не считаться с людьми, потому что вы правы в своем праведном гневе) И далеко не все сотрудники Рамблера были в восторге — поищите публичные письма их сотрудников, вроде были как раз тут, на хабре.
                                            0
                                            Так никто не говорит людям «уходите из Рамблера» или «не печатайте статьи на Хабре»! Достаточно просто не печатать в их корпоративном блоге. Или за это там тоже наказывают? ))
                                              +1
                                              Ну так давайте не будем смотреть конференции и прочие мероприятия Google?) Они ведь пиарятся, хотя давно убрали «Don't be evil» из своего устава, да и сколько было скандалов (что неизбежно при увеличении размеров фирмы).
                                              Все равно мы будем пользоваться Android, писать на Go и прочее-прочее).
                                              Имидж компании состоит не только из негативных поступков, но и из положительных. Лично я не одобряю заведение уголовного дела, но и продолжать бессмысленное макание людей (которых причисляют косвенно к виновникам) в грязь не вижу смысла. Имиджевые потери для Рамблера и так были достаточно высокие — пусть отрабатывают, как могут. Даже если это простые статьи тут, которые кому-то пригодятся.
                                              И если капнуть чуть глубже в те события: подавляющее в тот момент было именно пожелание «уходите из Рамблера».
                                              И давайте размышлять абстрактно — кто-то обрушил своим недальновидным поведением имидж фирмы, причем осудил другого человека. Что будет дальше? Фирма должна развалиться из-за того, что теперь ее имидж плохой? А чем это отличается от «уходите из Рамблера» по причине того, что он перестанет существовать?)) Вариант «не чинить репутацию» равен «уйти из Рамблера» на длинной дистанции, просто не по своему желанию)
                                                0
                                                Да, ради этих слов стоило зарегиться!
                                                Вариант «не чинить репутацию» равен «уйти из Рамблера» на длинной дистанции
                                                Разработчику важно работать на свою репутацию, тогда ничего не страшно. К тому же на длинной дистанции все уходят.
                                                  0
                                                  Давно сидел тут без учетки, но после N-ого раза на тему злых корпораций прорвало)
                                                  Согласен про работу на свою репутацию. Про уход — все хотят уходить из компаний с нормальной репутацией, а не оставаться мечеными. Пускай исправляются полезными для общества делами.
                                                  Осуждать программиста за его статью по вине какого-то менеджера (которого он и никогда не видел/слышал скорее всего в наших реалиях) есть дело не самое адекватное. Какой срок давности у данного преступления? Или Рамблер уже никогда не отмоется?))
                                                    0
                                                    Всегда интересно, как это на хабре (все же адекватные, уважают чужое мнение) ставят минус в карму без явной аргументации в комментариях — узнал)
                                                      –1

                                                      Минус и за просто вопросы или цитирование исторических фактов могут поставить. Молча ибо "сам догадайся чем ты мимопроходившему сообществу не мил". Особенно когда кроме минуса ответить то и нечего ибо документальный факт, а чью-то мозоль отдавил или картину мира пошатнул.
                                                      Так что — остаётся относиться к хабру как к просто месту общения просто случайных людей. Ничем, по факту, айтишники не отличаются от кого угодно стереотипно другого. И хабр тут не исключение.

                                            • UFO just landed and posted this here
                                                0
                                                Тут запрет на личное мнение по данной ситуации?)
                                                Попытка дискредитации через сопричастность к фирме (а не к решению по данной проблеме) — это «сильный» аргумент. Напишешь хоть слово — сразу враг?) С подобной нетерпимостью даже диалог не выстроишь.
                                                Я не просил поддерживать Рамблер. Не писал о том, что невиновата организация — только люди, которые управляют (которые уже почти все свалили или будут заменены). Взваливать ответственность на сотрудников — как вспоминать немцев из ВОВ и вешать их вину на современников; как запрещать олимпийцам выступать под флагом страны, в которой они родились и любят (страну, а не государство). У всех должен быть шанс исправиться, да и не отвечают сыны за отцов.
                                                И если мы перешли на личности — у вас у самого карма далеко внизу, что намекает на не самый адекватный способ ведения диалога самим обществом. Да и классификация про клоунов заочно в профиле — это вряд ли нормально. С чужим мнением принято считаться, даже если оно вас не устраивает — априори. Вы сами об этом просите в профиле) Более того — вы пытаетесь задавить не конкретные действия, а чувства других людей: кому было все равно, тот уже ушел на волне «как ты можешь там работать».
                                                • UFO just landed and posted this here
                                                    0
                                                    Это лишь обозначение вашей ангажированности.

                                                    Я не стал скрывать свою личность, хотя и мог.
                                                    Ваш пример изначально некорректный.

                                                    Потому что вы не уловили суть примера — сейчас никто из рядовых сотрудников не совершает преступление. Да и не принимали рядовые сотрудники решение (а уж тем более какое-либо действие) в отношении данного дела, в отличие от тех же рядовых нацистов.
                                                    Моя карма свидетельствует лишь о том, что я не стесняюсь в выражениях.

                                                    И зря — слово режет сильнее ножа. Для себя вы просите корректности в выборе слов.
                                                    Не у всех, а только у тех кто признал ошибку.

                                                    Где вы увидели НЕ признание ошибки?) Ошибка есть — это факт, о чем мы уже писали ранее.
                                                    Вы читали чем кончилось дело? Дело передали в компанию, которая продолжила судебное преследование уже в международном формате. Заслуживает ли права на исправление тот, кто только делает вид что исправился?

                                                    Если вы настолько в курсе, то стоит углубиться еще больше в последние новости — Мамут больше не у руля, виновник самоудалился (при этом попортив кровь Сберу, который ни сном-ни духом об этом был). Да, разбирательство продолжается, и всем очевидно, что оно закончится в пользу автора — проще в суде заткнуть одну российскую компанию по неправомерной претензии, чем заставить весь мир платить за софт с оглядкой на 15 лет использования.
                                                      0
                                                      проще в суде заткнуть одну российскую компанию по неправомерной претензии, чем заставить весь мир платить за софт с оглядкой на 15 лет использования
                                                      А-а… так вы считаете, что весь мир должен платить Рамблеру???
                                                        –1
                                                        Ни в коем случае. Вы ведь сами отвечаете на свой вопрос цитированием меня.
                                                        неправомерной претензии

                                                        В итоге недальновидной и жадной (и несколько жалкой на фоне сделки F5 Networks по Nginx) попытки нагреться высшего руководства перед уходом сильно пострадал имидж фирмы — а также был причинен ущерб автору. О какой выплате по авторским правам может идти речь, если все понимают, что доказательств нет?
                                +5
                                По поводу части с pattern-matching:
                                Так как указано в статье в 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 — будет исключение.
                                  –1
                                  И создал бы дополнительный словарь в памяти, который находится в куче, а не в стеке и обращение к нему будет дольше, не говоря уже про вычисление хэш-функции на ровном месте. А главное непонятно зачем, все инструменты для получения строки из enum есть в стандартной библиотеке.
                                  Сейчас в python 3.10 pattern matching подвезут — станет полегче.
                                    +5
                                    На питоне так и должно быть.
                                      +4
                                      Как должно быть? должна быть написана лишняя функция дублирующая функционал из стандартной библиотеки? или нужно объявить недостатки языка его достоинствами?
                                        0
                                        По моему, по питоновски в таких случаях нужно просто использовать словарь. Без классов и функций.
                                          +2
                                          Словарь это не единственная структура данных. Каждая структура данных для своей задачи. И все многообразие структур данных даже в python весьма полезно, нужно только понимать сильные и слабые стороны. Или вы всерьёз думаете что в других языках нет хэш таблиц и это разработчики на python такие умные?
                                          Чем перечисление лучше словаря?
                                          Экземпляр перечисления является самим перечислением. В результате чего нормально начинает работать типизация и исчезают ошибки с опечатками в ключах словаря, просто потому что IDE наконец понимает что там должно быть, а чего нет. Плюс перечисление невозможно случайно изменить в процессе работы программы и не нужно инициализировать.
                                            0
                                            Я не против перечислений. Я против того, чтобы из динамического языка делать статический. Ну и против того, чтобы на динамическом языке писать так же как и на статическом.
                                            Нужно ясно понимать области применения и преимущества каждого типа языков и:
                                            — Выбирать язык сознательно, ориентируясь на его плюсы.
                                            — Стиль программирования должен быть тоже разный.
                                            А то я насмотрелся на то, как люди приходят в питон из джавы и начинают лепить скажем геттеры и сеттеры, ну или посмотрят на модные веяния и давай тащить в питон статическую типизацию.
                                              +3
                                              Так потому она и опциональная, что язык динамический. Хотите — тащите, не хотите — не тащите. Мне вот удобно, что IDE может правильно подсказки выдавать, удобно, что видно, что из себя представляют аргументы функций.
                                                –2
                                                Это добавляет много ненужного шума в текст. А тексты приходится читать.
                                                  +1
                                                  Не знаю, что именно вы называете шумом. Но знать, какой тип у аргумента — это очень даже удобно.

                                                  def get_user_by_uuid(user_uuid):
                                                      pass
                                                  

                                                  def get_user_by_uuid(user_uuid: UUID):
                                                      pass
                                                  

                                                  UUID здесь спокойно мог бы быть и обычной строкой.
                                                    –5
                                                    Здесь фактически комментарий вынесен в синтаксис, что ИМХО есть не очень хорошо.
                                                    Когда мне кажется это полезным и нужным, я указываю тип аргумента в комментариях. Но в большинстве случаев это вообще не нужно. Тем более, что не обязательно всегда передавать один и тот же тип аргумента в функцию.
                                                      +4
                                                      Автоматическая генерация документации будет доступна только при очень определенном типе форматирования комментария. Кроме того, комментарий будет дублировать саму переменную.
                                                      Пример простой, а на деле часто бывают более интересные варианты, где объект будет dataclass'ом, где один из аттрибутов будет enum'ом и так далее. IDE на каждый уровень будет показывать подсказки и типы. Я понимаю, если у вас какой-то pet-проект, где кроме вас никого, но если это библиотека и у нее есть публичный api, то почему бы не облегчить жизнь тем, кто будет ею пользоваться?
                                                    +3
                                                    Это всё достаточно субъективно.
                                                    Мне, например, очень не нравится кадый раз обращаться к документации (при условии что она есть) чтобы понять какие типы данных я могу передать в функцию. Это скорее дело привычки и определяется личными приоритетами разработчика.
                                                      +2

                                                      Так то и вынесение куска метода в другой метод можно обозвать как "ненужный шум в тексте".

                                                        –2
                                                        Иногда не только можно, но и нужно! :)
                                                        +5
                                                        Ты просто не пробовал новые языки со статической типизацией. Алгоритм Хиндли — Милнера позволяет тебе не указывать тип на каждый чих. Например
                                                        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");
                                                        }
                                                        

                                                          –2
                                                          Но, справедливости ради, rust все равно когнитивно сложнее.
                                                          Тогда лучше уж C# рекомендуйте
                                                          Zero cost abstractions — это про стоимость для производительности, а не для мозга.
                                                          4 типа указателей, 2 строки (Я про срез), на первых порах — войны с borrow checker-ом, операции над срезами
                                                            +2
                                                            Впрочем rust все равно няшка, что тут говорить
                                                        +2
                                                        Нужно ясно понимать области применения и преимущества каждого типа языков и:
                                                        — Выбирать язык сознательно, ориентируясь на его плюсы.

                                                        А какие, по вашему, плюсы у динамической типизации?

                                                          0

                                                          Скорость написания скрипта, например. Если объект поддерживает некое производимое над ним действие — скрипт просто работает; если нет — выбрасывается исключение.
                                                          И если нужно — то в Питоне объекту возможность нужное действие производить можно и на лету добавить.


                                                          Или переиспользовать одно имя переменной под разные типы объектов в рамках одного метода.
                                                          dict((v: k) for k, v in some_entities.items()) без разницы что за объекты в k и v до тех пор, пока у каждого из попавших в цикле итерирования объектов можно получить хэш. И вот уже словарь перевёрнут, значения стали ключами.

                                                            0
                                                            Или переиспользовать одно имя переменной под разные типы объектов в рамках одного метода.

                                                            Это не только с динамической типизацией возможно:


                                                            let a = "some string";
                                                            let a = 10;
                                                            let a = Some(2.0);

                                                            С остальным спорить не буду, хотя я предпочту получать ошибку компиляции, а не исключение в рантайме.

                                                              +2
                                                              Скорость написания скрипта, например

                                                              Возможно, я что-то делаю не так, но на статически типизированных языках я пишу как-то быстрее.


                                                              Если объект поддерживает некое производимое над ним действие — скрипт просто работает; если нет и управление заходит в ту ветку кода, в которой пытаются вызвать неподерживаемое действие — выбрасывается исключение.

                                                              Поправил.


                                                              Или переиспользовать одно имя переменной под разные типы объектов в рамках одного метода.

                                                              А это вообще ортогонально типизации.


                                                              dict((v: k) for k, v in some_entities.items()) без разницы что за объекты в k и v до тех пор, пока у каждого из попавших в цикле итерирования объектов можно получить хэш.

                                                              let flipped: HashMap<_, _> = dict.into_iter().map(|(k, v)| (v, k)).collect();


                                                              Причём в этом коде я узнаю о том, что значений не поддерживают хэширование, до запуска, а не после.

                                                                +1
                                                                Скорость написания скрипта, например.
                                                                Вывод типов в современных языках довольно развит. Посмотрите на тот же Crystal, там почти не требуется указывать тип.
                                                                Или переиспользовать одно имя переменной под разные типы объектов в рамках одного метода.
                                                                Это уже реализовано как в Rust, так и в Crystal
                                                                0
                                                                Ну, скажем REPL, манки патчинг, duck typing.
                                                                  0

                                                                  А разве репл только для динамических языков есть?

                                                                  +1
                                                                  REPL

                                                                  Есть и для статически типизированных ЯП.


                                                                  манки патчинг

                                                                  Не вижу в этом ничего хорошего.


                                                                  duck typing

                                                                  А, чудесная возможность принять жёлтый снег за лимонное мороженое.

                                                                    –1
                                                                    Не вижу в этом ничего хорошего.

                                                                    А, чудесная возможность принять жёлтый снег за лимонное мороженое.

                                                                    Ну так бы и сказали сразу что "хорошего(по версии AnthonyMikh)".
                                                                    То, что лично Вы и ещё кто-то не хочет считать плюс плюсом — он не становится минусом. Он просто не нравится лично Вам и/или не подходит Вашим задачам/требованиям/руководству/феншую. Не более того.

                                                                      +1

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

                                                                        0

                                                                        Не могу, как не могу сказать и обратного. Недостаточно знаком со всеми "многими популярными динамическими языками" для такого вывода.

                                                                          0
                                                                          javascript -> typescript
                                                                          php8
                                                                          python
                                                                          ruby -> sorbet
                                                                            0

                                                                            Не совсем понял, конкретно где в питоне "разворот в сторону статической типизации" происходит. В Питоне есть "аннотация типов"(то, что обсуждается в статье и комментариях), которая подсказка для пользователей библиотек/API, для создания документации и для подсветки возможных ошибок IDE.
                                                                            Ни на что большее, нежели аннотация типов, она и не претендует, хотя её и можно задействовать для квази-статической проверки.

                                                                              0

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

                                                                                0
                                                                                Скромно пытаются и в статическую типизацию
                                                                      0
                                                                      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() }
                                                          0
                                                          Это, в целом, достаточно синтетический пример, но не сложно представить чуть более развесистую логику с более сложных форматированием, для получения, например, ключа кеширования. Вариантов решения что в расте что в пайтоне больше одного, рассматривать их все, слишком объёмно. Pattern matching ждём, само собой.
                                                            0

                                                            Я выберу читаемость над эффективностью если это не bottle neck.

                                                          +10
                                                          Зачем на питоне писать как на сях остается непонятным. особенно чудовищно использование дженериков. аннотации, как по мне, нужны ну разве что в крупной кодобазе на питоне. А зачем ее на питоне то писать? Чтобы побыстрее или тормозило как следует? В общем, присоединяюсь к Шарикову, «Ерунда это всё. Надо взять всё и поделить!»
                                                            +8

                                                            Аннотации удобны — смотришь на код и сразу видно, что именно принимает/возвращает функция.

                                                            –1
                                                            Если не хочется читать эту статью или невтерпеж ждать второй части материала, можно посмотреть видео нашего выступления.

                                                            А можно сделать ссылку на таймкод, на котором начинается часть выступления, не покрытая в статье?

                                                            +7
                                                            пока опциональная статическая типизация в Python выглядит недостаточно мощным инструментом, позволяющим избавиться от всех ошибок, связанных с несоответствием типов

                                                            и вероятно никогда и не станет. Начнём с того, что это не "статическая типизация", а статическая проверка типов. Если в язык занесут типизацию времени исполнения, то возможно это будет шагом к "избавлению от всех ошибок", а возможно — началом конца питона. А пока что вам ничто не мешает обойти эти проверки в случае необходимости.


                                                            Во-вторых, mypy это всего лишь один из инструментов проверки, который не реализует на 100% весь спектр возможных проверок (он не является референсом и постоянно дополняется). Есть и другие тулзы.

                                                              –2
                                                              а возможно — началом конца питона.

                                                              Имхо, конец уже начался, ну или по крайней мере ухудшения того, за что так здорово питон выдвинулся. По моему мнению ошибки, если не сказать хуже это:
                                                              — 3-я версия питона с поломанной совместимостью.
                                                              — Внедрение подсказок статической типизации.
                                                              — И вообще затаскивание в ядро языка вещей типа асинхронщины.
                                                              Идем по пути превращения питона в подбие С++, ужасного монстра, на полном подмножестве которого никто толком не умеет писать.
                                                                +5

                                                                Хмм. Готов поспорить )))


                                                                — 3-я версия питона с поломанной совместимостью.

                                                                Обратная совместимость — это зло, что можно наблюдать на примере жаваскрипта )) Но согласен, можно было получше организовать. 3.0, 3.1, 3.2 — неюзабельные версии. Что ж, на ошибках учатся. С другой стороны проблему unicode vs str без поломки совместимости было не решить, а надо было решать. Или поддержка "старых" классов...


                                                                — Внедрение подсказок статической типизации.

                                                                Крутая вещь. В больших проектах очень помогает. Главное, никто не форсирует, не нужно — не используй, нет проблем.


                                                                — И вообще затаскивание в ядро языка вещей типа асинхронщины.

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


                                                                А вцелом по развитию питона, так оно как раз, кажется, сильно замедлилось после асинка.
                                                                Последнее значительное нововведение — f-строки в 3.6.


                                                                В последнее время, с 3.7, особо ничего нового и не появилось, так по мелочи.


                                                                А насчёт ухудшений… было бы интересно услышать ваше мнение.

                                                              +9
                                                              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. Ну и наоборот :)

                                                                +6
                                                                Я думаю комментарий немного уводит в сторону изначальную мысль.
                                                                Конечно, если ты помнишь что array.pop() может бросить исключение, и еще миллион методов могут бросить исключение и везде лепить проверки, то проблем не будет. Но суть в том, что в Rust за тебя об этом помнит компилятор, ты пишешь items.pop() и он тебе говорит «Чувак, а тут вобщето не обязательно число, вектор может быть и пуст». А стиль написание, это немного ортогонально мысли из статьи.
                                                                  0

                                                                  Да, согласен. С другой стороны, я написал,


                                                                  в теории для питона ничто не мешает принять какой-нибудь новый PEP, в котором было бы описано поведение статической проверки исключений.

                                                                  Если представить, что тайпхинтингу в питоне столько же лет, сколько самому Расту (который ещё сколько-то лет до релиза пилился), то в общем уже и тот результат, что имеем, неплохой. И я уверен, он будет развиваться.


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

                                                                  0
                                                                  None по мнению некоторых уже можно признавать главной ошибкой в программировании. Следующей я думаю можно признавать концепцию исключений. Исключения делают код неявным и нестабильным. Распаковка контейнера с результатом дает тебе точную проверку на наличие результата. Исключение которые пользователь библиотеки перехватывал в v1, в v2 переименовали и код неожиданно упадет с ошибкой. Вот здесь гораздо лучше расписано
                                                                    +1

                                                                    Всегда будут 2 мнения. Одним солёное, другим сладкое. Код на питоне — лучший код из всех возможных императивных языков, имхо. И всё вами перечисленное без сомнения гениально. Даже если кому-то это ошибка, то по крайней мере https://www.northeastern.edu/graduate/blog/most-popular-programming-languages/ вот это говорит о том, что солидной части разработчиков она может приносить 120К баксов в год ))) а на вашем ЯП без None и исключений сколько?

                                                                      +3

                                                                      None — это не null, не путайте, к главной ошибке программирования не относится. None — это нормальное значение для нормального типа.


                                                                      А вот исключения, да, неприятненько.

                                                                        0
                                                                        None — это не null, не путайте, к главной ошибке программирования не относится. None — это нормальное значение для нормального типа.

                                                                        У вас никогда прод не падал с ошибкой "Ой, у NoneType не такого метода"?

                                                                          0

                                                                          Бывало, но оно ничем не отличается от условного TypeError: list indices must be integers or slices, not str

                                                                            0
                                                                            Условное TypeError падает на проде или при компиляции?
                                                                          +3

                                                                          None в Python — это просто переименованный null, ничем от него не отличается.


                                                                          Option::None в Rust это уже совсем другое дело, и, действительно, более безопасная концепция.

                                                                            +1

                                                                            Основная разница None и Null состоит в том, что None не вызывает undefined behavior. А Null вызывает. [null + 100500] куда запишет? Никто не знает.

                                                                              +1

                                                                              Надо различать сишный низкоуровневый NULL и null в ЯВУ типа java/sql.


                                                                              Нельзя запретить struct {int a;int b;} * x = NULL потому что железо разрешает такое (а если вам до лампочки железо, то не пишите на си). Но при этом, написать struct {int a;int b;} x = NULL (не указатель!) в си нельзя. А во многих ЯВУ можно и лично меня это шокирует.

                                                                                0

                                                                                Это в каком-таком языке (кроме питона с его "хинтами") можно иметь указанный тип и его нарушать в той же строке?

                                                                                  0

                                                                                  Например в Java. Там любая переменная типа какого-нибудь класса — на самом деле ссылка, с типом как будто тип-суммой из значения указателя на объект данного класса и значения null типа null:


                                                                                  Object obj = null;
                                                                      +5
                                                                      Не понимаю, зачем в питон тащить чуждые концепции.
                                                                      Python любим как раз за низкий порог вхождения, в отличии от rust. Начните писать на питоне в манере rust, и вы получите другой язык. Который возможно найдет своих поклонников, но будет ли их так же много, сколько сейчас есть у питона? Сомневаюсь.
                                                                      Пример perl6 должен заставить задуматься. Язык, в конце — то концов сделали, но успех пятерки ему даже и не сниться. Непродуманные изменения, не поддержанные сообществом, могут толкнуть питон на ту же дорожку.
                                                                      Есть nim, который достаточно похож на питон, не один к одному, но все таки. Компилируемый, строго типизированный, с достаточно лаконичным синтаксисом. Сколько человек могут похвастаться его знанием?
                                                                      В самом питоне есть многим, если не всем, известный пример не очень удачного заимствования — это django. Я пробовал писать на рельсах и джанго. Сравнения не в пользу последнего. Хотя отдаю должное, django появился к месту и ко времени, и не мало способствовал росту python — сообщества. Только, на мой взгляд, сейчас уже устарел и предпочтение лучше отдавать тому же flask, например.
                                                                      Да и rust ни куда не денется, и питон так и останется подражателем.
                                                                        0
                                                                        зачем в питон тащить чуждые концепции.

                                                                        Очень просто объясню.
                                                                        Низкий порог входжения никуда не денется от того, что в ЯП есть разные фичи. Но как только тебе надо шагнуть за порог, если за ним пустота, тебе придётся менять ЯП на другой, с более высоким порогом. Питон же как раз тем и хорош, что его не надо менять как перчатки.

                                                                          +1
                                                                          Вот совершенно не соглашусь. ИМХО, базовый набор фич должен быть ограничен. Для того, чтобы обеспечить легкий порог вхождения и трудность написания замудренного когда.
                                                                          Дополнительные фичи, если нужны, должны вноситься в виде внешних библиотек.
                                                                          Если же нужно то, что питон не может обеспечить, скажем быстродействие, нужно испольовать или внешние библиотеки, либо переписывать критические части кода на другом языке.
                                                                          Насчет же статических типов и/или статических подсказок типов, то вроде как эта фишка должна помогать бороться с ошибками в сложных программах. Но, в моем личном опыте, ошибки свяазнные с типами или стоят на одном из последних мест по проблемности, или вызывают исключение, что стопроцентно укладывается в концепцию раннего падения.
                                                                          Мне кажется, что если у кого-то ошибки, связанные с типами вызывает очень много трудностей, настолько, что нужно менять язык, то вероятно, что-то не так с подходом к написанию программ на питоне.
                                                                          Для питона лично я бы хотел, чтобы ядро языка сохранялось бы как можно более простым и компактным.
                                                                          А все писать только на питоне — это невозможно да и не нужно.
                                                                            0
                                                                            базовый набор фич должен быть ограничен

                                                                            Если вы говорите о синтаксисе, то в питоне он и так довольно сильно ограничен. И расширяется крайне редко, в отличие от ЖС, в котором каждый год пол-языка добавляется. Вот гляньте в https://habr.com/ru/post/533672/ если интересно. И там мои комментарии, зачем всё это туда тащить, получили минусов больше, чем за все предыдущие 10 лет на хабре =) Каждому своё.


                                                                            Дополнительные фичи, если нужны, должны вноситься в виде внешних библиотек.

                                                                            В идеале — да. Но когда это невозможно, приходится синтаксис расширять. В случае с async например — @coroutine и yield / yield from очень сильно запутывали, и безусловно async/await на порядок проще. Или f-string, тоже значительно упростило многие вещи.


                                                                            вроде как эта фишка должна помогать бороться с ошибками в сложных программах. Но, в моем личном опыте,

                                                                            Ну опыт у всех разный. Лично мне (и со мной согласны остальные 15 человек в моей компании) оно сильно помогает работать с PyCharm, подсказки, переход к определению, подстановка атрибутов, в общем куча плюшек. Может, это даже важнее гипотетических ошибок, которые у опытных программистов и так нечасто бывают. Да и не нравится — не используй, в чём проблема, никто ж не форсирует.


                                                                            А все писать только на питоне — это невозможно да и не нужно.

                                                                            не всё. Но если у вас проект в 100000 строк, то чем богаче язык, тем проще писать.


                                                                            Почему вы так против? В конце концов, вас кто-то заставляет учить все фичи? Вы можете использовать столько, сколько хотите. Можете даже форкнуть и удалить лишнее =)

                                                                              0
                                                                              Почему вы так против? В конце концов, вас кто-то заставляет учить все фичи? Вы можете использовать столько, сколько хотите. Можете даже форкнуть и удалить лишнее =)

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

                                                                                Если вы ограничите область использования языка, то пользоваться им будут меньше и контрибьютить в опенсорс тоже будут меньше. Такими темпами может и читать вскоре станет особо нечего.

                                                                                  –1
                                                                                  Я нигде не имел в виду область применения, если что. Я за ограничение введения новых фич в ядро языка.
                                                                                    +1
                                                                                    Если же нужно то, что питон не может обеспечить, скажем быстродействие, нужно испольовать или внешние библиотеки, либо переписывать критические части кода на другом языке.

                                                                                    Проще уж тогда отказаться от Python в пользу этого другого языка, если он еще и высокоуровневый код позволяет нормально писать.


                                                                                    Насчет же статических типов и/или статических подсказок типов, то вроде как эта фишка должна помогать бороться с ошибками в сложных программах.

                                                                                    Ага. Поэтому давайте большие и сложные программы писать на Python не будем, раз мы не хотим, чтобы он становился комфортным выбором для них. Может тогда вообще лучше отказаться от Питона и осваивать язык, который подойдет и для больших, и для малых проектов? Пусть и с не столь быстрым стартом в обучении.


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

                                                                                      0
                                                                                      Я просто хочу сказать, что ваша позиция на самом деле противоречит вектору развития языка (любого языка, претендующего на звание языка общего назначения).

                                                                                      Да, я против превращения всех ЯП в монстров типа C++. А тенденцию я вижу именно такую.
                                                                                      ИМХО, питон выбез наверх именно в силу своих плюсов, таких как простота ядра языка, хорошая читабельность исходных кодов написанных на нем, REPL, duck typing, жестких требовани1 к оформлению (отступы).
                                                                                      ИМХО статическая типизация в чилсло этих козырных фишек не входила и для меня она не важна, так как в моей практике количество ошибок связанных с типами ничтожно. А вот возможность подсунуть мок объект в любое место — неоценима.
                                                                                        +1
                                                                                        ИМХО статическая типизация в чилсло число этих козырных фишек не входила и для меня она не важна, так как в моей практике количество ошибок связанных с типами ничтожно

                                                                                        У вас просто типов нормальных не было.


                                                                                        А вот возможность подсунуть мок объект в любое место — неоценима.

                                                                                        Тривиально реализуется на любом языке с интерфейсами или его аналогами.

                                                                                          –1
                                                                                          Тривиально реализуется на любом языке с интерфейсами или его аналогами.

                                                                                          Вот этого как раз и хотелось бы избежать.
                                                                                          +1
                                                                                          так как в моей практике количество ошибок связанных с типами ничтожно.
                                                                                          А если mypy --strict? )
                                                                                  0
                                                                                  Если вы говорите о синтаксисе, то в питоне он и так довольно сильно ограничен. И расширяется крайне редко, в отличие от ЖС, в котором каждый год пол-языка добавляется.

                                                                                  В моем представлении есть два ужасных ужаса, C++ и JS где наворочены горы ненужного.
                                                                                  Ну и еще в какой-то степени PHP, куда тоже тащат все, что увидят в других языках. Питон сравнительно простой и хотелось бы сохранить эту ситуацию как можно дольше.
                                                                                0
                                                                                Rust компилируемый язык. Его подход — проверил все что только можно и скомпилировал бинарь, которая, с определенными оговорками, самодостаточна.
                                                                                С питоном такой финт не работает: питон компилирует модули по мере обращения к ним. Да, можно проверять модуль перед релизом, но это не то, потому что это не сработает. Стоит поменять api пакета, оставив иерархию классов прежней (это можете быть необязательно вы, за вас это может сделать один из авторов, творение которых вы помянули в депсах), и питон снова кувыркнется только при попытке вызова «поломанного» метода (Проверка typehint-а недостаточна).
                                                                                Чем подход rust в обработке ошибок, например, отличается от питона, что его нужно тянуть в язык отдельно? Там на невосстановимые ошибки паникуем, на остальные возвращаем соответствующий результат, в питоне ни что не мешает делать так же, кидать эксепшн или возвращать результат, который не обязан быть простым по своему устройству.
                                                                                Даже если rust подобный синтаксис подтянут в питон, его реализация должна будет нормально соседствовать с прежним подходом, учитывая размер существующей кодовой базы, смешивание 2 подходов, как минимум, не должно порождать ошибок, а то, что оно будет — это объективная реальность, а как максимум, данные изменения не должны приводить к просадке скорости существующего кода. Не факт, что это просто будет сделать.

                                                                                В чем я наверное готов согласится с автором публикации — это в том, что, возможно, к инструментам проверки качества кода на питоне стоит присмотреться еще раз, в контексте идей привнесенных rust и изменений в самом python.
                                                                                  +1

                                                                                  Дело в том, что тип Result в Rust, во-первых, является тип-суммой, работать с которым нельзя без того, чтобы явно не рассмотреть оба возможных варианта. А во-вторых, он помечен атрибутом #[must_use], что не позволяет просто забыть его обработать. И проверки этих случаев обеспечивает компилятор статически. Python не сможет такого by design.

                                                                                –1
                                                                                Я пробовал писать на рельсах и джанго. Сравнения не в пользу последнего.

                                                                                джанго — это всё-таки не питон, и вряд ли похвастается первым местом среди веб-фреймворков даже на питоне, не говоря уж о вообще. Плюс разрабы джанго — ребята крайне консервативные и некоторые баги и фичереквесты не закрывают по 10 лет. Насчёт устарел, отнюдь. Он вполне неплохо со своими обязанностями справляется. А сравнивать джанго с фласком, это как сравнивать грузовик с самолётом.


                                                                                Да и rust ни куда не денется, и питон так и останется подражателем.

                                                                                Это ещё кто кому подражатель, интересно. 5-летний Раст или 30-летний питон?


                                                                                Непродуманные изменения, не поддержанные сообществом

                                                                                это намёк на оператор моржа :=? :=) не в восторге от него, но пока что не особо и встречаю. Возможно, пройдёт пара лет, все попривыкнут.


                                                                                Есть nim… Компилируемый, строго типизированный

                                                                                так может в этом то и проблема?
                                                                                Нужен компилируемый язык? — Жава, С, С#, тра-та-та… пара сотен таких есть, навскидку. Нужен интерпретируемый? Ну не РНР же, или ЖС с 900-страничным мануалом по синтаксису и встроенным объектам (без библиотек), где одним лишь описанием того, как работает конверсия типов, 10 страниц заняты, и где фичи можно (то есть нужно) включать флагом в файле настроек или вообще использовать внешний транслятор? Или перл, при всех его достоинствах пригодный разве что статью на хабре распарсить. Да, ещё руби, но по близости к человеческому языку и по читаемости питон всё равно намного впереди.
                                                                                Нужен язык со строгой типизацией времени выполнения? GOTO предыдущий пункт. Не нужна такая типизация? Опять же, см. выше.


                                                                                Так что пока не вижу причин беспокоиться за судьбу питона.

                                                                                +1

                                                                                Во-первых в Rust'е можно иметь динамические типы (dyn).


                                                                                Во-вторых, типизация питона ужасающа. Типы в кавычках, отсутствие Self (хотя, казалось бы, что мешало завести вместе с остальными типами?)


                                                                                class X:
                                                                                  @classmethod
                                                                                  def from_int(cls: 'X', i: int) -> 'X'
                                                                                    ....

                                                                                Если вам в этом месте не захотелось плюнуть на типизацию в Питоне, то вы мало писали на Rust.


                                                                                Вообще, если сравнивать питон с Rust'ом, надо начинать с вот таких вот штук:


                                                                                >>> a=([],)
                                                                                >>> a[0].append(0)

                                                                                А вот место, в котором надо ругать Rust — это за отсутствие yield. Нет ни одной причины, почему нельзя было не завести yield в язык. Но нет.

                                                                                  +2
                                                                                  Выглядит так, как будто генераторы в Rust есть, даже с тем самым ключевым словом yield.

                                                                                  UPD: А, это ещё RFC. Только в процессе обсуждения
                                                                                    0

                                                                                    Очень, очень интересно. Спасибо, буду ожидать с большим интересом.

                                                                                    +1
                                                                                    По поводу типов в кавычках: нужно добавить в модуль
                                                                                    from __future__ import annotations

                                                                                    Да, придется дописывать лишнюю строчку (или прописать её добавление в isort через add-import), но тогда кавычки использовать не нужно.
                                                                                      0

                                                                                      Но Self (тип self) это не привезёт, увы.

                                                                                      +2
                                                                                      А вот место, в котором надо ругать Rust — это за отсутствие yield. Нет ни одной причины, почему нельзя было не завести yield в язык. Но нет.

                                                                                      Согласен. Постоянно писать руками итераторы и ухудшать читаемость кода довольно сильно напрягает, особенно когда знаешь что в других языках проблему решает простая замена return на yield.
                                                                                      Однако, эту функциональность явно вскоре добавят. Есть даже экспериментальные крейт, но он требует включения фич.
                                                                                      0
                                                                                      Если вам в этом месте не захотелось плюнуть на типизацию в Питоне

                                                                                      Расшифруйте. Отсылки вида "на-раст-смотрите" говорят примерно ни о чём.

                                                                                        +2
                                                                                        Система типизации в языке не та вещь которую можно рассматривать по кусочкам. Оно только в комплексе языка работает. Надо несколько языков попробовать чтобы сравнивать. Вот например в тоже TypeScript очень хорошо сделана типизация. У разных языков есть концепции которые реализованы и работают лучше чем в других языках. Вот например про self в rust. Вот два куска кода которые делают одно и тоже в python и в rust
                                                                                        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 или нет. И доступ до метода будет разный через "." или через "::"
                                                                                          +1

                                                                                          Я так и не увидел, где же тут "после раст захотите плюнуть на питонскую типизацию" начинать. Разные языки с разной историей по-разному подошли к вопросу. Нихил нови.


                                                                                          Кроме того, истовое желание писать на питоне как на расте(или наоборот) зло куда большее, нежели ненравящаяся типизация.

                                                                                            –1

                                                                                            Я не могу просто взять и пройти мимо данного комментария.


                                                                                            От типизации, что вы указали, код выглядит грязным. Быстрее питон работать не будет, зачем она нужна да ещё и так некрасиво?


                                                                                            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) ?

                                                                                              +3

                                                                                              Зато совершенно непонятно, какого типа должен быть name. Сегодня вроде бы вполне нормально работает строка, а через некоторое время уже на продакшене выясняется, что автор подразумевал какую-нибудь специальную структуру UserName и забыл отметить это в документации, и в итоге всё падает с ошибкой AttributeError: 'str' object has no attribute 'first_name', ведь в коде этого случайно никто не заметил и автоматические тесты случайно не покрыли этот кейс.

                                                                                                –1

                                                                                                Осмелюсь предположить, что такая ситуация происходит, очевидно, на стадии разработки продукта, не на продакшене.
                                                                                                И эта ситуация весьма обычная, если нету документации или её лень прочитать.


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

                                                                                                  +1

                                                                                                  Ну вот, как можно видеть прямо в этом посте, как минимум у Рамблера подобные штуки успешно доезжают до продакшена

                                                                                                    +2

                                                                                                    Зачем документировать то, что можно указать типами? Документация — это информация для человека и она может не соответствовать реальным данным в коде, потому что происходит дублирование. Типы же понятны не только человеку, но и машине. И машина может проверить автоматически их соответствие. Там, где вводятся типы, специально документировать их наличие не нужно вовсе.

                                                                                                      0

                                                                                                      Я именно это же самое и имел в виду в своём ответе, что тип инстанса класса User будет очевидным. И вообще питон настолько очевидный и простой язык, что если где-то написано сложно или грязно, значит разработчик, скажем мягко, не старался.


                                                                                                      А что до явной типизации — было бы весьма странно требовать коньяк в макдональдс, не находите? Когда я хочу типизацию, я пишу на любимом языке, в котором есть типизация.


                                                                                                      И я буду первым, кто навсегда забросит питон, как только в нём (в чём я сильно сомневаюсь) будет требоваться явная типизация.

                                                                                                        +3

                                                                                                        Ну так для очевидных вещей писать аннотации типов никто и не заставляет. Вот такой код содержит все нужные аннотации типов и успешно пройдёт проверку mypy --strict:


                                                                                                        class User:
                                                                                                            pass
                                                                                                        
                                                                                                        user = User()

                                                                                                        Некоторые менее очевидные, но достаточно простые ситуации mypy способен додумать самостоятельно, например вот здесь никакие аннотации для списка не требуются, всё и так понятно по первому вызову append и mypy --strict тоже успешно проходится:


                                                                                                        class User:
                                                                                                            pass
                                                                                                        
                                                                                                        users = []
                                                                                                        
                                                                                                        users.append(User())
                                                                                                        

                                                                                                        А вот name из вашего примера выше — совершенно не очевиден и требует пояснений. И между документацией и аннотациями типов я выберу аннотации, потому что они, в отличие от документации, проверяются автоматическими инструментами и, как следствие, не могут врать. А вот с врущей документацией я сталкиваюсь регулярно.

                                                                                                          0
                                                                                                          и, как следствие, не могут врать.

                                                                                                          "Don't be so sure, Mr President!"©
                                                                                                          Наверняка можно создать код, который поставит в поле не то, что увидит ожидаемым там любая проверка линтером.
                                                                                                          И при этом не уронит код.

                                                                                                            +3
                                                                                                            mypy еще промахивается много где. Ты вот попробуй на расте так сделать без unsafe.
                                                                                                  0
                                                                                                  Что такое можно писать, и каким образом, чтобы потребовалась типизация на питоне, я реквестирую примеры.

                                                                                                  Blender с версии 2.80 и далее стал требовать от аддонов явно задавать поля для ряда структур, которые потом в сохранённый файл пойдут.
                                                                                                  Но они используют свою сборку питона.

                                                                                                0

                                                                                                Расшифровываю: В методе класса нельзя написать сигнатуру, которая переживёт наследование.


                                                                                                class X:
                                                                                                  def combine (self, other: 'X') -> 'X':
                                                                                                    return other

                                                                                                Что станет с сигнатурой combine при наследовании X?


                                                                                                class Y(X):
                                                                                                   pass

                                                                                                Какой возвращаемый тип у Y().combine()?

                                                                                                  +1

                                                                                                  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*'
                                                                                                  
                                                                                                    0

                                                                                                    Ох, всё ещё хуже. Я написал что-то странное. Я ожидал вернуть инстанс класса, а получился монстр, которого я просто разобрать не могу. Попробуйте сами поиграться с вашим кодом… Почему combine возвращает класс, а не инстанс?


                                                                                                    В расте всё так просто было… self — инстанс, Self — его тип. А тут какая-то интроспективная жуть.


                                                                                                    type(Y().combine(Y()))
                                                                                                    <class '__main__.Y'>
                                                                                                      +1

                                                                                                      Вы всё напутали. combine() возвращает инстанс, а вот type() возвращает уже класс этого инстанса.

                                                                                                        +1

                                                                                                        Собственно, аннотации тут вообще ни при чём, без них ведь то же самое будет


                                                                                                        class X:
                                                                                                          def combine (self, other):
                                                                                                            return other
                                                                                                        
                                                                                                        class Y(X):
                                                                                                           pass
                                                                                                        
                                                                                                        print(type(Y().combine(Y())))  # <class '__main__.Y'>
                                                                                                      0
                                                                                                      Какой возвращаемый тип у Y().combine()?

                                                                                                      Тип TypeError. Как и при X().combine()


                                                                                                      И, поскольку эта типизация подсказка программисту и среде, в среде будет подсветка ошибки для любого переданного не-экземпляра класса(X или Y) или наследник от оных. Если среда поддерживает, конечно. В PyCharm будет.


                                                                                                      А так — во всех случаях "то, что туда передаст программист". Эта типизация совсем не мешает передать что-то совсем другое, даже не унаследованное от ожидаемого типа… и получить валидный результат, если передающий программист озаботился совместимостью данных/протоколов с тем, что ожидал программист метода.


                                                                                                      В плюсах именно что возможность передать что-то другого типа, но совместимое по протоколам/форматам, не борясь с правильным указанием "ожидаем такой вот тип объекта, и такой, и такой, и вот на такой надо перегрузку тоже указать" для компилятора.
                                                                                                      В минусах — исключения времени исполнения и отсутствие гарантии что придёт именно ожидаемый тип объекта.
                                                                                                      Кому что больше перевешивает.

                                                                                                        0
                                                                                                        Код на Crystal
                                                                                                        class A
                                                                                                          def a
                                                                                                            puts "a"
                                                                                                            return self
                                                                                                          end
                                                                                                        end
                                                                                                         
                                                                                                        class B < A
                                                                                                          def b
                                                                                                            puts "b"
                                                                                                          end
                                                                                                        end
                                                                                                         
                                                                                                        B.new().a.b
                                                                                                      +1

                                                                                                      Да есть там причина. yield создаёт самоссылающуюся структуру данных, со всеми проблемами await. А await и появился-то совсем недавно...

                                                                                                      –2
                                                                                                      Данная статья будет неполной без упоминания, что в Питоне тоже есть `Result`, `Maybe` и тд.
                                                                                                      И типизация замечательно работает: github.com/dry-python/returns
                                                                                                        +1
                                                                                                        Там про это есть отдельный абзац, ссылка, к сожалению, потерялась по дороге
                                                                                                          +1
                                                                                                          Никита, поправил. Не было ссылки, как Макс уже написал.
                                                                                                          +2

                                                                                                          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);
                                                                                                          }
                                                                                                            0
                                                                                                            Да, вы правы, спасибо!
                                                                                                            0
                                                                                                            Коллеги, у меня немного не технический вопрос.
                                                                                                            В статье мы расскажем о том, что заставило нас отойти от привычного стека технологий

                                                                                                            Вот скажите, вы такие сидели писали на питоне много лет, много-много кода.
                                                                                                            Вам говорят, сделайте новый проект. И вы «мы его будем писать на Раст, ничего что у нас нет в этом опыта, да и Раст мы первый раз видим, но этот новый и важный проект для бизнеса мы напишем только на Раст», и вам отвечают «ОК! Вот вам сундук с золотом»

                                                                                                            Не стеб, правда интересно, как это происходит.

                                                                                                            И еще, вот если ваша команда уволится, кто будет поддерживать одновременно проекты на питон и раст? Неужели таких спецов на рынке вагон?
                                                                                                              +2
                                                                                                              Вопрос вполне понятный. Давайте расскажу. Сначала тимлид нашего проекта cbmw сделал небольшой прототип для того, чтобы понять насколько готова инфраструктура – есть ли для Rust веб-фреймворки, орм, чем собирать метрики, логи и проч. Сделали нагрузочное тестирование. Потом подключилась часть команды (python-разработчики) и написали несколько дополнительных ручек апишки. В итоге стало понятно с чем будем работать, какие плюсы принесет и насколько сложно python-разработчику начать писать на Rust. Взвесили известные проблемы с ошибками, производительность Python-приложений и риски, которые Rust принес. Поняли, что оно того стоит :)
                                                                                                                0
                                                                                                                насколько сложно python-разработчику начать писать на Rust

                                                                                                                Вот здесь по-подробнее пожалуйста
                                                                                                                  0
                                                                                                                  Я смогу ответить на ваш вопрос не в общем случае, а только применительно к нашей команде. Если выделить суть – когда в команде есть кто-то, кто уже пишет на Rust и может помочь разобраться с ошибками, то можно почитать Rust Book и сразу начать писать код приложения. Поначалу придется много дергать того, кто уже понимает, как разобраться с ошибками, через месяц-два поменьше :) Еще через полгода общая картина наконец начнет складываться и тогда можно помогать другим разбираться с ошибками.
                                                                                                              –2
                                                                                                              if len(array) == 0:

                                                                                                              А вы точно python программист? Python программист напишет тут


                                                                                                              if not array:

                                                                                                              Я люблю Rust но вы сравниваете тёплое с мягким. Если вы хотите находить ошибки локально вам не mypy писать надо а тесты.

                                                                                                                +4

                                                                                                                … И теперь код работает в том числе и с целыми числами, и с булевыми значениями там, где работал только с объектами с методом __len__. Хороший размен, да.

                                                                                                              Only users with full accounts can post comments. Log in, please.