printing

0 views
Skip to first unread message

Sebastian

unread,
Nov 4, 2008, 10:20:20 PM11/4/08
to sympy-...@googlegroups.com
As Brian Granger proposed in the thread
http://groups.google.com/group/sympy/browse_thread/thread/2927bac0810029a0
, it would be preferable if user could define printing methods for their
own classes without having to patch the printer.
A patch is attached, that implements this proposal in the following way:
Every printer can define a name under Printer.printmethod that will be
searched for in every object that is printed. E.g. the Latex Printer it
would look something like:

class LatexPrinter(Printer):
printmethod = "__latex__"
....

Every object that defines a __latex__ method will than be printed by
using this method.

It is important to notice that a so defined method always overrules the
Printer. This has to be, since the user probably will inherit from a
sympy class and nevertheless wants to use his own routine. This leads to
the problem that the StrPrinter can't look for methods called "__str__"
since all sympy classes have it and this would lead to infinite
recursion. The same for ReprPrinter.
Therefore the following printers can be overruled by the following methods:
StrPrinter: __sympystr__
ReprPrinter: __sympyrepr__
LatexPrinter: __latex__
MathMlPrinter: __mathml__

Waiting for comments,
Sebastian

printer.patch

Brian Granger

unread,
Nov 4, 2008, 2:08:14 PM11/4/08
to sympy-patches
One issue with the choice of method names like __latex__. Name with
both leading and trailing double underscores are (at least informally)
reservered for Python itself. Thus, the common naming scheme for
methods like this (Sage for example follows this) is only single
underscores:

_latex_
_ccode_
etc.


Brian

On Nov 4, 7:20 pm, Sebastian <basti...@gmail.com> wrote:
> As Brian Granger proposed in the threadhttp://groups.google.com/group/sympy/browse_thread/thread/2927bac0810...
> [printer.patch12K ]# HG changeset patch
> # User Sebastian Krämer <basti...@gmail.com>
> # Date 1225850642 28800
> # Node ID 054226e56c36b2809594a9b5662b8d5a2e4d0fb5
> # Parent  d66dc8f1f1077acc5776284d9fbbcc4c9871fb01
> Improve printing system
>  - Printer._depth was not needed
>  - Raise exception when printing method returns None. The reason
>    is, that this most likely comes from forgetting to return the
>    result and otherwise it hides this error by using the emptyPrinter
>  - Wrote a summary how the printing is supposed to work in printer.py
>  - Implemented Brian Granger's proposal of also letting objects define
>    their own printing methods.
>    (http://groups.google.com/group/sympy/browse_thread/thread/2927bac0810...)
>
> diff -r d66dc8f1f107 -r 054226e56c36 sympy/printing/latex.py
> --- a/sympy/printing/latex.py   Fri Oct 24 19:09:04 2008 +0200
> +++ b/sympy/printing/latex.py   Tue Nov 04 18:04:02 2008 -0800
> @@ -5,6 +5,8 @@ import re
>
>  class LatexPrinter(Printer):
>      """A printer which converts an expression into its LaTeX equivalent."""
> +
> +    printmethod = "__latex__"
>
>      def __init__(self, inline=True):
>          Printer.__init__(self)
> @@ -310,6 +312,8 @@ class LatexPrinter(Printer):
>                  sign = "- "
>                  p = -p
>              return r"%s\frac{%d}{%d}" % (sign, p, expr.q)
> +        else:
> +            return self._print(expr.p)
>
>      def _print_Infinity(self, expr):
>          return r"\infty"
> diff -r d66dc8f1f107 -r 054226e56c36 sympy/printing/mathml.py
> --- a/sympy/printing/mathml.py  Fri Oct 24 19:09:04 2008 +0200
> +++ b/sympy/printing/mathml.py  Tue Nov 04 18:04:02 2008 -0800
> @@ -4,6 +4,8 @@ from printer import Printer
>
>  class MathMLPrinter(Printer):
>      """A MathML printer."""
> +    printmethod = "__mathml__"
> +
>      def __init__(self, *args, **kwargs):
>          Printer.__init__(self, *args, **kwargs)
>          from xml.dom.minidom import Document
> diff -r d66dc8f1f107 -r 054226e56c36 sympy/printing/pretty/pretty.py
> --- a/sympy/printing/pretty/pretty.py   Fri Oct 24 19:09:04 2008 +0200
> +++ b/sympy/printing/pretty/pretty.py   Tue Nov 04 18:04:02 2008 -0800
> @@ -36,7 +36,7 @@ class PrettyPrinter(Printer):
>              # print atoms like Exp1 or Pi
>              return prettyForm(pretty_atom(e.__class__.__name__))
>          except KeyError:
> -            pass
> +            return self.emptyPrinter(e)
>
>      # Infinity inherits from Rational, so we have to override _print_XXX order
>      _print_Infinity         = _print_Atom
> @@ -460,6 +460,8 @@ class PrettyPrinter(Printer):
>                  return prettyForm(binding=prettyForm.NEG, *pform.left('- '))
>              else:
>                  return prettyForm(str(r.p))/prettyForm(str(r.q))
> +        else:
> +            return self.emptyPrinter(r)
>
>      def _print_seq(self, seq, left=None, right=None):
> diff -r d66dc8f1f107 -r 054226e56c36 sympy/printing/printer.py
> --- a/sympy/printing/printer.py Fri Oct 24 19:09:04 2008 +0200
> +++ b/sympy/printing/printer.py Tue Nov 04 18:04:02 2008 -0800
> @@ -1,15 +1,84 @@
> -"""Printing subsystem driver"""
> +"""Printing subsystem driver
> +
> +Sympy's printing system works the following way: Any expression can be passed to
> +a designated Printer who then is responsible to return a adequate representation
> +of that expression.
> +
> +The basic concept is the following:
> +  1. Let the object print itself if it knows how.
> +  2. Take the best fitting method defined in the printer.
> +  3. As fall-back use the emptyPrinter method for the printer.
> +
> +Some more information how the single concepts work and who should use which:
> +
> +1. The object prints itself
> +
> +    This was the original way of doing printing in sympy. Every class had it's
> +    own latex, mathml, str and repr methods, but it turned out that it is hard
> +    to produce a high quality printer, if all the methods are spread out that
> +    far. Therefor all printing code was combined into the different printers,
> +    which works great for built-in sympy objects, but not that good for user
> +    defined classes where it is inconvenient to patch the printers.
> +    To get nevertheless a fitting representation, the printers look for a
> +    specific method in every object, that will be called if it's available and
> +    is then responsible for the representation. The name of that method depends
> +    on the specific printer and is defined under Printer.printmethodname.
> +
> +
> +2. Take the best fitting method defined in the printer.
> +
> +    The printer loops through expr classes (class + it's bases), and tries to dispatch the
> +    work to _print_<EXPR_CLASS>
> +
> +    e.g., suppose we have the following class hierarchy::
> +
> +            Basic
> +            |
> +            Atom
> +            |
> +            Number
> +            |
> +        Rational
> +
> +    then, for expr=Rational(...), in order to dispatch, we will try calling printer methods
> +    as shown in the figure below::
> +
> +        p._print(expr)
> +        |
> +        |-- p._print_Rational(expr)
> +        |
> +        |-- p._print_Number(expr)
> +        |
> +        |-- p._print_Atom(expr)
> +        |
> +        `-- p._print_Basic(expr)
> +
> +    if ._print_Rational method exists in the printer, then it is called,
> +    and the result is returned back.
> +
> +    otherwise, we proceed with trying Rational bases in the inheritance
> +    order.
> +
> +3. As fall-back use the emptyPrinter method for the printer.
> +
> +    As fall-back self.emptyPrinter will be called with the expression. If
> +    not defined in the Printer subclass this will be the same as str(expr)
> +"""
>
>  class Printer(object):
> -    """Generic printer driver
> +    """Generic printer
>
> -    This is a generic printer driver.
>      It's job is to provide infrastructure for implementing new printers easily.
>
>      Basically, if you want to implement a printer, all you have to do is:
>
>      1. Subclass Printer.
> -    2. In your subclass, define _print_<CLASS> methods
> +
> +    2. Define Printer.printmethod in your subclass.
> +       If a object has a method with that name, this method will be used
> +       for printing.
> +
> +    3. In your subclass, define _print_<CLASS> methods
>
>         For each class you want to provide printing to, define an appropriate
>         method how to do it. For example if you want a class FOO to be printed in
> @@ -31,15 +100,16 @@ class Printer(object):
>         On the other hand, a good printer will probably have to define separate
>         routines for Symbol, Atom, Number, Integral, Limit, etc...
>
> -    3. If convenient, override self.emptyPrinter
> +    4. If convenient, override self.emptyPrinter
>
>         This callable will be called to obtain printing result as a last resort,
> -       that is when no appropriate _print_<CLASS> was found for an expression.
> +       that is when no appropriate print method was found for an expression.
>
>      """
>      def __init__(self):
> -        self._depth = -1
>          self._str = str
> +        if not hasattr(self, "printmethod"):
> +            self.printmethod = None
>          if not hasattr(self, "emptyPrinter"):
>              self.emptyPrinter = str
>
> @@ -48,57 +118,35 @@ class Printer(object):
>          return self._str(self._print(expr))
>
>      def _print(self, expr, *args):
> -        """internal dispatcher
> +        """Internal dispatcher
>
> -           It's job is to loop through expr classes (class + it's bases), and
> -           try to dispatch the work to _print_<EXPR_CLASS>
> +        Tries the followingc concepts to print an expression:
> +            1. Let the object print itself if it knows how.
> +            2. Take the best fitting method defined in the printer.
> +            3. As fall-back use the emptyPrinter method for the printer.
> +        """
>
> -           e.g., suppose we have the following class hierarchy::
> -
> -                 Basic
> -                   |
> -                 Atom
> -                   |
> -                 Number
> -                   |
> -                Rational
> -
> -           then, for expr=Rational(...), in order to dispatch, we will try
> -           calling printer methods as shown in the figure below::
> -
> -               p._print(expr)
> -               |
> -               |-- p._print_Rational(expr)
> -               |
> -               |-- p._print_Number(expr)
> -               |
> -               |-- p._print_Atom(expr)
> -               |
> -               `-- p._print_Basic(expr)
> -
> -           if ._print_Rational method exists in the printer, then it is called,
> -           and the result is returned back.
> -
> -           otherwise, we proceed with trying Rational bases in the inheritance
> -           order.
> -
> -           if nothing exists, we just return:
> -
> -               p.emptyPrinter(expr)
> -        """
> -        self._depth += 1
> +        # If the printer defines a name for a printing method (Printer.printmethod) and the
> +        # object knows for itself how it should be printed, use that method.
> +        if self.printmethod and hasattr(expr, self.printmethod):
> +            res = getattr(expr, self.printmethod)()
> +            if res is None:
> +                raise Exception("Printing method '%s' of an instance of '%s' did return None" %\
> +                                    (self.printmethod, expr.__class__.__name__))
> +            return res
>
>          # See if the class of expr is known, or if one of its super
>          # classes is known, and use that print function
> -        res = None
>          for cls in type(expr).__mro__:
> -            if hasattr(self, '_print_'+cls.__name__):
> -                res = getattr(self, '_print_'+cls.__name__)(expr, *args)
> -                break
> +            printmethod = '_print_' + cls.__name__
> +            if hasattr(self, printmethod):
> +                res = getattr(self, printmethod)(expr, *args)
> +                if res is None:
> +                    raise Exception("Printing method '%s' did return None" % printmethod)
> +                return res
>
> -        # Unknown object, just use its string representation
> +        # Unknown object, fall back to the emptyPrinter.
> +        res = self.emptyPrinter(expr)
>          if res is None:
> -            res = self.emptyPrinter(expr)
> -
> -        self._depth -= 1
> +            raise Exception("emptyPrinter method of '%s' did return None" % self.__class__.__name__)
>          return res
> diff -r d66dc8f1f107 -r 054226e56c36 sympy/printing/repr.py
> --- a/sympy/printing/repr.py    Fri Oct 24 19:09:04 2008 +0200
> +++ b/sympy/printing/repr.py    Tue Nov 04 18:04:02 2008 -0800
> @@ -12,6 +12,7 @@ from sympy.mpmath.settings import prec_t
>  from sympy.mpmath.settings import prec_to_dps, repr_dps
>
>  class ReprPrinter(Printer):
> +    printmethod = "__sympyrepr__"
>      def reprify(self, args, sep):
>          return sep.join([self.doprint(item) for item in args])
>
> diff -r d66dc8f1f107 -r 054226e56c36 sympy/printing/str.py
> --- a/sympy/printing/str.py     Fri Oct 24 19:09:04 2008 +0200
> +++ b/sympy/printing/str.py     Tue Nov 04 18:04:02 2008 -0800
> @@ -14,6 +14,8 @@ from sympy.mpmath.settings import prec_t
>  from sympy.mpmath.settings import prec_to_dps
>
>  class StrPrinter(Printer):
> +    printmethod = "__sympystr__"
> +
>      def parenthesize(self, item, level):
>          if precedence(item) <= level:
>              return "(%s)"%self._print(item)
> diff -r d66dc8f1f107 -r 054226e56c36 sympy/printing/tests/test_latex.py
> --- a/sympy/printing/tests/test_latex.py        Fri Oct 24 19:09:04 2008 +0200
> +++ b/sympy/printing/tests/test_latex.py        Tue Nov 04 18:04:02 2008 -0800
> @@ -8,6 +8,12 @@ from sympy.functions import DiracDelta
>
>  x,y = symbols('xy')
>  k,n = symbols('kn', integer=True)
> +
> +def test_printmethod():
> +    class R(abs):
> +        def __latex__(self):
> +            return "foo"
> +    assert latex(R(x)) == "$foo$"
>
>  def test_latex_basic():
>      assert latex(1+x) == "$1 + x$"
> diff -r d66dc8f1f107 -r 054226e56c36 sympy/printing/tests/test_mathml.py
> --- a/sympy/printing/tests/test_mathml.py       Fri Oct 24 19:09:04 2008 +0200
> +++ b/sympy/printing/tests/test_mathml.py       Tue Nov 04 18:04:02 2008 -0800
> @@ -5,6 +5,9 @@ from sympy.utilities.pytest import XFAIL
>
>  x = Symbol('x')
>  mp = MathMLPrinter()
> +
> +def test_printmethod():
> +    pass #TODO
>
>  def test_mathml_core():
>      mml_1 = mp._print(1+x)
> diff -r d66dc8f1f107 -r 054226e56c36 sympy/printing/tests/test_repr.py
> --- a/sympy/printing/tests/test_repr.py Fri Oct 24 19:09:04 2008 +0200
> +++ b/sympy/printing/tests/test_repr.py Tue Nov 04 18:04:02 2008 -0800
> @@ -29,6 +29,11 @@ def sT(expr, string):
>      assert srepr(expr) == string
>      assert eval(string, ENV) == expr
>
> +def test_printmethod():
> +    class R(oo.__class__):
> +        def __sympyrepr__(self):
> +            return "foo"
> +    assert srepr(R()) == "foo"
>
>  def test_Add():
>      sT(x+y, "Add(Symbol('x'), Symbol('y'))")
> diff -r d66dc8f1f107 -r 054226e56c36 sympy/printing/tests/test_str.py
> --- a/sympy/printing/tests/test_str.py  Fri Oct 24 19:09:04 2008 +0200
> +++ b/sympy/printing/tests/test_str.py  Tue Nov 04 18:04:02 2008 -0800
> @@ -20,6 +20,12 @@ spr = StrPrinter.doprint
>
>  x, y, z, w = symbols('xyzw')
>  d = Symbol('d', dummy=True)
> +
> +def test_printmethod():
> +    class R(abs):
> +        def __sympystr__(self):
> +            return "foo"
> +    assert spr(R(x)) == "foo"
>
>  def test_abs():
>      assert str(abs(x)) == "abs(x)"

Ondrej Certik

unread,
Nov 4, 2008, 5:07:09 PM11/4/08
to sympy-...@googlegroups.com
On Tue, Nov 4, 2008 at 8:08 PM, Brian Granger <elliso...@gmail.com> wrote:
>
> One issue with the choice of method names like __latex__. Name with
> both leading and trailing double underscores are (at least informally)
> reservered for Python itself. Thus, the common naming scheme for
> methods like this (Sage for example follows this) is only single
> underscores:
>
> _latex_
> _ccode_
> etc.

Another problem is that I am getting these doctests failures:

$ ./setup.py test_doc
running test_doc
Testing docstrings.
.......................................................................................................................................................................F.............................
======================================================================
FAIL: Doctest: sympy.simplify.simplify.collect
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/lib/python2.5/doctest.py", line 2128, in runTest
raise self.failureException(self.format_failure(new.getvalue()))
AssertionError: Failed doctest test for sympy.simplify.simplify.collect
File "/home/ondra/repos/sympy.hg/sympy/simplify/simplify.py", line
349, in collect

----------------------------------------------------------------------
File "/home/ondra/repos/sympy.hg/sympy/simplify/simplify.py", line
449, in sympy.simplify.simplify.collect
Failed example:
collect(a*D(D(f,x),x) + b*D(D(f,x),x), D(f,x))
Expected:
(a + b)*D(D(f(x), x), x)
Got:
(a + b)*D(f(x), x, x)
----------------------------------------------------------------------
File "/home/ondra/repos/sympy.hg/sympy/simplify/simplify.py", line
452, in sympy.simplify.simplify.collect
Failed example:
collect(a*D(D(f,x),x) + b*D(D(f,x),x), D(f,x), exact=True)
Expected:
a*D(D(f(x), x), x) + b*D(D(f(x), x), x)
Got:
a*D(f(x), x, x) + b*D(f(x), x, x)
----------------------------------------------------------------------
File "/home/ondra/repos/sympy.hg/sympy/simplify/simplify.py", line
455, in sympy.simplify.simplify.collect
Failed example:
collect(a*D(D(f,x),x) + b*D(D(f,x),x) + a*D(f,x) + b*D(f,x), D(f,x))
Expected:
(a + b)*D(D(f(x), x), x) + (a + b)*D(f(x), x)
Got:
(a + b)*D(f(x), x) + (a + b)*D(f(x), x, x)
----------------------------------------------------------------------
File "/home/ondra/repos/sympy.hg/sympy/simplify/simplify.py", line
460, in sympy.simplify.simplify.collect
Failed example:
collect(a*D(D(f,x),x)**2 + b*D(D(f,x),x)**2, D(f,x))
Expected:
(a + b)*D(D(f(x), x), x)**2
Got:
(a + b)*D(f(x), x, x)**2


----------------------------------------------------------------------
Ran 197 tests in 8.870s

FAILED (failures=1)

But otherwise all tests pass, so definitely there should also be
regular tests for this. Apparently your patch exposes (or introduces)
some bugs, but all tests pass.

Ondrej

Ondrej Certik

unread,
Nov 4, 2008, 5:14:33 PM11/4/08
to sympy-...@googlegroups.com

Oops, I am getting this failure even without your patch. Sorry about
that. It was the last commit. I'll fix that and push this in.

Ondrej

Ondrej Certik

unread,
Nov 4, 2008, 5:20:54 PM11/4/08
to sympy-...@googlegroups.com

This is in. If you agree, could you please also change the __ methods
to _ methods?

Thanks,
Ondrej

Sebastian

unread,
Nov 5, 2008, 10:48:35 AM11/5/08
to sympy-...@googlegroups.com
Yes I agree that single underscores are better. This patch fixes that.
printing2.patch

basti

unread,
Nov 6, 2008, 1:00:59 PM11/6/08
to sympy-patches
Could someone review this patch? It's not really urgent, but I don't
want it to get forgotten.

Sebastian

Ondrej Certik

unread,
Nov 6, 2008, 1:57:27 PM11/6/08
to sympy-...@googlegroups.com
Oops, sorry. It's +1. Please push it in.

Ondrej

Andy Ray Terrel

unread,
Nov 6, 2008, 4:42:40 PM11/6/08
to sympy-...@googlegroups.com
+1 I think this is a nice design. Perhaps we should add CCodePrinter as well.

-- Andy
- Show quoted text -
> # HG changeset patch
> # User Sebastian Krämer <bast...@gmail.com>
> # Date 1225850642 28800
> # Node ID 054226e56c36b2809594a9b5662b8d5a2e4d0fb5
> # Parent d66dc8f1f1077acc5776284d9fbbcc4c9871fb01
> Improve printing system
> - Printer._depth was not needed
> - Raise exception when printing method returns None. The reason
> is, that this most likely comes from forgetting to return the
> result and otherwise it hides this error by using the emptyPrinter
> - Wrote a summary how the printing is supposed to work in printer.py
> - Implemented Brian Granger's proposal of also letting objects define
> their own printing methods.
> (http://groups.google.com/group/sympy/browse_thread/thread/2927bac0810029a0)
typ0: followingc -> following


> + 1. Let the object print itself if it knows how.
> + 2. Take the best fitting method defined in the printer.
> + 3. As fall-back use the emptyPrinter method for the printer.
> + """
>

Ondrej Certik

unread,
Nov 6, 2008, 5:13:35 PM11/6/08
to sympy-...@googlegroups.com
>> + Tries the followingc concepts to print an expression:
>
> typ0: followingc -> following


Thanks for noticing. Fixed.

The printing2.patch is in as well.

Ondrej

Sebastian

unread,
Nov 7, 2008, 6:10:50 PM11/7/08
to sympy-...@googlegroups.com
Thanks for pushing it in and thanks for the correction!

Sebastian

Reply all
Reply to author
Forward
0 new messages