Создание нового примитивного типа

PythonPythonBeginner
Практиковаться сейчас

This tutorial is from open-source community. Access the source code

💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал

Введение

В этом практическом занятии (лабораторной работе) вы научитесь создавать новый примитивный тип в Python и реализовывать для него основные методы. Вы также получите понимание объекта - протокола Python. В большинстве программ на Python для представления данных используются встроенные примитивные типы, такие как int, float и str. Однако Python позволяет создавать пользовательские типы, как это можно увидеть в модулях, таких как decimal и fractions в стандартной библиотеке.

В этом практическом занятии вы создадите новый примитивный тип с именем MutInt (Изменяемое целое число). В отличие от неизменяемых целых чисел Python, MutInt можно изменить после создания. Это упражнение продемонстрирует основные принципы, необходимые для создания полностью функционального примитивного типа в Python.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/BasicConceptsGroup(["Basic Concepts"]) python(("Python")) -.-> python/ControlFlowGroup(["Control Flow"]) python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python(("Python")) -.-> python/AdvancedTopicsGroup(["Advanced Topics"]) python/BasicConceptsGroup -.-> python/type_conversion("Type Conversion") python/ControlFlowGroup -.-> python/conditional_statements("Conditional Statements") python/FunctionsGroup -.-> python/function_definition("Function Definition") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") python/ObjectOrientedProgrammingGroup -.-> python/constructor("Constructor") python/AdvancedTopicsGroup -.-> python/decorators("Decorators") subgraph Lab Skills python/type_conversion -.-> lab-132443{{"Создание нового примитивного типа"}} python/conditional_statements -.-> lab-132443{{"Создание нового примитивного типа"}} python/function_definition -.-> lab-132443{{"Создание нового примитивного типа"}} python/classes_objects -.-> lab-132443{{"Создание нового примитивного типа"}} python/constructor -.-> lab-132443{{"Создание нового примитивного типа"}} python/decorators -.-> lab-132443{{"Создание нового примитивного типа"}} end

Создание базового класса MutInt

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

  1. Откройте WebIDE и перейдите в каталог /home/labex/project. WebIDE - это интегрированная среда разработки, в которой вы можете писать, редактировать и запускать свой код. Переход в этот каталог гарантирует, что все ваши файлы будут организованы в одном месте и могут корректно взаимодействовать друг с другом.

  2. Откройте файл mutint.py, который был создан для вас на этапе настройки. В этом файле будет определен наш класс MutInt.

  3. Добавьте следующий код для определения базового класса MutInt:

## mutint.py

class MutInt:
    """
    A mutable integer class that allows its value to be modified after creation.
    """
    __slots__ = ['value']

    def __init__(self, value):
        """Initialize with an integer value."""
        self.value = value

Атрибут __slots__ используется для определения атрибутов, которые может иметь этот класс. Атрибуты - это, по сути, переменные, принадлежащие объекту класса. Используя __slots__, мы сообщаем Python, чтобы он использовал более эффективный по памяти способ хранения атрибутов. В данном случае наш класс MutInt будет иметь только один атрибут с именем value. Это означает, что каждый объект класса MutInt сможет хранить только одну часть данных, а именно целое число.

Метод __init__ является конструктором нашего класса. Конструктор - это специальный метод, который вызывается при создании объекта класса. Он принимает параметр value и сохраняет его в атрибуте value экземпляра. Экземпляр - это отдельный объект, созданный на основе чертежа класса.

Протестируем наш класс, создав скрипт на Python для его использования:

  1. Создайте новый файл с именем test_mutint.py в том же каталоге:
## test_mutint.py

from mutint import MutInt

## Create a MutInt object
a = MutInt(3)
print(f"Created MutInt with value: {a.value}")

## Modify the value (demonstrating mutability)
a.value = 42
print(f"Modified value to: {a.value}")

## Try adding (this will fail)
try:
    result = a + 10
    print(f"Result of a + 10: {result}")
except TypeError as e:
    print(f"Error when adding: {e}")

В этом тестовом скрипте мы сначала импортируем класс MutInt из файла mutint.py. Затем создаем объект класса MutInt с начальным значением 3. Выводим начальное значение, затем изменяем его на 42 и выводим новое значение. Наконец, пытаемся добавить 10 к объекту MutInt, что приведет к ошибке, так как наш класс пока не поддерживает операцию сложения.

  1. Запустите тестовый скрипт, выполнив следующую команду в терминале:
python3 /home/labex/project/test_mutint.py

Терминал - это командная строка, в которой вы можете выполнять различные команды для взаимодействия с системой и вашим кодом. Выполнение этой команды запустит скрипт test_mutint.py.

Вы должны увидеть вывод, похожий на следующий:

Created MutInt with value: 3
Modified value to: 42
Error when adding: unsupported operand type(s) for +: 'MutInt' and 'int'

Наш класс MutInt успешно хранит и обновляет значение. Однако у него есть несколько ограничений:

  • Он не выводится красиво при печати.
  • Он не поддерживает математические операции, такие как сложение.
  • Он не поддерживает сравнения.
  • Он не поддерживает преобразования типов.

В следующих шагах мы пошагово устраним эти ограничения, чтобы наш класс MutInt вел себя более как настоящий примитивный тип.

✨ Проверить решение и практиковаться

Улучшение строкового представления

Когда вы выводите объект MutInt в Python, вы увидите такой вывод, как <__main__.MutInt object at 0x...>. Этот вывод не очень полезен, так как он не сообщает вам фактическое значение объекта MutInt. Чтобы сделать более понятным, что представляет собой объект, мы реализуем специальные методы для строкового представления.

  1. Откройте файл mutint.py в WebIDE и обновите его следующим кодом:
## mutint.py

class MutInt:
    """
    A mutable integer class that allows its value to be modified after creation.
    """
    __slots__ = ['value']

    def __init__(self, value):
        """Initialize with an integer value."""
        self.value = value

    def __str__(self):
        """Return a string representation for printing."""
        return str(self.value)

    def __repr__(self):
        """Return a developer-friendly string representation."""
        return f'MutInt({self.value!r})'

    def __format__(self, fmt):
        """Support string formatting with format specifications."""
        return format(self.value, fmt)

Мы добавили три важных метода в класс MutInt:

  • __str__(): Этот метод вызывается, когда вы используете функцию str() на объекте или когда вы напрямую выводите объект. Он должен возвращать человекочитаемую строку.
  • __repr__(): Этот метод предоставляет "официальное" строковое представление объекта. Он в основном используется для отладки и, желательно, должен возвращать строку, которая, если передать в функцию eval(), воссоздаст объект.
  • __format__(): Этот метод позволяет использовать систему форматирования строк Python с объектами MutInt. Вы можете использовать спецификации форматирования, такие как выравнивание и форматирование чисел.
  1. Создайте новый тестовый файл с именем test_string_repr.py для тестирования этих новых методов:
## test_string_repr.py

from mutint import MutInt

## Create a MutInt object
a = MutInt(3)

## Test string representation
print(f"str(a): {str(a)}")
print(f"repr(a): {repr(a)}")

## Test direct printing
print(f"Print a: {a}")

## Test string formatting
print(f"Formatted with padding: '{a:*^10}'")
print(f"Formatted as decimal: '{a:d}'")

## Test mutability
a.value = 42
print(f"After changing value, repr(a): {repr(a)}")

В этом тестовом файле мы сначала импортируем класс MutInt. Затем создаем объект MutInt со значением 3. Мы тестируем методы __str__() и __repr__() с помощью функций str() и repr(). Мы также тестируем прямой вывод, форматирование строк и изменяемость объекта MutInt.

  1. Запустите тестовый скрипт:
python3 /home/labex/project/test_string_repr.py

Когда вы выполните эту команду, Python выполнит скрипт test_string_repr.py. Вы должны увидеть вывод, похожий на следующий:

str(a): 3
repr(a): MutInt(3)
Print a: 3
Formatted with padding: '****3*****'
Formatted as decimal: '3'
After changing value, repr(a): MutInt(42)

Теперь наши объекты MutInt выводятся красиво. Строковое представление показывает базовое значение, и мы можем использовать форматирование строк, как и с обычными целыми числами.

Разница между __str__() и __repr__() заключается в том, что __str__() предназначен для создания человекочитаемого вывода, в то время как __repr__() должен, желательно, создавать строку, которая, когда передается в eval(), воссоздает объект. Именно поэтому мы включили имя класса в метод __repr__().

Метод __format__() позволяет нашему объекту работать с системой форматирования Python, поэтому мы можем использовать спецификации форматирования, такие как выравнивание и форматирование чисел.

✨ Проверить решение и практиковаться

Добавление математических операций

В настоящее время наш класс MutInt не поддерживает математические операции, такие как сложение. В Python, чтобы включить такие операции для пользовательского класса, нам нужно реализовать специальные методы. Эти специальные методы также известны как "магические методы" или "дандер-методы", так как они окружены двойными подчеркиваниями. Давайте добавим функциональность сложения, реализовав соответствующие специальные методы для арифметических операций.

  1. Откройте файл mutint.py в WebIDE и обновите его следующим кодом:
## mutint.py

class MutInt:
    """
    A mutable integer class that allows its value to be modified after creation.
    """
    __slots__ = ['value']

    def __init__(self, value):
        """Initialize with an integer value."""
        self.value = value

    def __str__(self):
        """Return a string representation for printing."""
        return str(self.value)

    def __repr__(self):
        """Return a developer-friendly string representation."""
        return f'MutInt({self.value!r})'

    def __format__(self, fmt):
        """Support string formatting with format specifications."""
        return format(self.value, fmt)

    def __add__(self, other):
        """Handle addition: self + other."""
        if isinstance(other, MutInt):
            return MutInt(self.value + other.value)
        elif isinstance(other, int):
            return MutInt(self.value + other)
        else:
            return NotImplemented

    def __radd__(self, other):
        """Handle reversed addition: other + self."""
        ## For commutative operations like +, we can reuse __add__
        return self.__add__(other)

    def __iadd__(self, other):
        """Handle in-place addition: self += other."""
        if isinstance(other, MutInt):
            self.value += other.value
            return self
        elif isinstance(other, int):
            self.value += other
            return self
        else:
            return NotImplemented

Мы добавили три новых метода в класс MutInt:

  • __add__(): Этот метод вызывается, когда оператор + используется с нашим объектом MutInt слева. Внутри этого метода мы сначала проверяем, является ли операнд other экземпляром MutInt или int. Если это так, мы выполняем сложение и возвращаем новый объект MutInt с результатом. Если операнд other является чем-то другим, мы возвращаем NotImplemented. Это сообщает Python попробовать другие методы или вызвать TypeError.
  • __radd__(): Этот метод вызывается, когда оператор + используется с нашим объектом MutInt справа. Поскольку сложение является коммутативной операцией (т.е. a + b равно b + a), мы можем просто повторно использовать метод __add__.
  • __iadd__(): Этот метод вызывается, когда оператор += используется с нашим объектом MutInt. Вместо создания нового объекта он модифицирует существующий объект MutInt и возвращает его.
  1. Создайте новый тестовый файл с именем test_math_ops.py для тестирования этих новых методов:
## test_math_ops.py

from mutint import MutInt

## Create MutInt objects
a = MutInt(3)
b = MutInt(5)

## Test regular addition
c = a + b
print(f"a + b = {c}")

## Test addition with int
d = a + 10
print(f"a + 10 = {d}")

## Test reversed addition
e = 7 + a
print(f"7 + a = {e}")

## Test in-place addition
print(f"Before a += 5: a = {a}")
a += 5
print(f"After a += 5: a = {a}")

## Test in-place addition with reference sharing
f = a  ## f and a point to the same object
print(f"Before a += 10: a = {a}, f = {f}")
a += 10
print(f"After a += 10: a = {a}, f = {f}")

## Test unsupported operation
try:
    result = a + 3.5  ## Adding a float is not supported
    print(f"a + 3.5 = {result}")
except TypeError as e:
    print(f"Error when adding float: {e}")

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

  1. Запустите тестовый скрипт:
python3 /home/labex/project/test_math_ops.py

Вы должны увидеть вывод, похожий на следующий:

a + b = MutInt(8)
a + 10 = MutInt(13)
7 + a = MutInt(10)
Before a += 5: a = MutInt(3)
After a += 5: a = MutInt(8)
Before a += 10: a = MutInt(8), f = MutInt(8)
After a += 10: a = MutInt(18), f = MutInt(18)
Error when adding float: unsupported operand type(s) for +: 'MutInt' and 'float'

Теперь наш класс MutInt поддерживает базовые операции сложения. Обратите внимание, что когда мы использовали +=, и a, и f были обновлены. Это показывает, что a += 10 модифицировал существующий объект, а не создал новый.

Это поведение с изменяемыми объектами аналогично встроенным изменяемым типам Python, таким как списки. Например:

a = [1, 2, 3]
b = a
a += [4, 5]  ## Both a and b are updated

В отличие от этого, для неизменяемых типов, таких как кортежи, += создает новый объект:

c = (1, 2, 3)
d = c
c += (4, 5)  ## c is a new object, d still points to the old one
✨ Проверить решение и практиковаться

Реализация операций сравнения

В настоящее время наши объекты MutInt не могут быть сравнены между собой или с обычными целыми числами. В Python операции сравнения, такие как ==, <, <=, >, >=, очень полезны при работе с объектами. Они позволяют нам определять отношения между разными объектами, что является важной частью многих сценариев программирования, таких как сортировка, фильтрация и условные операторы. Поэтому давайте добавим функциональность сравнения в наш класс MutInt, реализовав специальные методы для операций сравнения.

  1. Откройте файл mutint.py в WebIDE и обновите его следующим кодом:
## mutint.py

from functools import total_ordering

@total_ordering
class MutInt:
    """
    A mutable integer class that allows its value to be modified after creation.
    """
    __slots__ = ['value']

    def __init__(self, value):
        """Initialize with an integer value."""
        self.value = value

    def __str__(self):
        """Return a string representation for printing."""
        return str(self.value)

    def __repr__(self):
        """Return a developer-friendly string representation."""
        return f'MutInt({self.value!r})'

    def __format__(self, fmt):
        """Support string formatting with format specifications."""
        return format(self.value, fmt)

    def __add__(self, other):
        """Handle addition: self + other."""
        if isinstance(other, MutInt):
            return MutInt(self.value + other.value)
        elif isinstance(other, int):
            return MutInt(self.value + other)
        else:
            return NotImplemented

    def __radd__(self, other):
        """Handle reversed addition: other + self."""
        return self.__add__(other)

    def __iadd__(self, other):
        """Handle in-place addition: self += other."""
        if isinstance(other, MutInt):
            self.value += other.value
            return self
        elif isinstance(other, int):
            self.value += other
            return self
        else:
            return NotImplemented

    def __eq__(self, other):
        """Handle equality comparison: self == other."""
        if isinstance(other, MutInt):
            return self.value == other.value
        elif isinstance(other, int):
            return self.value == other
        else:
            return NotImplemented

    def __lt__(self, other):
        """Handle less-than comparison: self < other."""
        if isinstance(other, MutInt):
            return self.value < other.value
        elif isinstance(other, int):
            return self.value < other
        else:
            return NotImplemented

Мы внесли несколько важных улучшений:

  1. Импортируем и используем декоратор @total_ordering из модуля functools. Декоратор @total_ordering - это мощный инструмент в Python. Он помогает нам сэкономить много времени и усилий при реализации методов сравнения для класса. Вместо того, чтобы вручную определять все шесть методов сравнения (__eq__, __ne__, __lt__, __le__, __gt__, __ge__), нам нужно определить только __eq__ и один другой метод сравнения (в нашем случае __lt__). Затем декоратор автоматически сгенерирует оставшиеся четыре метода сравнения для нас.

  2. Добавляем метод __eq__() для обработки операций сравнения на равенство (==). Этот метод используется для проверки, имеют ли два объекта MutInt или объект MutInt и целое число одинаковые значения.

  3. Добавляем метод __lt__() для обработки операций сравнения "меньше чем" (<). Этот метод используется для определения, имеет ли один объект MutInt или объект MutInt по сравнению с целым числом меньшее значение.

  4. Создайте новый тестовый файл с именем test_comparisons.py для тестирования этих новых методов:

## test_comparisons.py

from mutint import MutInt

## Create MutInt objects
a = MutInt(3)
b = MutInt(3)
c = MutInt(5)

## Test equality
print(f"a == b: {a == b}")  ## Should be True (same value)
print(f"a == c: {a == c}")  ## Should be False (different values)
print(f"a == 3: {a == 3}")  ## Should be True (comparing with int)
print(f"a == 5: {a == 5}")  ## Should be False (different values)

## Test less than
print(f"a < c: {a < c}")    ## Should be True (3 < 5)
print(f"c < a: {c < a}")    ## Should be False (5 is not < 3)
print(f"a < 4: {a < 4}")    ## Should be True (3 < 4)

## Test other comparisons (provided by @total_ordering)
print(f"a <= b: {a <= b}")  ## Should be True (3 <= 3)
print(f"a > c: {a > c}")    ## Should be False (3 is not > 5)
print(f"c >= a: {c >= a}")  ## Should be True (5 >= 3)

## Test with a different type
print(f"a == '3': {a == '3'}")  ## Should be False (different types)

В этом тестовом файле мы создаем несколько объектов MutInt и выполняем различные операции сравнения над ними. Мы также сравниваем объекты MutInt с обычными целыми числами и с другим типом (в данном случае строкой). Запустив эти тесты, мы можем убедиться, что наши методы сравнения работают как ожидается.

  1. Запустите тестовый скрипт:
python3 /home/labex/project/test_comparisons.py

Вы должны увидеть вывод, похожий на следующий:

a == b: True
a == c: False
a == 3: True
a == 5: False
a < c: True
c < a: False
a < 4: True
a <= b: True
a > c: False
c >= a: True
a == '3': False

Теперь наш класс MutInt поддерживает все операции сравнения.

Декоратор @total_ordering особенно полезен, так как он спасает нас от необходимости вручную реализовывать все шесть методов сравнения. Предоставив только __eq__ и __lt__, Python может автоматически вывести остальные четыре метода сравнения.

При реализации пользовательских классов, как правило, хорошей практикой является сделать их совместимыми как с объектами того же типа, так и с встроенными типами, где это имеет смысл. Вот почему наши методы сравнения обрабатывают как объекты MutInt, так и обычные целые числа. Таким образом, наш класс MutInt может быть использован более гибко в различных сценариях программирования.

✨ Проверить решение и практиковаться

Добавление преобразований типов (Type Conversions)

Наш класс MutInt в настоящее время поддерживает операции сложения и сравнения. Однако он не работает со встроенными в Python функциями преобразования, такими как int() и float(). Эти функции преобразования очень полезны в Python. Например, когда вы хотите преобразовать значение в целое число или число с плавающей точкой для различных вычислений или операций, вы полагаетесь на эти функции. Итак, давайте добавим возможности нашему классу MutInt для работы с ними.

  1. Откройте mutint.py в WebIDE и обновите его следующим кодом:
## mutint.py

from functools import total_ordering

@total_ordering
class MutInt:
    """
    Изменяемый класс целых чисел, который позволяет изменять свое значение после создания.
    """
    __slots__ = ['value']

    def __init__(self, value):
        """Инициализация целочисленным значением."""
        self.value = value

    def __str__(self):
        """Возвращает строковое представление для печати."""
        return str(self.value)

    def __repr__(self):
        """Возвращает строковое представление, удобное для разработчиков."""
        return f'MutInt({self.value!r})'

    def __format__(self, fmt):
        """Поддержка форматирования строк со спецификациями формата."""
        return format(self.value, fmt)

    def __add__(self, other):
        """Обработка сложения: self + other."""
        if isinstance(other, MutInt):
            return MutInt(self.value + other.value)
        elif isinstance(other, int):
            return MutInt(self.value + other)
        else:
            return NotImplemented

    def __radd__(self, other):
        """Обработка обратного сложения: other + self."""
        return self.__add__(other)

    def __iadd__(self, other):
        """Обработка сложения на месте (in-place addition): self += other."""
        if isinstance(other, MutInt):
            self.value += other.value
            return self
        elif isinstance(other, int):
            self.value += other
            return self
        else:
            return NotImplemented

    def __eq__(self, other):
        """Обработка сравнения на равенство: self == other."""
        if isinstance(other, MutInt):
            return self.value == other.value
        elif isinstance(other, int):
            return self.value == other
        else:
            return NotImplemented

    def __lt__(self, other):
        """Обработка сравнения "меньше чем": self < other."""
        if isinstance(other, MutInt):
            return self.value < other.value
        elif isinstance(other, int):
            return self.value < other
        else:
            return NotImplemented

    def __int__(self):
        """Преобразование в int (integer)."""
        return self.value

    def __float__(self):
        """Преобразование в float (число с плавающей точкой)."""
        return float(self.value)

    __index__ = __int__  ## Поддержка индексации массивов и других операций, требующих индекс

    def __lshift__(self, other):
        """Обработка сдвига влево: self << other."""
        if isinstance(other, MutInt):
            return MutInt(self.value << other.value)
        elif isinstance(other, int):
            return MutInt(self.value << other)
        else:
            return NotImplemented

    def __rlshift__(self, other):
        """Обработка обратного сдвига влево: other << self."""
        if isinstance(other, int):
            return MutInt(other << self.value)
        else:
            return NotImplemented

Мы добавили три новых метода в класс MutInt:

  1. __int__(): Этот метод вызывается, когда вы используете функцию int() для объекта нашего класса MutInt. Например, если у вас есть объект MutInt a, и вы пишете int(a), Python вызовет метод __int__() объекта a.
  2. __float__(): Аналогично, этот метод вызывается, когда вы используете функцию float() для нашего объекта MutInt.
  3. __index__(): Этот метод используется для операций, которые специально требуют целочисленный индекс. Например, когда вы хотите получить доступ к элементу в списке, используя индекс, или выполнить операции определения битовой длины (bit-length operations), Python требует целочисленный индекс.
  4. __lshift__(): Этот метод обрабатывает операции сдвига влево, когда объект MutInt находится слева от оператора <<.
  5. __rlshift__(): Этот метод обрабатывает операции сдвига влево, когда объект MutInt находится справа от оператора <<.

Метод __index__ имеет решающее значение для операций, требующих целочисленный индекс, таких как индексация списка, срезы (slicing) и операции определения битовой длины. В нашей простой реализации мы устанавливаем его таким же, как __int__, потому что значение нашего объекта MutInt можно напрямую использовать в качестве целочисленного индекса.

Методы __lshift__ и __rlshift__ необходимы для поддержки побитовых операций сдвига влево (bitwise left shift operations). Они позволяют нашим объектам MutInt участвовать в побитовых операциях, что является общим требованием для целочисленных типов.

  1. Создайте новый тестовый файл с именем test_conversions.py для тестирования этих новых методов:
## test_conversions.py

from mutint import MutInt

## Создаем объект MutInt
a = MutInt(3)

## Тестируем преобразования
print(f"int(a): {int(a)}")
print(f"float(a): {float(a)}")

## Тестируем использование в качестве индекса
names = ['Dave', 'Guido', 'Paula', 'Thomas', 'Lewis']
print(f"names[a]: {names[a]}")

## Тестируем использование в битовых операциях (требуется __index__)
print(f"1 << a: {1 << a}")  ## Сдвиг влево на 3

## Тестируем функции hex/oct/bin (требуется __index__)
print(f"hex(a): {hex(a)}")
print(f"oct(a): {oct(a)}")
print(f"bin(a): {bin(a)}")

## Изменяем и тестируем снова
a.value = 4
print(f"\nПосле изменения значения на 4:")
print(f"int(a): {int(a)}")
print(f"names[a]: {names[a]}")
  1. Запустите тестовый скрипт:
python3 /home/labex/project/test_conversions.py

Вы должны увидеть вывод, похожий на этот:

int(a): 3
float(a): 3.0
names[a]: Thomas
1 << a: 8
hex(a): 0x3
oct(a): 0o3
bin(a): 0b11

После изменения значения на 4:
int(a): 4
names[a]: Lewis

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

Метод __index__ особенно важен. Он был введен в Python, чтобы позволить объектам использоваться в ситуациях, когда требуется целочисленный индекс, таких как индексация списка, побитовые операции и различные функции, такие как hex(), oct() и bin().

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

Полная реализация MutInt

Вот наша полная реализация MutInt со всеми добавленными нами функциями:

## mutint.py

from functools import total_ordering

@total_ordering
class MutInt:
    """
    Изменяемый класс целых чисел, который позволяет изменять свое значение после создания.
    """
    __slots__ = ['value']

    def __init__(self, value):
        """Инициализация целочисленным значением."""
        self.value = value

    def __str__(self):
        """Возвращает строковое представление для печати."""
        return str(self.value)

    def __repr__(self):
        """Возвращает строковое представление, удобное для разработчиков."""
        return f'MutInt({self.value!r})'

    def __format__(self, fmt):
        """Поддержка форматирования строк со спецификациями формата."""
        return format(self.value, fmt)

    def __add__(self, other):
        """Обработка сложения: self + other."""
        if isinstance(other, MutInt):
            return MutInt(self.value + other.value)
        elif isinstance(other, int):
            return MutInt(self.value + other)
        else:
            return NotImplemented

    def __radd__(self, other):
        """Обработка обратного сложения: other + self."""
        return self.__add__(other)

    def __iadd__(self, other):
        """Обработка сложения на месте (in-place addition): self += other."""
        if isinstance(other, MutInt):
            self.value += other.value
            return self
        elif isinstance(other, int):
            self.value += other
            return self
        else:
            return NotImplemented

    def __eq__(self, other):
        """Обработка сравнения на равенство: self == other."""
        if isinstance(other, MutInt):
            return self.value == other.value
        elif isinstance(other, int):
            return self.value == other
        else:
            return NotImplemented

    def __lt__(self, other):
        """Обработка сравнения "меньше чем": self < other."""
        if isinstance(other, MutInt):
            return self.value < other.value
        elif isinstance(other, int):
            return self.value < other
        else:
            return NotImplemented

    def __int__(self):
        """Преобразование в int (integer)."""
        return self.value

    def __float__(self):
        """Преобразование в float (число с плавающей точкой)."""
        return float(self.value)

    __index__ = __int__  ## Поддержка индексации массивов и других операций, требующих индекс

    def __lshift__(self, other):
        """Обработка сдвига влево: self << other."""
        if isinstance(other, MutInt):
            return MutInt(self.value << other.value)
        elif isinstance(other, int):
            return MutInt(self.value << other)
        else:
            return NotImplemented

    def __rlshift__(self, other):
        """Обработка обратного сдвига влево: other << self."""
        if isinstance(other, int):
            return MutInt(other << self.value)
        else:
            return NotImplemented

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

✨ Проверить решение и практиковаться

Резюме

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

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