On Sat, Dec 30, 2017 at 12:49 PM Branko Čibej <br...@apache.org> wrote:

> On 30.12.2017 16:11, Troy Curtis Jr wrote:
> >
> >
> > On Fri, Dec 29, 2017 at 11:46 PM Daniel Shahaf <d...@daniel.shahaf.name
> > <mailto:d...@daniel.shahaf.name>> wrote:
> >
> >     Good morning Troy,
> >
> >     troycurti...@apache.org <mailto:troycurti...@apache.org> wrote on
> >     Fri, Dec 22, 2017 at 03:52:59 -0000:
> >     > On branch swig-py3: Correctly manage swig objects with Python
> >     new style classes.
> >     >
> >     > * build/ac-macros/swig.m4
> >     >   (SVN_FIND_SWIG):
> >     >   Request that swig generate new style classes, even for Python
> >     2, to reduce
> >     >   differences with Python 3.
> >     >
> >     > Modified: subversion/branches/swig-py3/build/ac-macros/swig.m4
> >     > URL:
> >
> http://svn.apache.org/viewvc/subversion/branches/swig-py3/build/ac-macros/swig.m4?rev=1818995&r1=1818994&r2=1818995&view=diff
> >     >
> >
>  
> ==============================================================================
> >     > --- subversion/branches/swig-py3/build/ac-macros/swig.m4 (original)
> >     > +++ subversion/branches/swig-py3/build/ac-macros/swig.m4 Fri Dec
> >     22 03:52:59 2017
> >     > @@ -143,7 +143,7 @@ AC_DEFUN(SVN_FIND_SWIG,
> >     >          if test "$ac_cv_python_is_py3" = "yes"; then
> >     >             SWIG_PY_OPTS="-python -py3"
> >     >          else
> >     > -           SWIG_PY_OPTS="-python -classic"
> >     > +           SWIG_PY_OPTS="-python"
> >     >          fi
> >
> >     What are the compatibility implications of this change?  I
> >     couldn't find any
> >     documentation for the "-classic" option.
> >
> >     > * subversion/bindings/swig/include/proxy.py
> >     >   (__getattr__): Replace __getattr__ with __getattribute__ to
> >     correctly
> >     >   intercept attribute access, even when swig uses descriptors
> >     for new style
> >     >   classes (which are the only type available in Python 3).
> >
> >     Could you help me understand what's going on here and why it's
> >     correct?  I've
> >     read the documentation of object.__getattr__ and
> >     object.__getattribute__¹ but I
> >     don't follow how the fact that the class into which this code is
> >     included
> >     became a new-style class (rather than a classic class) brought on
> >     the need to
> >     migrate from __getattr__ to __getattribute__, nor why the order of
> >     precedence
> >     of lookup (first __dict__, then object.__getattribute__, then
> >     _swig_getattr) is
> >     correct.
> >
> >     I'm asking because I'm trying to review the branch (to +1 its
> >     merge) and this
> >     is one of the open questions I have.
> >
> >     ¹
> >
> https://docs.python.org/3/reference/datamodel.html#object.__getattr__
> >     et seq.
> >
> >
> > Sure thing.  This is the change that took me so long to find, and then
> > fix the right way, so I certainly understand the non-obviousness of
> > it.  Perhaps some portion of this explanation could go in an
> > appropriate place in the code for future reference?  Though much of it
> > only applies to the motivation for the change and probably wouldn't be
> > beneficial just given the final implementation.
> >
> > Giving swig the '-classic' option forces it to generate only classic
> > classes.  Without that flag it will produce code that works as either
> > an old style or new style class, dependent on a try/except block
> > around 'import object'.  In practice, this means that for any Python
> > past 2.2 (when new-style classes were introduced), the classes are in
> > the new style and inherit from 'object'.
>
> This all makes sense and seems nice on the surface, but I'm not sure we
> can just change the behaviour of the bindings from old-style to
> new-style classes in a Python 2.x build. There are enough subtle
> differences in behaviour between the two that existing scripts could
> break after an upgrade of the bindings.
>
> Python 3.x has only new-style (or rather, even-newer-style) classes and
> there's no backward-compatibility consideration, since our bindings
> currently don't work with Python3.
>
>
That is a reasonable concern.  I definitely preferred the cleaner single
implementation, but honestly the code necessary to continue to use classic
classes in python 2 is not large.  I've attached a working patch for
reference/discussion.  It is a bit more code and some conditional
definitions, but perhaps it is the more preferred course to take?

[[[
On branch swig-py3: Go back to using classic classes for Python 2 swig
bindings.

Add some additional clarifying comments for the reasons behind overriding
__getattr__ and __getattribute__.

* build/ac-macros/swig.m4
  (SVN_FIND_SWIG): Add the '-classic' flag to swig when python 2 is
detected.

* subversion/bindings/swig/include/proxy.py
   (_retrieve_swig_value): Factor out metadata retrieval from
__getattribute__ to a new function.
   (__getattribute__): Only define __getattribute__ for new style classes.
   (__getattr__): Add back implementation for classic classes.
]]]

Troy
Index: build/ac-macros/swig.m4
===================================================================
--- build/ac-macros/swig.m4	(revision 1819610)
+++ build/ac-macros/swig.m4	(working copy)
@@ -155,7 +155,7 @@
             if test "$ac_cv_python_is_py3" = "yes"; then
                SWIG_PY_OPTS="-python -py3"
             else
-               SWIG_PY_OPTS="-python"
+               SWIG_PY_OPTS="-python -classic"
             fi
 
             dnl Sun Forte adds an extra space before substituting APR_INT64_T_FMT
Index: subversion/bindings/swig/include/proxy.py
===================================================================
--- subversion/bindings/swig/include/proxy.py	(revision 1819610)
+++ subversion/bindings/swig/include/proxy.py	(working copy)
@@ -12,42 +12,68 @@
     if "_is_valid" in self.__dict__:
       assert self.__dict__["_is_valid"](), "Variable has already been deleted"
 
-  def __getattribute__(self, name):
-    """Manage access to all attributes of this object."""
+  def _retrieve_swig_value(self, name, value):
+    # If we got back a different object than we have cached, we need to copy
+    # all our metadata into it, so that it looks identical to the one
+    # originally set.
+    members = self.__dict__.get('_members')
+    if members is not None and name in members:
+      _copy_metadata_deep(value, members[name])
 
-    # Start by mimicing __getattr__ behavior: immediately return __dict__ or
-    # items directly present in __dict__
-    mydict = object.__getattribute__(self, '__dict__')
-    if name == "__dict__":
-      return mydict
+    # Verify that the new object is good
+    _assert_valid_deep(value)
 
-    if name in mydict:
-      return mydict[name]
+    return value
 
-    object.__getattribute__(self, 'assert_valid')()
+  # SWIG classes generated with -classic do not define this variable,
+  # so set it to 0 when it doesn't exist
+  try: _newclass
+  except NameError: _newclass = 0
 
-    try:
-      value = object.__getattribute__(self, name)
-    except AttributeError:
-      value = _swig_getattr(self,
-                            object.__getattribute__(self, '__class__'),
-                            name)
+  # Attribute access must be intercepted in order ensure that objects coming
+  # from read attribute access match those that are set with write
+  # attribute access. Specifically the metadata, such as associated apr_pool
+  # object, should match the originally assigned object.
+  #
+  # For classic classes it is enough to use __getattr__ to intercept swig
+  # derived attributes. However, with new style classes SWIG makes use of
+  # descriptors which mean that __getattr__ is never called. Therefore,
+  # __getattribute__ must be used for the interception.
 
-    # If we got back a different object than we have, we need to copy all our
-    # metadata into it, so that it looks identical
-    try:
-      members = object.__getattribute__(self, '_members')
-      if name in members:
-          _copy_metadata_deep(value, members[name])
-          # Verify that the new object is good
-    except AttributeError:
-      pass
+  if _newclass:
+    def __getattribute__(self, name):
+      """Manage access to all attributes of this object."""
 
-    # Verify that the new object is good
-    _assert_valid_deep(value)
+      # Start by mimicing __getattr__ behavior: immediately return __dict__ or
+      # items directly present in __dict__
+      mydict = object.__getattribute__(self, '__dict__')
 
-    return value
+      if name == "__dict__":
+        return mydict
 
+      if name in mydict:
+        return mydict[name]
+
+      object.__getattribute__(self, 'assert_valid')()
+
+      try:
+        value = object.__getattribute__(self, name)
+      except AttributeError:
+        value = _swig_getattr(self,
+                              object.__getattribute__(self, '__class__'),
+                              name)
+
+      fn = object.__getattribute__(self, '_retrieve_swig_value')
+      return fn(name, value)
+  else:
+    def __getattr__(self, name):
+      """Get an attribute from this object"""
+      self.assert_valid()
+
+      value = _swig_getattr(self, self.__class__, name)
+
+      return self._retrieve_swig_value(name, value)
+
   def __setattr__(self, name, value):
     """Set an attribute on this object"""
     self.assert_valid()

Reply via email to