Разное

Сравнение объектов c: Сравнение объектов в C#.NET / Хабр

is против == в Python — Сравнение объектов

Spread the love

Оригинальная статья: Olivera Popović — ‘is’ vs ‘==’ in Python — Object Comparison

В Python есть два очень похожих оператора для проверки равенства двух объектов. Эти два оператора is и ==.

Их обычно путаются друг с другом, потому что с простыми типами данных, такими как int и string (с которыми многие люди начинают изучать Python), они, кажется, делают то же самое:

x = 5
s = "example"
print("x == 5: " + str(x == 5))
print("x is 5: " + str(x is 5))
print("s == 'example': " + str(s == "example"))
print("s is 'example': " + str(s is "example"))

Запуск этого кода приведет к:

x == 5: True x is 5: True s == 'example': True s is 'example': True

Это показывает, что == и is возвращает одинаковое значение (True) в этих случаях. Однако, если вы попытались сделать это с более сложной структурой:

some_list = [1]
print("some_list == [1]: " + str(some_list == [1]))
print("some_list is [1]: " + str(some_list is [1]))

Это приведет к:

some_list == [1]: True
some_list is [1]: False

Здесь становится очевидным, что эти операторы не одинаковы.

Разница заключается в том, что is проверяет идентичность (объектов), а == проверяет равенство (значения).

Вот еще один пример, который может прояснить разницу между этими двумя операторами:

some_list1 = [1]
some_list2 = [1]
some_list3 = some_list1
print("some_list1 == some_list2: " + str(some_list1 == some_list2))
print("some_list1 is some_list2: " + str(some_list1 is some_list2))
print("some_list1 == some_list3: " + str(some_list1 == some_list3))
print("some_list1 is some_list3: " + str(some_list1 is some_list3))

Это приводит к:

some_list1 == some_list2: True some_list1 is some_list2: False some_list1 == some_list3: True some_list1 is some_list3: True

Как мы видим, some_list1 по значению равен some_list2 (они оба равны [1]]), но они не идентичны, то есть они не являются одним и тем же объектом, даже если они имеют одинаковые значения.

Тем не менее, some_list1 идентичен some_list3, так как они ссылаются на один и тот же объект в памяти.

Изменяемые и неизменяемые типы данных

Хотя эта часть проблемы теперь может быть понятна (когда мы использовали переменные), может возникнуть другой вопрос:

Почему is и == ведут себя одинаково с простыми значениями типа int и string (например,

5 и «example»), но не ведут себя одинаково со списками (такими как [1])?

В Python есть два типа данных: изменяемые и неизменяемые.

  • Изменяемые типы данных — это типы данных, которые вы можете «менять» со временем
  • Неизменяемые типы данных остаются неизменными (имеют одно и то же место в памяти, что is и проверяет) после их создания.

Изменяемые типы данных: list, dictionary, set и пользовательские классы.

Неизменяемые типы данных: int, float, decimal, bool, string, tuple и range.

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

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

Если вы используете строку «example» один раз, каждый раз, когда вы используете «example», это будет точно такой же объект. см. это примечание для дальнейшего разъяснения.

Далее чтобы более подробно рассмотреть эту концепцию изменчивости, используем функцию Python с именем id(), которая выводит уникальный идентификатор для каждого объекта, :

s = "example"
print("Id of s: " + str(id(s)))
print("Id of the String 'example': " + str(id("example")) + " (note that it's the same as the variable s)")
print("s is 'example': " + str(s is "example"))
print("Change s to something else, then back to 'example'.
") s = "something else" s = "example" print("Id of s: " + str(id(s))) print("s is 'example': " + str(s is "example")) print() list1 = [1] list2 = list1 print("Id of list1: " + str(id(list1))) print("Id of list2: " + str(id(list2))) print("Id of [1]: " + str(id([1])) + " (note that it's not the same as list1!)") print("list1 == list2: " + str(list1 == list2)) print("list1 is list2: " + str(list1 is list2)) print("Change list1 to something else, then back to the original ([1]) value.") list1 = [2] list1 = [1] print("Id of list1: " + str(id(list1))) print("list1 == list2: " + str(list1 == list2)) print("list1 is list2: " + str(list1 is list2))

Это выводит:

Id of s: 22531456 Id of the String 'example': 22531456 (note that it's the same as the variable s) s is 'example': True Change s to something else, then back to 'example'. Id of s: 22531456 s is 'example': True Id of list1: 22103504 Id of list2: 22103504 Id of [1]: 22104664 (note that it's not the same as list1!) list1 == list2: True list1 is list2: True Change list1 to something else, then back to the original ([1]) value. Id of list1: 22591368 list1 == list2: True list1 is list2: False

Мы можем видеть, что в первой части примера s вернулся к точно такому же «example» объекту, которому он был назначен в начале, даже если мы изменим значение s за это время.

Однако list

не возвращает тот же объект со значением [1], он создает новый объект, даже если он имеет то же значение, что и в первый раз [1].

Если вы запустите приведенный выше код, вы, вероятно, получите разные идентификаторы для объектов, но равенства будут одинаковыми.

Когда использовать is и == ?

Оператор is чаще всего используется, когда мы хотим сравнить объект с None, и обычно рекомендуется ограничить его использование этим конкретным сценарием, если вы действительно (и я действительно имею в виду) не хотите проверить, идентичны ли два объекта.

Кроме того, обычно is быстрее, чем оператор ==, потому что он просто проверяет целочисленное равенство адреса памяти.

Важное примечание: единственная ситуация, когда is работает точно так, как можно было бы ожидать, это с singleton классами или объектами (как например с None). Даже с неизменяемыми объектами бывают ситуации, когда is не работает должным образом.

Например, для больших объектов string, генерируемых некоторой кодовой логикой или большими целыми числами int, is может (и будет) вести себя непредсказуемо. Если вы не пройдете interning (проверку) (т.е. убедитесь, что существует только одна копия string / int), Поведение равенства со всеми различными неизменяемые объектами, которые вы планируете использовать с is будут непредсказуемыми.

Суть в следующем: используйте == в 99% случаев.

Если два объекта идентичны, они также равны, и обратное не всегда верно.

Переопределение операторов == и !=

Операторы != и is not не ведут себя так же, как их «положительные» коллеги (==/is). А именно, != возвращает True, если объекты не имеют одно и то же значение, в то время как is not не возвращает True, если объекты не хранятся в одном и том же адресе памяти.

Еще одно различие между этими двумя операторами заключается в том, что вы можете переопределить поведение == / != для пользовательского класса, в то время как вы не можете переопределить поведение is.

Если вы реализуете собственный метод __eq()__ в своем классе, вы можете изменить поведение операторов

== / !=:

class TestingEQ:
    def __init__(self, n):
        self. n = n
    # используя '==', чтобы проверить, оба ли числа
    # четные, или если оба числа нечетные
    def __eq__(self, other):
        if (self.n % 2 == 0 and other % 2 == 0):
            return True
        else:
            return False
print(5 == TestingEQ(1))
print(2 == TestingEQ(10))
print(1 != TestingEQ(2))

Это приводит к:

False
True
True

Заключение

Короче говоря, == / != проверяет равенство (по значению) и is / is not проверяет идентичность двух объектов, то есть проверяет адреса их памяти.

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

Была ли вам полезна эта статья?

[10 / 5]


Spread the love

8. Основы Kotlin. Простые классы — Страница 2 — Fandroid.info

Содержание

  1. Сравнение объектов класса на равенство
  2. Включение классов
  3. Переопределение equals для класса

Сравнение объектов класса на равенство

Как нам уже известно, на равенство == можно сравнивать не только числа, но и переменные более сложных типов, например, строки или списки. Часто необходимо уметь сравнить на равенство переменные с типом, определённым пользователем в виде класса. Например, в группе задач lesson8.task2 про шахматную доску и фигуры имеется класс Square, описывающий одну клетку шахматной доски. В наиболее простой форме он выглядел бы так:

class Square(val column: Int, val row: Int)

Проверим, что будет, если сравнить две одинаковых клетки first и second на равенство:

fun main(args: Array<String>) {
    val first = Square(3, 6)
    val second = Square(3, 6)
    println(first == second)
}

Если запустить эту главную функцию, мы увидим на консоли результат false.

Почему?

Всё дело в способе работы, принятом в JVM для любых объектов. Каждый раз, когда мы вызываем конструктор какого-либо класса, в динамической памяти JVM создаётся объект этого класса. Ссылка на него запоминается в стеке JVM (подробности будут в разделе 4.5). По умолчанию, при сравнении объектов на равенство сравниваются друг с другом ссылки, а не содержимое объектов.

Немного изменим определение класса Square, добавив впереди модификатор data. Такое определение обычно читается как «класс с данными».

data class Square(val column: Int, val row: Int)

Запустив главную функцию ещё раз, мы увидим результат true. При наличии модификатора data, для объектов класса работает другой способ сравнения на равенство: все свойства первого объекта сравниваются с соответствующими свойствами второго. Поскольку для обоих объектов column = 3 и row = 6, данные объекты равны.

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

fun main(args: Array<String>) {
    val first = Square(3, 6)
    println(first)
}

Эта функция выведет на консоль Square(x=3, y=6). Попробуйте теперь убрать модификатор data в определении класса и посмотрите, как изменится вывод. Заметим, что строковое представление используется не только при выводе на консоль, но и в отладчике.

Каким же образом осуществляется переопределение способа сравнения объектов и способа их представления в виде строки? Для этой цели в Java придуманы две специальные функции. Первая из них называется equals, она имеет объект-получатель, принимает ещё один объект как параметр и выдаёт результат true, если эти два объекта равны. Чуть ниже приведён пример переопределения equals для класса Segment.

Вторая функция называется toString. Она также имеет объект-получатель, но не имеет параметров. Её результат — это строковое представление объекта. Например:

class Square(val column: Int, val row: Int) {
    override fun toString() = "$row - $column"
}

Запустив главную функцию выше, мы увидим на консоли строку 6 - 3. Обратите внимание на модификатор overrideперед определением toString(). Он указывает на тот факт, что данная функция переопределяет строковое представление по умолчанию. Подробнее об этом опять-таки в разделе 9.

О других возможностях классов с данными можно прочитать здесь: https://kotlinlang.org/docs/reference/data-classes.html.

Включение классов

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

data class Triangle(val a: Point, val b: Point, val c: Point) {
    // ...
}
data class Segment(val begin: Point, val end: Point) {
    // ...
}

Здесь треугольник (Triangle) имеет три свойства ab и c, каждое из которых, в свою очередь, имеет тип Point — точка. В таких случаях говорят, что треугольник включает три точки, состоит из трёх точек или описывается тремя точками. Отрезок (Segment) имеет два таких же свойства begin и end — то есть описывается своим началом и концом.

Точки, в свою очередь, описываются двумя вещественными координатами. Например:

fun main(args: Array<String>) {
    val t = Triangle(Point(0.0, 0.0), Point(3.0, 0.0), Point(0.0, 4.0))
    println(t.b.x) // 3.0
}

При вызове println мы прочитали свойство x свойстваb треугольника t. Для этого мы дважды использовали точку для обращения к свойству объекта.

Переопределение equals для класса

Рассмотрим пример переопределения equals для класса Segment. Дело в том, что для отрезка, вообще говоря, всё равно, в каком порядке в нём идут начало и конец, то есть отрезок AB равен отрезку BA. Применение способа сравнения на равенство, действующего для классов с данными по умолчанию, даст нам другой результат: AB не равно BA.

data class Segment(val begin: Point, val end: Point) {

    override fun equals(other: Any?) =
            other is Segment && ((begin == other.begin && end == other.end) ||
                                 (begin == other.end && end == other.begin))
}

Модификатор override перед определением equals говорит о том, что мы хотим изменить уже имеющийся метод сравнения на равенство. Единственный параметр other данного метода обязан иметь тип Any?, то есть «любой, в том числе null». В Котлине действует правило: абсолютно любой тип является разновидностью Any?, то есть значение любой переменной или константы можно использовать как значение типа Any?. Это обеспечивает возможность сравнения на равенство чего угодно с чем угодно.

Результат equals имеет тип Boolean. В первую очередь, мы должны проверить, что переданный нам аргумент — тоже отрезок: other is Segment. Ключевое слово is в Котлине служит для определения принадлежности значения к заданному типу. Аналогично !is делает проверку на не принадлежность.

Если аргумент — отрезок, мы сравниваем точки двух имеющихся отрезков на равенство, с точностью до их перестановки. Если же аргумент — не отрезок, то логическое И в любом случае даст результат false. Обратите внимание, что справа от && мы вправе использовать other как отрезок (например, используя его begin и end), поскольку проверка этого факта была уже сделана.

Страницы: 1 2 3

Сравнение и наследование — Хилке Розема

Опубликовано 10 мая 2021 г.

Краткое изложение курса на datacamp.com:

При сравнении двух объектов пользовательского класса с использованием == Python по умолчанию сравнивает только ссылки на объекты, а не данные, содержащиеся в объектах. Чтобы переопределить это поведение, в классе можно реализовать специальный метод __eq__() , который принимает два аргумента — сравниваемые объекты — и возвращает True или 9. 0007 Ложь . Этот метод будет неявно вызываться при сравнении двух объектов.

Класс BankAccount из предыдущей главы доступен на панели сценариев. У него есть один атрибут balance и метод remove() . Два банковских счета с одинаковым балансом не обязательно являются одним и тем же счетом, но банковский счет обычно имеет номер счета , и два счета с одинаковым номером счета следует считать одним и тем же.

Попробуйте выбрать код в строках 1-7 и нажать кнопку «Выполнить код». Затем попробуйте создать в консоли несколько объектов BankAccount и сравнить их.

  • Измените метод __init__() , чтобы принять новый параметр — число — и инициализировать новый атрибут число .
  • Определите метод  __eq__()  , возвращающий True  если атрибут number двух объектов равен.
  • Проверьте операторы печати и вывод в консоли.
  • Метод __eq__() должен принимать два аргумента, обычно называемых self и other .
  • При добавлении параметров в __init__() помните, что параметры без значений по умолчанию должны располагаться перед параметрами со значениями по умолчанию.
 класс BankAccount:
   # MODIFY для инициализации числового атрибута
    def __init__(я, число, баланс=0):
        самобаланс = баланс
        self.number = число
      
    деф снять(я, сумма):
        self.balance -= количество
    
    # Определите __eq__, который возвращает True, если числовые атрибуты равны
    def __eq__(я, другой):
        вернуть self.number == other.number
# Создавайте учетные записи и сравнивайте их
acct1 = Банковский счет (123, 1000)
acct2 = Банковский счет (123, 1000)
acct3 = Банковский счет (456, 1000)
печать (аккт1 == аккт2)
печать (аккт1 == аккт3) 
 Вывод py>:Истинный
False

Обратите внимание,что ваш метод сравнивает только номера счетов,но не остатки.Что произойдет,если два счета имеют одинаковый номер счета,но разные балансы?Код,который вы написали,будет рассматривать эти учетные записи как равные,но,возможно,было бы лучше выдать ошибку  exception ,информируя пользователя о том,что что-то не так.В конце главы вы узнаете,как определить свои собственные классы исключений для создания таких пользовательских ошибок.

В предыдущем упражнении вы определили классBankAccountс атрибутомномер,который использовался для сравнения.Но если бы вы сравнили объектBankAccountс объектом другого класса,который также имеет атрибутnumber,вы могли бы получить неожиданные результаты.

Например,рассмотрим два класса

class Phone:def __init__(self,number):self.number=number def __eq__(self,other):return self.number==\ other.number pn=Телефон(873555333)class BankAccount:def __init__(self,number):self.number=number def __eq__(self,other):return self.number==\ other.number acct=BankAccount(873555333)

Выполнениеacct==pnвернетTrue,даже если мы сравниваем номер телефона с номером банковского счета.

Рекомендуется проверять класс объектов,переданных методу__eq__(),чтобы убедиться,что сравнение имеет смысл.

Определены классыPhoneиBankAccount.Попробуйте запустить код как есть,используя кнопку «Выполнить код»,и проверьте вывод.

  • Измените определениеBankAccount,чтобы оно возвращало толькоTrue,если атрибутnumberодин и тот жеиtype()обоих переданных ему объектов.

Запустите код и снова проверьте вывод

класс BankAccount:def __init__(я,число,баланс=0):self.number,self.balance=число,баланс
деф снять(я,сумма):self.balance-=количество
#ИЗМЕНИТЬ,чтобы добавить проверку для type()
def __eq__(я,другой):возврат(self.number==other.number)
счет=банковский счет(873555333)
pn=Телефон(873555333)
print(acct==pn)
ОператорМетод
==__eq__()
!=__ne__()
>=__ge__()
<=__le__()
>__gt__()
<__lt__()
  • __hash__()для использования объектов в качестве ключей словаря и в наборах
класс BankAccount:def __init__(я,число,баланс=0):self.number,self.balance=число,баланс
деф снять(я,сумма):self.balance-=количество
#ИЗМЕНИТЬ,чтобы добавить проверку для type()
def __eq__(я,другой):return((self.number==other.number)&(type(self)==type(other)))
счет=банковский счет(873555333)
pn=Телефон(873555333)
print(acct==pn)
вывод:Ложь

Теперь только сравнение объектов одного классаBankAccountможет вернутьTrue.Другой способ убедиться,что объект имеет тот же тип,что и вы,— использовать функцию isinstance(obj,Class) .Это может быть полезно при обработке наследования,поскольку Python считает объект экземпляром как родительского,так и дочернего класса.

Попробуйте запуститьpn==acct в консоли(с обратным порядком равенства).

Что это говорит вам о__eq__() метод?

Что происходит,когда объект сравнивается с объектом дочернего класса?Рассмотрим следующие два класса:

class Parent:def __eq__(я,другой):print("Вызван родительский __eq__()")
вернуть Истина
класс Ребенок(Родитель):def __eq__(я,другой):print("Вызван дочерний __eq__()")
вернуть Истина

КлассChildнаследуется от классаParent,и оба реализуют__eq__() метод,включающий диагностическую распечатку.

[an error occurred while processing the directive]

Добавить комментарий

Ваш адрес email не будет опубликован.Обязательные поля помечены*

[an error occurred while processing the directive]
[an error occurred while processing the directive]