Dunder-методы в Python: предназначение и применение

    Команда Simple-Server
    03.06.2026
    21 мин

    Материал подготовлен командой Simple-Server для администраторов VPS и выделенных серверов. Команды и пути проверяйте на тестовой машине перед production.

    Создание, инициализация и удаление

    Создание, инициализация и удаление — основные этапы жизненного цикла объекта в языке Python. Каждому из таких этапов соответствует определенный dunder-метод.

    СинтаксисDunder-методРезультатОписание
    a = C(b, c)C.new(b, c)CСоздание
    a = C(b, c)C.init(b, c)NoneИнициализация
    del aa.del()NoneУдаление

    При этом общий алгоритм функционирования этих методов имеет свои особенности:

    • Создание. Вызывается метод __new__() с набором аргументов. Первый — класс объекта (именно класс, а не сам объект), имя которого не регламентировано и может быть любым. Все остальные — параметры, указанные при создании объекта в вызывающем коде. По итогу метод __new__() должен вернуть экземпляр класса — новый объект.

    • Инициализация. Сразу после возврата нового объекта из метода __new__() автоматически вызывается метод __init__(), внутри которого выполняется инициализация созданного объекта. Первым аргументом передается сам объект, а всеми остальными — параметры, указанные при создании объекта. При этом имя первого аргумента регламентировано — им является ключевое слово self.

    • Удаление. Явное удаление объекта с помощью ключевого слово del сопровождается вызовом метода __del__(). Его единственный аргумент — сам объект, доступ к которому осуществляется через ключевое слово self.

    Создание. Вызывается метод __new__() с набором аргументов. Первый — класс объекта (именно класс, а не сам объект), имя которого не регламентировано и может быть любым. Все остальные — параметры, указанные при создании объекта в вызывающем коде. По итогу метод __new__() должен вернуть экземпляр класса — новый объект.

    Инициализация. Сразу после возврата нового объекта из метода __new__() автоматически вызывается метод __init__(), внутри которого выполняется инициализация созданного объекта. Первым аргументом передается сам объект, а всеми остальными — параметры, указанные при создании объекта. При этом имя первого аргумента регламентировано — им является ключевое слово self.

    Удаление. Явное удаление объекта с помощью ключевого слово del сопровождается вызовом метода __del__(). Его единственный аргумент — сам объект, доступ к которому осуществляется через ключевое слово self.

    Благодаря возможности переопределения dunder-методов, отвечающих за жизненный цикл объекта, можно создавать уникальную реализацию пользовательских классов:

    class Initable: instances = 0 # переменная класса, но не объекта def __new__(cls, first, second): print(cls.instances) cls.instances += 1 return super().__new__(cls) # вызов метода создания объекта базового класса с именем текущего класса в качестве аргумента def __init__(self, first, second): self.first = first # переменная объекта self.second = second # еще одна переменная объекта def __del__(self): print("Аннигилирован!") inited = Initable("Инициализируемый", 13) # вывод: 0 print(inited.first) # вывод: Инициализируемый print(inited.second) # вывод: 13 del inited # вывод: Аннигилирован!

    Благодаря подобным методам-хукам можно управлять не только внутренним состоянием объекта, но и ресурсами вне него.

    Созданные объекты в языке Python можно сравнивать между собой, получая положительный или отрицательный результат. Каждый оператор сравнения ассоциирован со своим dunder-методом.

    СинтаксисDunder-методРезультатОписание
    a == b или a is ba.eq(b)boolРавно
    a != ba.ne(b)boolНеравно
    a > ba.gt(b)boolБольше
    a < ba.lt(b)boolМеньше
    a >= ba.ge(b)boolБольше или равно
    a <= ba.le(b)boolМеньше или равно
    hash(a)a.hash()intХеширование

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

    a = 5 b = 6 c = "Это самая обыкновенная строка" print(a == b) # вывод: False print(a is b) # вывод: False print(a.__eq__(b)) # вывод: False print(a != b) # вывод: True print(a is not b) # вывод: True print(a.__ne__(b)) # вывод: True print(not a.__eq__(b)) # вывод: True print(a > b) # вывод: False print(a < b) # вывод: True print(a >= b) # вывод: False print(a <= b) # вывод: True print(a.__gt__(b)) # вывод: False print(a.__lt__(b)) # вывод: True print(a.__ge__(b)) # вывод: False print(a.__le__(b)) # вывод: True print(hash(a)) # вывод: 5 print(a.__hash__()) # вывод: 5 print(c.__hash__()) # вывод: 1745008793

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

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

    По умолчанию любой пользовательский класс уже реализует методы __eq__(), __ne__() и __hash__():

    class Comparable: def __init__(self, value1, value2): self.value1 = value1 self.value2 = value2 c1 = Comparable(4, 3) c2 = Comparable(7, 9) print(c1 == c1) # вывод: True print(c1 != c1) # вывод: False print(c1 == c2) # вывод: False print(c1 != c2) # вывод: True print(c1.__hash__()) # вывод (примерный): -2146408067 print(c2.__hash__()) # вывод (примерный): 1076316

    В этом случае стандартный метод __eq__() сравнивает экземпляры без учета их внутренних переменных, созданных в конструкторе __init__(). Впрочем, тоже самое касается и метода __hash__(), значения которого отличаются от вызова к вызову.

    Механика языка Python устроена таким образом, что переопределение метода __eq__() автоматически удаляет стандартный метод __hash__():

    class Comparable: def __init__(self, value1, value2): self.value1 = value1 self.value2 = value2 def __eq__(self, other): if isinstance(other, self.__class__): if self.value1 == other.value1: return self.value2 == other.value2 else: return False else: return False c1 = Comparable(4, 3) c2 = Comparable(7, 9) print(c1 == c1) # вывод: True print(c1 != c1) # вывод: False print(c1 == c2) # вывод: False print(c1 != c2) # вывод: True print(c1.__hash__()) # вывод: ОШИБКА (метод не определен) print(c2.__hash__()) # вывод: ОШИБКА (метод не определен)

    Поэтому переопределение метода __eq__() требует также переопределения метода __hash__() вместе с реализацией нового алгоритма хеширования:

    class Comparable: def __init__(self, value1, value2): self.value1 = value1 self.value2 = value2 def __eq__(self, other): if isinstance(other, self.__class__): if self.value1 == other.value1: return self.value2 == other.value2 else: return False else: return False def __ne__(self, other): return not self.__eq__(other) def __gt__(self, other): return self.value1 + self.value2 > other.value1 + other.value2 def __lt__(self, other): return not self.__gt__(other) def __ge__(self, other): return self.value1 + self.value2 >= other.value1 + other.value2 def __le__(self, other): return self.value1 + self.value2 <= other.value1 + other.value2 def __hash__(self): return hash((self.value1, self.value2)) # возвращает хэш кортежа из двух чисел c1 = Comparable(4, 3) c2 = Comparable(7, 9) print(c1 == c1) # вывод: True print(c1 != c1) # вывод: False print(c1 == c2) # вывод: False print(c1 != c2) # вывод: True print(c1 > c2) # вывод: False print(c1 < c2) # вывод: True print(c1 >= c2) # вывод: False print(c1 <= c2) # вывод: True print(c1.__hash__()) # вывод: -1441980059 print(c2.__hash__()) # вывод: -2113571365

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

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

    СинтаксисDunder-методРезультатОписание
    str(a)a.str()strСтрока
    bool(a)a.bool()boolБулев
    int(a)a.int()intЦелое число
    float(a)a.float()floatВещественное число
    bytes(a)a.bytes()bytesПоследовательность байтов
    complex(a)a.complex()complexКомплексное число

    Последовательность байтов

    По умолчанию каждый пользовательский класс может быть конвертирован лишь в несколько переменных встроенного типа данных:

    class Convertible: def __init__(self, value1, value2): self.value1 = value1 self.value2 = value2 someVariable = Convertible(4, 3) print(str(someVariable)) # вывод (примерный): <__main__.Convertible object at 0x1229620> print(bool(someVariable)) # вывод: True

    Однако с помощью переопределения соответствующих dunder-методов можно реализовать конвертацию пользовательского класса в любой встроенный тип данных:

    class Convertible: def __init__(self, value1, value2): self.value1 = value1 self.value2 = value2 def __str__(self): return str(self.value1) + str(self.value2) def __bool__(self): return self.value1 == self.value2 def __int__(self): return self.value1 + self.value2 def __float__(self): return float(self.value1) + float(self.value2) def __bytes__(self): return bytes(self.value1) + bytes(self.value2) def __complex__(self): return complex(self.value1) + complex(self.value2) someVariable = Convertible(4, 3) print(str(someVariable)) # вывод: 43 print(bool(someVariable)) # вывод: False print(int(someVariable)) # вывод: 7 print(float(someVariable)) # вывод: 7.0 print(bytes(someVariable)) # вывод: b'\x00\x00\x00\x00\x00\x00\x00' print(complex(someVariable)) # вывод: (7+0j)

    Таким образом, реализация dunder-методов конвертации позволяет объектам пользовательских классов вести себя как встроенный тип данных, тем самым расширяя свою полноту и универсальность.

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

    СинтаксисDunder-методОписание
    len(a)a.len()Длина
    iter(a) или for i in a:a.iter()Итератор
    a[b]a.getitem(b)Извлечение элемента
    a[b]a.missing(b)Извлечение несуществующего элемента словаря
    a[b] = ca.setitem(b, c)Установка элемента
    del a[b]a.delitem(b)Удаление элемента
    b in aa.contains(b)Проверка существования элемента
    reversed(a)a.reversed()Элементы в обратном порядке
    next(a)a.next()Извлечение следующего элемента

    Извлечение несуществующего элемента словаря

    Проверка существования элемента

    Элементы в обратном порядке

    Извлечение следующего элемента

    Несмотря на то, что внутренняя реализация итерируемого пользовательского класса может быть произвольной, управление его элементами будет выполняться через стандартный интерфейс контейнеров, а не какими-то специфическими методами:

    class Iterable: def __init__(self, e1, e2, e3, e4): self.e1 = e1 self.e2 = e2 self.e3 = e3 self.e4 = e4 self.index = 0 def __len__(self): len = 0 if self.e1: len += 1 if self.e2: len += 1 if self.e3: len += 1 if self.e4: len += 1 return len def __iter__(self): for i in range(0, self.__len__() + 1): if i == 0: yield self.e1 if i == 1: yield self.e2 if i == 2: yield self.e3 if i == 3: yield self.e4 def __getitem__(self, item): if item == 0: return self.e1 elif item == 1: return self.e2 elif item == 2: return self.e3 elif item == 3: return self.e4 else: raise Exception("Out of range") def __setitem__(self, item, value): if item == 0: self.e1 = value elif item == 1: self.e2 = value elif item == 2: self.e3 = value elif item == 3: self.e4 = value else: raise Exception("Out of range") def __delitem__(self, item): if item == 0: self.e1 = None elif item == 1: self.e2 = None elif item == 2: self.e3 = None elif item == 3: self.e4 = None else: raise Exception("Out of range") def __contains__(self, item): if self.e1 == item: return true elif self.e2 == item: return True elif self.e3 == item: return True elif self.e4 == item: return True else: return False def __reversed__(self): return Iterable(self.e4, self.e3, self.e2, self.e1) def __next__(self): if self.index >=4: self.index = 0 if self.index == 0: element = self.e1 if self.index == 1: element = self.e2 if self.index == 2: element = self.e3 if self.index == 3: element = self.e4 self.index += 1 return element someContainer = Iterable(-2, 54, 6, 13) print(someContainer.__len__()) # вывод: 4 print(someContainer[0]) # вывод: -2 print(someContainer[1]) # вывод: 54 print(someContainer[2]) # вывод: 6 print(someContainer[3]) # вывод: 13 someContainer[2] = 117 del someContainer[0] print(someContainer[2]) # вывод: 117 for element in someContainer: print(element) # вывод: None, 54, 117, 13 print(117 in someContainer) # вывод: True someContainerReversed = someContainer.__reversed__() for element in someContainerReversed: print(element) # вывод: 13, 117, 54, None print(someContainer.__next__()) # вывод: None print(someContainer.__next__()) # вывод: 54 print(someContainer.__next__()) # вывод: 117 print(someContainer.__next__()) # вывод: 13 print(someContainer.__next__()) # вывод: None

    Важно также понимать разницу между методами __iter__() и __next__(), которые позволяют выполнять итерацию объекта.

    Первый итерирует объект в моменте, а второй возвращает элемент с учетом некоего внутреннего индекса.

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

    Благодаря этому dunder-методу можно переопределить стандартное поведение dict в момент извлечения несуществующего элемента:

    class dict2(dict): def __missing__(self, item): return "Прости, но меня не существует..." someDictionary = dict2(item1=10, item2=20, item3=30) print(someDictionary["item1"]) # вывод: 10 print(someDictionary["item2"]) # вывод: 20 print(someDictionary["item3"]) # вывод: 30 print(someDictionary["item4"]) # вывод: Прости, но меня не существует...

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

    Чаще всего используются left-handed методы, выполняющие вычисления от имени правого операнда.

    СинтаксисDunder-методОписание
    a + ba.add(b)Сложение
    a - ba.sub(b)Вычитание
    a * ba.mul(b)Умножение
    a / ba.truediv(b)Деление
    a % ba.mod(b)Модуль
    a // ba.floordiv(b)Целочисленное деление
    a ** ba.pow(b)Степень

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

    СинтаксисDunder-методОписание
    a + ba.radd(b)Сложение
    a - ba.rsub(b)Вычитание
    a * ba.rmul(b)Умножение
    a / ba.rtruediv(b)Деление
    a % ba.rmod(b)Модуль
    a // ba.rfloordiv(b)Целочисленное деление
    a ** ba.rpow(b)Степень

    Также есть возможно переопределить арифметические операции, выполняемые на месте (in-place). В этом случае dunder-методы не возвращают новое значение, а изменяют переменные уже существующие левого операнда.

    СинтаксисDunder-методОписание
    a += ba.iadd(b)Сложение
    a -= ba.isub(b)Вычитание
    a *= ba.imul(b)Умножение
    a /= ba.itruediv(b)Деление
    a %= ba.imod(b)Модуль
    a //= ba.ifloordiv(b)Целочисленное деление
    a **= ba.ipow(b)Степень

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

    class Arithmetic: def __init__(self, value1, value2): self.value1 = value1 self.value2 = value2 def __add__(self, other): return Arithmetic(self.value1 + other.value1, self.value2 + other.value2) def __radd__(self, other): return Arithmetic(other + self.value1, other + self.value2) def __iadd__(self, other): self.value1 += other.value1 self.value2 += other.value2 return self def __sub__(self, other): return Arithmetic(self.value1 - other.value1, self.value2 - other.value2) def __rsub__(self, other): return Arithmetic(other - self.value1, other - self.value2) def __isub__(self, other): self.value1 -= other.value1 self.value2 -= other.value2 return self def __mul__(self, other): return Arithmetic(self.value1 * other.value1, self.value2 * other.value2) def __rmul__(self, other): return Arithmetic(other * self.value1, other * self.value2) def __imul__(self, other): self.value1 *= other.value1 self.value2 *= other.value2 return self def __truediv__(self, other): return Arithmetic(self.value1 / other.value1, self.value2 / other.value2) def __rtruediv__(self, other): return Arithmetic(other / self.value1, other / self.value2) def __itruediv__(self, other): self.value1 /= other.value1 self.value2 /= other.value2 return self def __mod__(self, other): return Arithmetic(self.value1 % other.value1, self.value2 % other.value2) def __rmod__(self, other): return Arithmetic(other % self.value1, other % self.value2) def __imod__(self, other): self.value1 %= other.value1 self.value2 %= other.value2 return self def __floordiv__(self, other): return Arithmetic(self.value1 // other.value1, self.value2 // other.value2) def __rfloordiv__(self, other): return Arithmetic(other // self.value1, other // self.value2) def __ifloordiv__(self, other): self.value1 //= other.value1 self.value2 //= other.value2 return self def __pow__(self, other): return Arithmetic(self.value1 ** other.value1, self.value2 ** other.value2) def __rpow__(self, other): return Arithmetic(other ** self.value1, other ** self.value2) def __ipow__(self, other): self.value1 **= other.value1 self.value2 **= other.value2 return self a1 = Arithmetic(4, 6) a2 = Arithmetic(10, 3) add = a1 + a2 sub = a1 - a2 mul = a1 * a2 truediv = a1 / a2 mod = a1 % a2 floordiv = a1 // a2 pow = a1 ** a2 radd = 50 + a1 rsub = 50 - a2 rmul = 50 * a1 rtruediv = 50 / a2 rmod = 50 % a1 rfloordiv = 50 // a2 rpow = 50 ** a2 a1 -= a2 a1 *= a2 a1 /= a2 a1 %= a2 a1 //= a2 a1 **= a2 print(add.value1, add.value2) # вывод: 14 9 print(sub.value1, sub.value2) # вывод: -6 3 print(mul.value1, mul.value2) # вывод: 40 18 print(truediv.value1, truediv.value2) # вывод: 0.4 2.0 print(mod.value1, mod.value2) # вывод: 4 0 print(floordiv.value1, floordiv.value2) # вывод: 0 2 print(pow.value1, pow.value2) # вывод: 1048576 216 print(radd.value1, radd.value2) # вывод: 54 56 print(rsub.value1, rsub.value2) # вывод: 40 47 print(rmul.value1, rmul.value2) # вывод: 200 300 print(rtruediv.value1, rtruediv.value2) # вывод: 5.0 16.666666666666668 print(rmod.value1, rmod.value2) # вывод: 2 2 print(rfloordiv.value1, rfloordiv.value2) # вывод: 5 16 print(rpow.value1, rpow.value2) # вывод: 97656250000000000 125000

    В реальности именно арифметические dunder-методы переопределяются чаще всего. Поэтому хорошей практикой будет одновременная реализация как left-handed, так и right-handed методов.

    Помимо обычных математических операций язык Python позволяет переопределить поведение пользовательского класса во время выполнения побитовых преобразований.

    СинтаксисDunder-методОписание
    a & ba.and(b)Битовое И
    aba.or(b)
    a ^ ba.xor(b)Битовое исключающее ИЛИ
    a >> ba.rshift(b)Битовый СДВИГ вправо
    a << ba.lshift(b)Битовый СДВИГ влево

    По аналогии с арифметическими операциями, побитовые преобразования можно выполнять от имени правого операнда.

    СинтаксисDunder-методОписание
    a & ba.rand(b)Битовое И
    aba.ror(b)
    a ^ ba.rxor(b)Битовое исключающее ИЛИ
    a >> ba.rrshift(b)Битовый СДВИГ вправо
    a << ba.rlshift(b)Битовый СДВИГ влево

    Разумеется, побитовые операции могут выполнены на месте, изменяя значения левого операнда, а не возвращая новый объект.****

    СинтаксисDunder-методОписание
    a &= ba.iand(b)Битовое И
    a= ba.ior(b)
    a ^= ba.ixor(b)Битовое исключающее ИЛИ
    a >>= ba.irshift(b)Битовый СДВИГ вправо
    a <<= ba.ilshift(b)Битовый СДВИГ влево

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

    class Bitable: def __init__(self, value1, value2): self.value1 = value1 self.value2 = value2 def __and__(self, other): return Bitable(self.value1 & other.value1, self.value2 & other.value2) def __rand__(self, other): return Bitable(other & self.value1, other & self.value2) def __iand__(self, other): self.value1 &= other.value1 self.value2 &= other.value2 return self def __or__(self, other): return Bitable(self.value1 | other.value1, self.value2 | other.value2) def __ror__(self, other): return Bitable(other | self.value1, other | self.value2) def __ior__(self, other): self.value1 |= other.value1 self.value2 |= other.value2 return self def __xor__(self, other): return Bitable(self.value1 ^ other.value1, self.value2 ^ other.value2) def __rxor__(self, other): return Bitable(other ^ self.value1, other ^ self.value2) def __ixor__(self, other): self.value1 |= other.value1 self.value2 |= other.value2 return self def __rshift__(self, other): return Bitable(self.value1 >> other.value1, self.value2 >> other.value2) def __rrshift__(self, other): return Bitable(other >> self.value1, other >> self.value2) def __irshift__(self, other): self.value1 >>= other.value1 self.value2 >>= other.value2 return self def __lshift__(self, other): return Bitable(self.value1 << other.value1, self.value2 << other.value2) def __rlshift__(self, other): return Bitable(other << self.value1, other << self.value2) def __ilshift__(self, other): self.value1 <<= other.value1 self.value2 <<= other.value2 return self b1 = Bitable(5, 3) b2 = Bitable(7, 2) resultAnd = b1 & b2 resultOr = b1 | b2 resultXor = b1 ^ b2 resultRshift = b1 >> b2 resultLshift = b1 << b2 resultRand = 50 & b1 resultRor = 50 | b2 resultRxor = 50 ^ b1 resultRrshift = 50 >> b2 resultRlshift = 50 << b1 b1 &= b2 b1 |= b2 b1 ^= b2 b1 >>= b2 b1 <<= b2 print(resultAnd.value1, resultAnd.value2) # вывод: 5 2 print(resultOr.value1, resultAnd.value2) # вывод: 7 2 print(resultXor.value1, resultAnd.value2) # вывод: 2 2 print(resultRshift.value1, resultAnd.value2) # вывод: 0 2 print(resultLshift.value1, resultAnd.value2) # вывод: 640 2 print(resultRand.value1, resultRand.value2) # вывод: 0 2 print(resultRor.value1, resultRor.value2) # вывод: 55 50 print(resultRxor.value1, resultRxor.value2) # вывод: 55 49 print(resultRrshift.value1, resultRrshift.value2) # вывод: 0 12 print(resultRlshift.value1, resultRlshift.value2) # вывод: 1600 400

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

    СинтаксисDunder-методОписание
    -aa.neg()Негация
    -aa.invert()Битовая инверсия
    +aa.pos()Битовая позитивизация

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

    В языке Python есть несколько dunder-методов, извлекающих дополнительную информацию об объекте.

    СинтаксисDunder-методОписание
    str(a)a.str()Значение
    repr(a)a.repr()Репрезентация

    Эти методы похожи, но различны:

    • Метод __str__() возвращает строку со значением переменной.

    Метод __str__() возвращает строку со значением переменной.

    • Метод __repr__() возвращает строку с кодом, репрезентирующим значение переменной. Его можно использовать для воссоздания оригинальной переменной в через функцию eval().

    Метод __repr__() возвращает строку с кодом, репрезентирующим значение переменной. Его можно использовать для воссоздания оригинальной переменной в через функцию eval().

    Соответственно, пользовательскому классу важно уметь предоставлять дополнительную информацию о себе:

    class Human: def __init__(self, name, age): self.name = name self.age = age def __str__(self): return str(self.name + " (" + str(self.age) + " лет)") def __repr__(self): return "Human(" + repr(self.name) + ", " + str(self.age) + ")" someHuman = Human("Иван", 35) someOtherHuman = eval(repr(someHuman)) print(str(someHuman)) # вывод: Иван (35 лет) print(repr(someHuman)) # вывод: Human('Иван', 35) print(str(someOtherHuman)) # вывод: Иван (35 лет) print(repr(someOtherHuman)) # вывод: Human('Иван', 35)

    Нужен сервер для практики? Арендуйте VPS/VDS в России — root-доступ, NVMe, DDoS-защита и поддержка 24/7.

    VPS для проекта

    VPS с root-доступом, NVMe и поддержкой 24/7 на Simple-Server.

    StarterVDS

    490₽

    в месяц

    1 ядро

    1 ГБ RAM

    20 ГБ NVMe

    • 1 IPv4
    • KVM
    • Root-доступ
    • Безлимитный трафик
    Заказать VPS
    Рекомендуем

    PerformanceVDS

    1190₽

    в месяц

    2 ядра

    4 ГБ RAM

    60 ГБ NVMe

    • 1 IPv4
    • KVM
    • Root-доступ
    • Базовая DDoS-защита
    Заказать VPS

    Нужна другая конфигурация или чистый VPS без панели?

    Все тарифы VPS

    Похожие статьи, которые могут быть вам интересны