[Jython-checkins] jython: Generalize adding special slot descriptors (__dict__, __weakref__)

jim.baker jython-checkins at python.org
Tue Mar 10 18:10:39 CET 2015


https://hg.python.org/jython/rev/4d28c47997de
changeset:   7607:4d28c47997de
user:        Jim Baker <jim.baker at rackspace.com>
date:        Tue Mar 10 11:10:34 2015 -0600
summary:
  Generalize adding special slot descriptors (__dict__, __weakref__)

Jython used to be too strict, and would throw TypeError if the special
slot descriptors of __dict__ and __weakref__ were used in a
subclass. With this change, CPython is stricter than Jython (see tests
in test_slots_jy for specifics). However Jython's underlying object
model is based on Java, and that give us more flexibility. In
particular, it's now possible to add a __dict__ slot descriptor to a
subclass of Java classes, such as java.util.HashMap.

Fixes http://bugs.jython.org/issue2272 and adds support for Werkzeug

files:
  Lib/test/test_slots_jy.py       |  98 ++++++++++++++++++++-
  src/org/python/core/PyType.java |  34 ++++--
  2 files changed, 120 insertions(+), 12 deletions(-)


diff --git a/Lib/test/test_slots_jy.py b/Lib/test/test_slots_jy.py
--- a/Lib/test/test_slots_jy.py
+++ b/Lib/test/test_slots_jy.py
@@ -3,6 +3,7 @@
 Made for Jython.
 """
 from test import test_support
+from java.util import HashMap
 import unittest
 
 # The strict tests fail on PyPy (but work on CPython and Jython).
@@ -134,10 +135,105 @@
         self.assert_(hasattr(Foo, '__weakref__'))
 
 
+class SpecialSlotsBaseTestCase(unittest.TestCase):
+    
+    # Tests for http://bugs.jython.org/issue2272, including support for
+    # werkzeug.local.LocalProxy
+    
+    def make_class(self, base, slot):
+        class C(base):
+            __slots__ = (slot)
+            if slot == "__dict__":
+                @property
+                def __dict__(self):
+                    return {"x": 42, "y": 47}
+                def __getattr__(self, name):
+                    try:
+                        return self.__dict__[name]
+                    except KeyError:
+                        raise AttributeError("%r object has no attribute %r" % (
+                            self.__class__.__name__, name))
+        return C
+
+    def test_dict_slot(self):
+        C = self.make_class(object, "__dict__")
+        c = C()
+        self.assertIn("__dict__", dir(c))
+        self.assertIn("x", dir(c))
+        self.assertIn("y", dir(c))
+        self.assertEqual(c.__dict__.get("x"), 42)
+        self.assertEqual(c.x, 42)
+        self.assertEqual(c.y, 47)
+        with self.assertRaisesRegexp(AttributeError, r"'C' object has no attribute 'z'"):
+            c.z
+
+    def test_dict_slot_str(self):
+        # Unlike CPython, Jython does not arbitrarily limit adding
+        # __dict__ slot to str and other types that are not object
+        C = self.make_class(str, "__dict__")
+        c = C("abc123")
+        self.assertTrue(c.startswith("abc"))
+        self.assertIn("__dict__", dir(c))
+        self.assertIn("x", dir(c))
+        self.assertIn("y", dir(c))
+        self.assertEqual(c.__dict__.get("x"), 42)
+        self.assertEqual(c.x, 42)
+        self.assertEqual(c.y, 47)
+        with self.assertRaisesRegexp(AttributeError, r"'C' object has no attribute 'z'"):
+            c.z
+
+    def test_dict_slot_subclass(self):
+        # Unlike CPython, Jython does not arbitrarily limit adding __dict__ slot to subtypes
+        class B(object):
+            @property
+            def w(self):
+                return 23
+        C = self.make_class(B, "__dict__")
+        c = C()
+        self.assertIn("__dict__", dir(c))
+        self.assertIn("x", dir(c))
+        self.assertIn("y", dir(c))
+        self.assertEqual(c.__dict__.get("x"), 42)
+        self.assertEqual(c.x, 42)
+        self.assertEqual(c.y, 47)
+        with self.assertRaisesRegexp(AttributeError, r"'C' object has no attribute 'z'"):
+            c.z
+        self.assertEqual(c.w, 23)
+
+    def test_dict_slot_subclass_java_hashmap(self):
+        C = self.make_class(HashMap, "__dict__")
+        # has everything in a HashMap, including Python semantic equivalence
+        c = C({"a": 1, "b": 2})
+        self.assertTrue(c.containsKey("a"))
+        self.assertEqual(sorted(c.iteritems()), [("a", 1), ("b", 2)])
+        # but also has a __dict__ slot for further interesting ;) possibilities
+        self.assertIn("__dict__", dir(c))
+        self.assertIn("x", dir(c))
+        self.assertIn("y", dir(c))
+        self.assertEqual(c.__dict__.get("x"), 42)
+        self.assertEqual(c.x, 42)
+        self.assertEqual(c.y, 47)
+        with self.assertRaisesRegexp(AttributeError, r"'C' object has no attribute 'z'"):
+            c.z
+
+    def test_weakref_slot(self):
+        self.assertNotIn("__weakref__", dir(object()))
+        self.assertIn("__weakref__", dir(self.make_class(object, "__weakref__")()))
+        class B(object):
+            pass
+        self.assertIn("__weakref__", dir(B()))
+        self.assertIn("__weakref__", dir(self.make_class(B, "__weakref__")()))
+        self.assertNotIn("__weakref__", dir("abc"))
+        self.assertIn("__weakref__", dir(self.make_class(str, "__weakref__")()))
+        self.assertNotIn("__weakref__", dir(HashMap()))
+        self.assertIn("__weakref__", dir(self.make_class(HashMap, "__weakref__")()))
+
+
 def test_main():
     test_support.run_unittest(SlottedTestCase,
                               SlottedWithDictTestCase,
-                              SlottedWithWeakrefTestCase)
+                              SlottedWithWeakrefTestCase,
+                              SpecialSlotsBaseTestCase)
 
 
 if __name__ == '__main__':
diff --git a/src/org/python/core/PyType.java b/src/org/python/core/PyType.java
--- a/src/org/python/core/PyType.java
+++ b/src/org/python/core/PyType.java
@@ -102,7 +102,7 @@
     /** Mapping of Java classes to their PyTypes. */
     private static Map<Class<?>, PyType> class_to_type;
     private static Set<PyType> exposedTypes;
-    
+
     /** Mapping of Java classes to their TypeBuilders. */
     private static Map<Class<?>, TypeBuilder> classToBuilder;
 
@@ -255,19 +255,31 @@
 
                 if (slotName.equals("__dict__")) {
                     if (!mayAddDict || wantDict) {
-                        throw Py.TypeError("__dict__ slot disallowed: we already got one");
+                        // CPython is stricter here, but this seems arbitrary. To reproduce CPython
+                        // behavior
+                        // if (base != PyObject.TYPE) {
+                        //     throw Py.TypeError("__dict__ slot disallowed: we already got one");
+                        // }
+                    } else {
+                        wantDict = true;
+                        continue;
                     }
-                    wantDict = true;
                 } else if (slotName.equals("__weakref__")) {
-                    if (!mayAddWeak || wantWeak) {
-                        throw Py.TypeError("__weakref__ slot disallowed: we already got one");
+                    if ((!mayAddWeak || wantWeak) && base != PyObject.TYPE) {
+                        // CPython is stricter here, but this seems arbitrary. To reproduce CPython
+                        // behavior
+                        // if (base != PyObject.TYPE) {
+                        //     throw Py.TypeError("__weakref__ slot disallowed: we already got one");
+                        // }
+                    } else {
+                        wantWeak = true;
+                        continue;
                     }
-                    wantWeak = true;
-                } else {
-                    slotName = mangleName(name, slotName);
-                    if (dict.__finditem__(slotName) == null) {
-                        dict.__setitem__(slotName, new PySlot(this, slotName, numSlots++));
-                    }
+                }
+
+                slotName = mangleName(name, slotName);
+                if (dict.__finditem__(slotName) == null) {
+                    dict.__setitem__(slotName, new PySlot(this, slotName, numSlots++));
                 }
             }
 

-- 
Repository URL: https://hg.python.org/jython


More information about the Jython-checkins mailing list