Fixed #19634 -- Added proper __hash__ methods.

Classes overriding __eq__ need a __hash__ such that equal objects have
the same hash.

Thanks akaariai for the report and regebro for the patch.
This commit is contained in:
Aymeric Augustin 2013-02-25 22:53:08 +01:00
parent 0836670c5c
commit e76147a83a
7 changed files with 24 additions and 16 deletions

View File

@ -51,7 +51,8 @@ class BaseDatabaseWrapper(object):
def __ne__(self, other): def __ne__(self, other):
return not self == other return not self == other
__hash__ = object.__hash__ def __hash__(self):
return hash(self.alias)
def get_connection_params(self): def get_connection_params(self):
raise NotImplementedError raise NotImplementedError

View File

@ -135,7 +135,8 @@ class Field(object):
return self.creation_counter < other.creation_counter return self.creation_counter < other.creation_counter
return NotImplemented return NotImplemented
__hash__ = object.__hash__ def __hash__(self):
return hash(self.creation_counter)
def __deepcopy__(self, memodict): def __deepcopy__(self, memodict):
# We don't have to deepcopy very much here, since most things are not # We don't have to deepcopy very much here, since most things are not

View File

@ -30,7 +30,6 @@ class FieldFile(File):
return not self.__eq__(other) return not self.__eq__(other)
def __hash__(self): def __hash__(self):
# Required because we defined a custom __eq__.
return hash(self.name) return hash(self.name)
# The standard File contains most of the necessary properties, but # The standard File contains most of the necessary properties, but

View File

@ -152,7 +152,8 @@ class BoundMethodWeakref(object):
__repr__ = __str__ __repr__ = __str__
__hash__ = object.__hash__ def __hash__(self):
return hash(self.key)
def __bool__( self ): def __bool__( self ):
"""Whether we are still a valid reference""" """Whether we are still a valid reference"""

View File

@ -85,7 +85,8 @@ class Element(object):
return False return False
return True return True
__hash__ = object.__hash__ def __hash__(self):
return hash((self.name,) + tuple(a for a in self.attributes))
def __ne__(self, element): def __ne__(self, element):
return not self.__eq__(element) return not self.__eq__(element)

View File

@ -152,7 +152,8 @@ def lazy(func, *resultclasses):
other = other.__cast() other = other.__cast()
return self.__cast() < other return self.__cast() < other
__hash__ = object.__hash__ def __hash__(self):
return hash(self.__cast())
def __mod__(self, rhs): def __mod__(self, rhs):
if self._delegate_bytes and not six.PY3: if self._delegate_bytes and not six.PY3:

View File

@ -576,9 +576,11 @@ class ThreadTests(TestCase):
different for each thread. different for each thread.
Refs #17258. Refs #17258.
""" """
connections_set = set() # Map connections by id because connections with identical aliases
# have the same hash.
connections_dict = {}
connection.cursor() connection.cursor()
connections_set.add(connection) connections_dict[id(connection)] = connection
def runner(): def runner():
# Passing django.db.connection between threads doesn't work while # Passing django.db.connection between threads doesn't work while
# connections[DEFAULT_DB_ALIAS] does. # connections[DEFAULT_DB_ALIAS] does.
@ -588,19 +590,19 @@ class ThreadTests(TestCase):
# main thread. # main thread.
connection.allow_thread_sharing = True connection.allow_thread_sharing = True
connection.cursor() connection.cursor()
connections_set.add(connection) connections_dict[id(connection)] = connection
for x in range(2): for x in range(2):
t = threading.Thread(target=runner) t = threading.Thread(target=runner)
t.start() t.start()
t.join() t.join()
# Check that each created connection got different inner connection. # Check that each created connection got different inner connection.
self.assertEqual( self.assertEqual(
len(set([conn.connection for conn in connections_set])), len(set(conn.connection for conn in connections_dict.values())),
3) 3)
# Finish by closing the connections opened by the other threads (the # Finish by closing the connections opened by the other threads (the
# connection opened in the main thread will automatically be closed on # connection opened in the main thread will automatically be closed on
# teardown). # teardown).
for conn in connections_set: for conn in connections_dict.values() :
if conn is not connection: if conn is not connection:
conn.close() conn.close()
@ -609,25 +611,27 @@ class ThreadTests(TestCase):
Ensure that the connections are different for each thread. Ensure that the connections are different for each thread.
Refs #17258. Refs #17258.
""" """
connections_set = set() # Map connections by id because connections with identical aliases
# have the same hash.
connections_dict = {}
for conn in connections.all(): for conn in connections.all():
connections_set.add(conn) connections_dict[id(conn)] = conn
def runner(): def runner():
from django.db import connections from django.db import connections
for conn in connections.all(): for conn in connections.all():
# Allow thread sharing so the connection can be closed by the # Allow thread sharing so the connection can be closed by the
# main thread. # main thread.
conn.allow_thread_sharing = True conn.allow_thread_sharing = True
connections_set.add(conn) connections_dict[id(conn)] = conn
for x in range(2): for x in range(2):
t = threading.Thread(target=runner) t = threading.Thread(target=runner)
t.start() t.start()
t.join() t.join()
self.assertEqual(len(connections_set), 6) self.assertEqual(len(connections_dict), 6)
# Finish by closing the connections opened by the other threads (the # Finish by closing the connections opened by the other threads (the
# connection opened in the main thread will automatically be closed on # connection opened in the main thread will automatically be closed on
# teardown). # teardown).
for conn in connections_set: for conn in connections_dict.values():
if conn is not connection: if conn is not connection:
conn.close() conn.close()