Python: неочевидное в очевидном
Изучение любого языка — очень долгий процесс, в ходе которого могут возникать ситуации, когда очевидные с виду вещи ведут себя странно. Даже спустя много лет изучения языка не все и не всегда могут с уверенностью сказать “да, я знаю этот на 100%, несите следующий”.
Python — один из самых популярных языков программирования на сегодняшний день, но и он имеет ряд своих нюансов, которые на протяжении многих лет изменялись, оптимизировались и теперь ведут себя немного не так, как это может показаться, глядя на строчки незамысловатого кода.
Немного строк
Метод is для сравнения строк
Рассмотрим несколько примеров, в которых работа со строками может быть не такой гладкой. Для начала необходимо создать файл (пусть будет test.py), и в нем реализовать функцию для тестирования работы строк:
После запуска кода вывод будет таким:
В примере 1 вроде всё логично, есть 2 строки, мы проверяем, является ли одна строка другой при помощи метода is (а именно, ссылаются ли две переменные на одну строку). Так как значения обеих строки равны, то и они (по идее) должны быть равны. Но в примере 2 видно, что сравниваются 2 одинаковые строки (ведь “hell” + “o” даст в итоге “hello”), но по итогу is вернул False.
Python оптимизирует работу со строками, используя метод интернирования, то есть для некоторых неизменяемых объектов python хранит только 1 экземпляр в памяти, как следствие, если в 2х переменных хранятся одинаковые строки, то они будут ссылаться на одну ячейку в памяти. Но это верно лишь отчасти.

При запуске программы интернирование происходит до момента её выполнения, именно поэтому в случае примера 2 строка, полученная при помощи конкатенации “hell” и “o” не была интернирована. Как следствие, во время выполнения программы строка “hello” (переменной a) и строка “hello” (полученная при помощи c + “o”) не будут ссылаться на один объект в памяти. Работу python можно проверить при помощи:
Как видно, в строке 0 и 4 в переменные a и b ссылаются на константные значения “hello”, а переменная d (в строке 26) ссылается на значение hell, после чего в 38 строке идет сложение значений из переменной d и строки “o”, и данное значение не берется из памяти (как в случае с переменными a и b), а получается новая строка с новым id.
Если же рассмотреть пример с вводом данных с клавиатуры (или из любого другого источника), то можно заметить следующий результат:
Все ASCII символы (ASCII строки длины 1 и 0) содержатся в python в единственном экземпляре изначально, поэтому вводя строку длиной 1 (или 0) можно быть уверенным, что это один тот же элемент в памяти, но для каждой не ASCII строки (либо строки с длиной больше 1) выделяется новое место в памяти в ходе выполнения программы.
Такая особенность хранения строк в Python позволяет экономить память, однако надо быть аккуратными при работе со строками, а именно, при сравнении строк при помощи метода is.
Конкатенация строк
Еще один пример. Конкатенирование строк в Python:
F-string было введено в Python 3.6. Хотя существует несколько способов объединять строки, многие не задаются вопросов “зачем столько всякого?”, считая, что это всего лишь украшения языка для удобства пользования (ведь запись f”
Проведя dis.dis() для данного метода, можно заметить, что разные методы конкатенации вызывают разные состояния, которые в свою очередь различаются по реализации.
Для “+” выполняются следующие действия:
Как видно, каждый раз вызывается состояние BINARY_ADD (см. подробнее оф. гитхаб cpython: строка case TARGET(BINARY_ADD)). Для метода “”.join():
Сравнение объектов
Рассмотрим небольшой пример сравнения двух экземпляров классов:
Всё замечательно. Теперь посмотрим на другой пример:
Что-то пошло не так. Вроде так же создаются 2 экземпляра класса, но при этом вывод показывает, что hash и id этих экземпляров одинаковы, но is показывает, что это получаются разные объекты. Рассмотрим еще пример:
Всё опять встало на свои места. Дополним немного my_class во всех примерах:
Как это работает? Когда создается экземпляр класса, он получает свой уникальный id, но как только у него вызывается метод __del__, тот самый id уже перестает принадлежать этому объекту, и точно такой же id может быть присвоен другому объекту, например:
Когда python создает объект класса, как в случае 1 (в методе test_class), то на этот объект ссылается переменная, следовательно, этот объект хранится в памяти до тех пор, пока на него что-то ссылается. В примере 3 аналогично, a := my_class() и b := my_class() — “моржовый” оператор := создает переменную a, которая во время выполнения функции print живёт и ссылается на объект, а переменная b ссылается уже на другой объект, так как у объекта в a:=my_class() не был вызван метод __del__.
Пример 2 самый интересный. Когда вызывается id(my_class()), то происходит следующее: создается экземпляр класса my_class, этот только что созданный объект передается функции id, которая берёт id объекта, затем объект уничтожается и вызывается часть id(my_class()), стоящая справа от ==. Опять создается my_class (который, как мы определили чуть выше, будет иметь такой же id, как был у предыдущего my_class(), который уже не существует), и правая функция id получается значение id этого объекта. Так получается, что id слева и справа от == получают идентификаторы своих объектов в то время, когда физически существует только один из этих объектов, следовательно id, полученное слева будет таким же, как id полученное справа.
Назревает вопрос: почему метод is в примере 2 выдает False, id объектов должны же быть одинаковы, значит, они ссылаются на один объект? Чуть выше был изменён класс my_class. Для наглядности можно запустить пример 2 еще раз. Получим такой результат:
Когда выполнялся print с hash или id, видно, что по отдельности сначала вызывается конструктор, потом деструктор одного объекта, а потом конструктор и деструктор другого объекта, именно после того, как функции hash или id завершали свои действия, вызывался метод __del__ и создавался новый объект. В примере с is видно, что сначала создались оба экземпляра класса, а только потом вызывался метод __del__ у каждого из них. Это дает понять, что метод is выполнялся таким образом, что сначала создавались оба объекта my_class(), потом они сравнивались, а только лишь после выполнения is они оба удалялись (т.е. два экземпляра my_class жили в одно время и физически не могли иметь 2 одинаковых id).
И еще немного
Изменение данных внутри кортежа
Создадим кортеж, который содержит в себе список, и попробуем расширить этот список, добавив новые элементы:
Очень забавная ситуация, кортеж выкинул ошибку, однако список все равно был расширен. Аналогично будет себя вести пример со словарями ( оператор |= — был добавлен в версии 3.9).
Создание таблицы
Допустим, необходимо создать таблицу (матрицу, двумерный массив) с данными и поменять в нём первый элемент:
Поменялся не только самый первый элемент таблицы, но и первые элементы в каждой строке. Если использовать инициализацию таблицы путем умножения одного списка, то каждая строка таблицы будет содержать один и тот же список (ссылаться на один список row).

Убедиться в этом можно, выполнив следующий код:
Метод all
Немного о методе all:
Почему так? all([]) — по определению выдаст True. Ситуация с all([[]]) объясняется тем, что для каждый элемент во внешнем списке приводится к булевскому значению (как известно, к False приводятся: ноль, пустая строка, пустой список и др.), поэтому внешний список содержит [], который интерпретируется как False. В all([[[]]]), внешний список содержит список, в котором есть список, а список с 1 элементов уже интерпретируется, как True, то есть all не проверяет вложенность списков.
Вывод
Все эти примеры довольно очевидны для опытного программиста, но иногда могут вводить в ступор, особенно, если приходится читать чужой код и пытаться найти в нём ошибку. Такого рода ошибки порождают массу проблем, если плохо разбираться в нюансах работы языка. Поэтому иногда стоит читать и изучать чужой код, изучать статьи по языку и читать документацию к работе методов, чтобы видеть и понимать, какие сюрпризы может преподнести язык. И не стоит забывать, используя два разных языка и описав в них одинаковые конструкции, они могут вести себя совершенно по-разному, потому что внутренняя реализация у каждого языка своя.
Compare object instances for equality by their attributes
I have a class MyClass , which contains two member variables foo and bar :
I have two instances of this class, each of which has identical values for foo and bar :
However, when I compare them for equality, Python returns False :
How can I make python consider these two objects equal?
![]()
16 Answers 16
You should implement the method __eq__ :
Note that implementing __eq__ will automatically make instances of your class unhashable, which means they can’t be stored in sets and dicts. If you’re not modelling an immutable type (i.e. if the attributes foo and bar may change the value within the lifetime of your object), then it’s recommended to just leave your instances as unhashable.
If you are modelling an immutable type, you should also implement the data model hook __hash__ :
A general solution, like the idea of looping through __dict__ and comparing values, is not advisable — it can never be truly general because the __dict__ may have uncomparable or unhashable types contained within.
N.B.: be aware that before Python 3, you may need to use __cmp__ instead of __eq__ . Python 2 users may also want to implement __ne__ , since a sensible default behaviour for inequality (i.e. inverting the equality result) will not be automatically created in Python 2.
You override the rich comparison operators in your object.
If you’re dealing with one or more classes that you can’t change from the inside, there are generic and simple ways to do this that also don’t depend on a diff-specific library:
Easiest, unsafe-for-very-complex-objects method
pickle is a very common serialization lib for Python objects, and will thus be able to serialize pretty much anything, really. In the above snippet, I’m comparing the str from serialized a with the one from b . Unlike the next method, this one has the advantage of also type checking custom classes.
The biggest hassle: due to specific ordering and [de/en]coding methods, pickle may not yield the same result for equal objects, especially when dealing with more complex ones (e.g. lists of nested custom-class instances) like you’ll frequently find in some third-party libs. For those cases, I’d recommend a different approach:
Thorough, safe-for-any-object method
You could write a recursive reflection that’ll give you serializable objects, and then compare results
Now it doesn’t matter what your objects are, deep equality is assured to work
The number of comparables doesn’t matter as well
My use case for this was checking deep equality among a diverse set of already trained Machine Learning models inside BDD tests. The models belonged to a diverse set of third-party libs. Certainly implementing __eq__ like other answers here suggest wasn’t an option for me.
Covering all the bases
You may be in a scenario where one or more of the custom classes being compared do not have a __dict__ implementation. That’s not common by any means, but it is the case of a subtype within sklearn’s Random Forest classifier: <type ‘sklearn.tree._tree.Tree’> . Treat these situations on a case by case basis — e.g. specifically, I decided to replace the content of the afflicted type with the content of a method that gives me representative information on the instance (in this case, the __getstate__ method). For such, the second-to-last row in base_typed became
Edit: for the sake of organization, I replaced the hideous oneliner above with return dict_from(obj) . Here, dict_from is a really generic reflection made to accommodate more obscure libs (I’m looking at you, Doc2Vec)
Do mind none of the above methods yield True for objects with the same key-value pairs in a differing order, as in
But if you want that you could use Python’s built-in sorted method beforehand anyway.
Как сравнить тип объекта в Python?
Кроме того, как насчет других типов? Например, я не мог реплицировать NoneType .
13 ответов
В вашем случае isinstance(«this is a string», str) вернет True .
Вы также можете прочитать следующее: http://www.canonical.org/
, но, имейте в виду: если он выглядит как утка, и если это звучит как утка, это утка.
EDIT: для типа «Нет» вы можете просто:
Во-первых, избегайте всех сравнений типов. Они очень, очень редко нужны. Иногда они помогают проверять типы параметров в функции — даже такой редкой. Неверные данные типа вызовут исключение и все, что вам когда-либо понадобится.
Все основные функции преобразования будут отображаться как равные функции типа.
Есть еще несколько случаев.
Нет, BTW, никогда не нуждается в такой проверке типов. None — единственный экземпляр NoneType. Объектом None является Singleton. Просто проверьте None
Кстати, не используйте вышеприведенное. Используйте обычные исключения и собственный естественный полиморфизм Python.
Для других типов проверьте модуль types:
P.S. Typechecking — плохая идея.
Вы всегда можете использовать трюк type(x) == type(y) , где y — это что-то с известным типом.
Часто есть лучшие способы сделать это, особенно с любым недавним python. Но если вы только хотите запомнить одну вещь, вы можете это вспомнить.
В этом случае лучшими способами будут:
Обратите внимание на последний случай: в python есть только один экземпляр NoneType , и это None . Вы увидите NoType много в исключениях ( TypeError: ‘NoneType’ object is unsubscriptable — происходит со мной все время..), но вам вряд ли когда-либо понадобится ссылаться на него в коде.
Наконец, как указывает fengshaun, проверка типов в python не всегда хорошая идея. Это более pythonic, чтобы просто использовать значение, как будто это тот тип, который вы ожидаете, и вылавливать (или допускать распространение) исключения, которые вытекают из него.
Это потому, что вам нужно написать
type принимает экземпляр и возвращает его тип. В этом случае вам нужно сравнить типы двух экземпляров.
Если вам нужно выполнить выборочную проверку, лучше проверить, поддерживается ли поддерживаемый интерфейс, чем тип.
Тип действительно не говорит вам о многом, кроме того, что вашему коду нужен экземпляр определенного типа, независимо от того, что у вас может быть другой экземпляр совершенно другого типа, который был бы прекрасно, потому что он реализует тот же интерфейс.
Например, предположим, что у вас есть этот код
Теперь предположим, что вы говорите: я хочу, чтобы этот код принимал только кортеж.
Это уменьшает повторное использование этой процедуры. Он не будет работать, если вы передадите список, строку или numpy.array. Что-то лучше было бы
но нет смысла в этом: параметр [0] вызовет исключение, если протокол не будет удовлетворен в любом случае. это, конечно, если вы не хотите предотвращать побочные эффекты или необходимость восстановления от вызовов, которые вы могли бы вызвать до терпит неудачу. (Глупый) пример, просто чтобы сделать точку:
в этом случае ваш код вызовет исключение перед запуском вызова system(). Без проверок интерфейса вы бы удалили файл, а затем подняли исключение.
Операторы сравнения в Python
Операторы сравнения в Python используются для сравнения двух объектов. Возвращаемый результат – логическое значение – True или False.
В Python есть 6 типов операторов сравнения:
| == | Возвращает True, если два операнда равны, в противном случае – False. | a == b |
| != | Возвращает True, если два операнда не равны, в противном случае – False. | a != b |
| > | Возвращает True, если левый операнд больше правого, в противном случае – False. | a > b |
| < | Возвращает True, если левый операнд меньше правого, в противном случае – False. | a < b |
| > = | Возвращает True, если левый операнд больше или равен правому операнду, в противном случае – False. | a > b |
| <= | Возвращает True, если левый операнд меньше или равен правому операнду, в противном случае – False. | a < b |
Пример
Давайте посмотрим на простой пример использования операторов сравнения с примитивным типом данных, например с целым числом.

Операторы сравнения со строкой
Строка – это объект в программировании на Python. Посмотрим, работают ли операторы сравнения со строками или нет.

Значит ли это, что операторы сравнения будут работать с любыми объектами Python?
Давайте проверим это, создав собственный класс.

Почему оператор equals и not-equals работал, а другие – нет?
Это потому, что «объект» является основой каждого класса в Python. И объект предоставляет реализацию функций, которые используются для операторов равенства и не равно.
Функции
Вот список функций, которые используются операторами сравнения. Поэтому, если вы хотите, чтобы они работали с настраиваемым объектом, вам необходимо предоставить для них реализацию.
| == | __eq __ (self, other) |
| != | __ne __ (self, other) |
| > | __gt __ (self, other) |
| < | __lt __ (self, other) |
| > = | __ge __ (self, other) |
| <= | __le __ (self, other) |
Перегрузка
Рассмотрим пример перегрузки операторов сравнения в настраиваемом объекте.
Резюме
Операторы сравнения в Python используются для сравнения двух объектов. Мы можем легко реализовать определенные функции, чтобы обеспечить поддержку этих операторов для наших пользовательских объектов.