Могу ли я изменить способ сравнения ключей в питоне? Я хочу использовать оператор 'is' вместо ==

Скажем, у меня есть два объекта одного класса: objA и objB. Их взаимосвязь такова:

(objA == objB)    #true
(objA is objB)    #false

Если я использую оба объекта в качестве ключей в питоне Python, они будут считаться одним и тем же ключом и перезаписывать друг друга. Есть ли способ переопределить компаратор dict, чтобы использовать сравнение is вместо ==, чтобы эти два объекта рассматривались как разные ключи в dict?

Может быть, я могу переопределить метод equals в классе или что-то еще? Чтобы быть более конкретным, я говорю о двух тегах из библиотеки BeautifulSoup4.

Вот более конкретный пример того, о чем я говорю:

from bs4 import BeautifulSoup

HTML_string = "<html><h1>some_header</h1><h1>some_header</h1></html>"

HTML_soup = BeautifulSoup(HTML_string, 'lxml')

first_h1 = HTML_soup.find_all('h1')[0]      #first_h1 = <h1>some_header</h1>
second_h1 = HTML_soup.find_all('h1')[1]     #second_h1 = <h1>some_header</h1>

print(first_h1 == second_h1)        # this prints True
print(first_h1 is second_h1)        # this prints False

my_dict = {}
my_dict[first_h1] = 1
my_dict[second_h1] = 1

print(len(my_dict))                 # my dict has only 1 entry!

# I want to have 2 entries in my_dict: one for key 'first_h1', one for key 'second_h1'.

Ответ 1

first_h1 и second_h1 являются Tag class экземплярами. Когда вы выполняете my_dict[first_h1] или my_dict[second_h1], для хеширования используются строковые представления тегов. Проблема в том, что оба этих экземпляра Tag имеют одинаковые представления строк:

<h1>some_header</h1>

Это связано с тем, что класс Tag имеет магический метод __hash__(), который определяется следующим образом:

def __hash__(self):
    return str(self).__hash__()

Одним из обходных решений может быть использование значений id() как хэширования, но существует проблема переопределения Tag классы внутри BeautifulSoup. Вы можете обойти эту проблему, создав свою собственную "обертку тегов":

class TagWrapper:
    def __init__(self, tag):
        self.tag = tag

    def __hash__(self):
        return id(self.tag)

    def __str__(self):
        return str(self.tag)

    def __repr__(self):
        return str(self.tag)

Затем вы сможете:

In [1]: from bs4 import BeautifulSoup
   ...: 

In [2]: class TagWrapper:
   ...:     def __init__(self, tag):
   ...:         self.tag = tag
   ...: 
   ...:     def __hash__(self):
   ...:         return id(self.tag)
   ...: 
   ...:     def __str__(self):
   ...:         return str(self.tag)
   ...: 
   ...:     def __repr__(self):
   ...:         return str(self.tag)
   ...:     

In [3]: HTML_string = "<html><h1>some_header</h1><h1>some_header</h1></html>"
   ...: 
   ...: HTML_soup = BeautifulSoup(HTML_string, 'lxml')
   ...: 

In [4]: first_h1 = HTML_soup.find_all('h1')[0]      #first_h1 = <h1>some_header</h1>
   ...: second_h1 = HTML_soup.find_all('h1')[1]     #second_h1 = <h1>some_header</h1>
   ...: 

In [5]: my_dict = {}
   ...: my_dict[TagWrapper(first_h1)] = 1
   ...: my_dict[TagWrapper(second_h1)] = 1
   ...: 
   ...: print(my_dict)
   ...: 
{<h1>some_header</h1>: 1, <h1>some_header</h1>: 1}

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

Вы также можете использовать monkey-patch bs4 с помощью возможностей самоанализа Python, например но это будет входить на довольно опасную территорию.

Ответ 2

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

def  __eq__(self,  obj) :
      return (self is obj)