Skip to content

Commit

Permalink
Implement python3 comparison protocol
Browse files Browse the repository at this point in the history
py3 does not handle `__cmp__`.

Note further that python3 implicitly adds `__hash__=None` to the class
members during class construction if there is an `__eq__` defined. The
result is that we see errors like this:

    class X:
        def __hash__(self):
            return hash(self.val)
        def __eq__(self, other):
            return self.val == other.val

    >>> hash(conn.modules.__main__.X())
    TypeError: unhashable instance

To fix this we need to either have `__hash__` AND the comparison methods
in `_local_netref_attrs`or neither of them.

Fixes #280, #293, #267
Closes #281
Supersedes #268
  • Loading branch information
coldfix committed Sep 6, 2018
1 parent ed4251d commit ab548e3
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 4 deletions.
16 changes: 14 additions & 2 deletions rpyc/core/netref.py
Expand Up @@ -21,6 +21,7 @@
'__init__', '__metaclass__', '__module__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__slots__', '__str__',
'__weakref__', '__dict__', '__members__', '__methods__', '__exit__',
'__eq__', '__ne__', '__lt__', '__gt__', '__le__', '__ge__',
]) | _deleted_netref_attrs
"""the set of attributes that are local to the netref object"""

Expand Down Expand Up @@ -172,7 +173,19 @@ def __dir__(self):
def __hash__(self):
return syncreq(self, consts.HANDLE_HASH)
def __cmp__(self, other):
return syncreq(self, consts.HANDLE_CMP, other)
return syncreq(self, consts.HANDLE_CMP, other, '__cmp__')
def __eq__(self, other):
return syncreq(self, consts.HANDLE_CMP, other, '__eq__')
def __ne__(self, other):
return syncreq(self, consts.HANDLE_CMP, other, '__ne__')
def __lt__(self, other):
return syncreq(self, consts.HANDLE_CMP, other, '__lt__')
def __gt__(self, other):
return syncreq(self, consts.HANDLE_CMP, other, '__gt__')
def __le__(self, other):
return syncreq(self, consts.HANDLE_CMP, other, '__le__')
def __ge__(self, other):
return syncreq(self, consts.HANDLE_CMP, other, '__ge__')
def __repr__(self):
return syncreq(self, consts.HANDLE_REPR)
def __str__(self):
Expand Down Expand Up @@ -278,4 +291,3 @@ def class_factory(clsname, modname, methods):
for cls in _builtin_types:
builtin_classes_cache[cls.__name__, cls.__module__] = class_factory(
cls.__name__, cls.__module__, inspect_methods(cls))

4 changes: 2 additions & 2 deletions rpyc/core/protocol.py
Expand Up @@ -577,11 +577,11 @@ def _handle_repr(self, obj):
return repr(obj)
def _handle_str(self, obj):
return str(obj)
def _handle_cmp(self, obj, other):
def _handle_cmp(self, obj, other, op='__cmp__'):
# cmp() might enter recursive resonance... yet another workaround
#return cmp(obj, other)
try:
return type(obj).__cmp__(obj, other)
return getattr(type(obj), op)(obj, other)
except (AttributeError, TypeError):
return NotImplemented
def _handle_hash(self, obj):
Expand Down
68 changes: 68 additions & 0 deletions tests/test_magic.py
@@ -0,0 +1,68 @@
import sys
import rpyc
import unittest

is_py3 = sys.version_info >= (3,)

class Meta(type):

def __hash__(self):
return 4321

Base = Meta('Base', (object,), {})

class Foo(Base):
def __hash__(self):
return 1234

class Bar(Foo):
pass

class Mux(Foo):
def __eq__(self, other):
return True


class TestContextManagers(unittest.TestCase):
def setUp(self):
self.conn = rpyc.classic.connect_thread()

def tearDown(self):
self.conn.close()

def test_hash_class(self):
hesh = self.conn.builtins.hash
mod = self.conn.modules.test_magic
self.assertEqual(hash(mod.Base), 4321)
self.assertEqual(hash(mod.Foo), 4321)
self.assertEqual(hash(mod.Bar), 4321)
self.assertEqual(hash(mod.Base().__class__), 4321)
self.assertEqual(hash(mod.Foo().__class__), 4321)
self.assertEqual(hash(mod.Bar().__class__), 4321)

basecl_ = mod.Foo().__class__.__mro__[1]
object_ = mod.Foo().__class__.__mro__[2]
self.assertEqual(hash(basecl_), hesh(basecl_))
self.assertEqual(hash(object_), hesh(object_))
self.assertEqual(hash(object_), hesh(self.conn.builtins.object))

def test_hash_obj(self):
hesh = self.conn.builtins.hash
mod = self.conn.modules.test_magic
obj = mod.Base()

self.assertNotEqual(hash(obj), 1234)
self.assertNotEqual(hash(obj), 4321)
self.assertEqual(hash(obj), hesh(obj))

self.assertEqual(hash(mod.Foo()), 1234)
self.assertEqual(hash(mod.Bar()), 1234)
if is_py3:
with self.assertRaises(TypeError):
hash(mod.Mux())
else:
self.assertEqual(hash(mod.Mux()), 1234)


if __name__ == "__main__":
unittest.main()

0 comments on commit ab548e3

Please sign in to comment.