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()