reporting proxy porting problem
Terry Reedy
tjreedy at udel.edu
Thu Nov 28 17:01:25 EST 2013
On 11/28/2013 6:12 AM, Robin Becker wrote:
> I am in the process of porting reportlab to python3.3, one of the
> contributions is a module that implements a reporting proxy with a
> canvas that records all access calls and attribute settings etc etc.
> This fails under python3 because of differences between old and new
> style classes.
All the transition guides I have seen recommend first updating 2.x code
(and its tests) to work in 2.x with *all* classes being new-style
classes. I presume one of the code checker programs will check this for
you. To some extent, the upgrade can be done by changing one class at a
time.
Yes, this means abandoning support of 2.1 ;-). It also means giving up
magical hacks that only work with old-style classes.
> I find that I don't understand exactly how the original works so well,
To me, not being comprehensible is not a good sign. I explain some of
the behavior below.
> but here is a cut down version
Much more should be cut to highlight the important parts.
> ##########################################################################
> class Canvas:
> def __init__(self,*args,**kwds):
> self._fontname = 'Helvetica'
This seems pretty useless, but maybe that is a result of cutting down.
> class PDFAction :
> """Base class to fake method calls or attributes on Canvas"""
> def __init__(self, parent, action) :
> """Saves a pointer to the parent object, and the method name."""
> self._parent = parent
> self._action = action
>
> def __getattr__(self, name) :
> """Probably a method call on an attribute, returns the real one."""
What if it is not a 'method call on an attribute'?
> print('PDFAction.__getattr__(%s)' % name)
> return getattr(getattr(self._parent._underlying, self._action), name)
I snipped several irrelevant methods. The important part is that there
is no __str__ method!
> class PyCanvas:
> _name = "c"
>
> def __init__(self, *args, **kwargs) :
> self._in = 0
> self._parent = self # nice trick, isn't it ?
I call this an ugly code stink. But this is not directly an issue here.
> self._underlying = Canvas(*args,**kwargs)
Snip irrelevant __bool__
> def __str__(self) :
> return 'PyCanvas.__str__()'
Also irrelevant for the example.
> def __getattr__(self, name) :
> return PDFAction(self, name)
> if __name__=='__main__':
> c = PyCanvas('filepath.pdf')
> print('c._fontname=%s' % c._fontname)
> print('is it a string? %r type=%s' %
> (isinstance(c._fontname,str),type(c._fontname))))
> ##########################################################################
>
> when run under python27
> C:\code\hg-repos\reportlab>\python27\python.exe z.py
> PDFAction.__getattr__(__str__)
> c._fontname=Helvetica
> is it a string? False type=<type 'instance'>
When Canvas and PyCanvas are upgraded, but PDFAction is not, the result
remains the same. This fact shows where the old-new difference makes a
difference.
> and under python33 I see this
> C:\code\hg-repos\reportlab>\python33\python.exe z.py
> c._fontname=<__main__.PDFAction object at 0x00BF8830>
> is it a string? False type=<class '__main__.PDFAction'>
With 2.7, and PDFAction also upgraded (subclassed from object), the
result is the same. So this is not a 3.x issue at all, but strictly an
old -- new class issue, which has existed since 2.2.
The first difference is that
print('c._fontname=%s' % c._fontname)
produces, with old-style PDFAction,
PDFAction.__getattr__(__str__)
c._fontname=Helvetica
but produces, with new-style PDFAction,
c._fontname=<__main__.PDFAction object at 0x00BF8830>
The reason is that c._fontname invokes PyCanvas.__getattr__, which
returns PDFAction(c, '_fontname') (call this p). The % string
interpolation calls p.__str__. For old p, that method does not exist, so
p.__getattr__('__str__) is called, and that prints a debug line and
returns 'Helvetica', which is interpolated and printed in the second
line. For new p, p.__str__ is inherited from object, and we see the
familiar default 'object at' string.
The hack is depending of the absence of a special method. The immediate
fix is to give PDFAction a .__str__ method that returns the same string
as the current .__attr__.
The second different is that type(c._fontname) is 'instance' versus
"<class '__main__.PDFAction'>". This is because the type of all
instances of all user-defined old classes is 'instance'. This is pretty
useless. Any test that depends on this is equally useless and should be
upgraded to only pass with an instance of the intended (new-style) class.
--
Terry Jan Reedy
More information about the Python-list
mailing list