+1  I think this is a nice design.  Perhaps we should add CCodePrinter as well.

-- Andy
- Show quoted text -

On Wed, Nov 5, 2008 at 4:20 AM, Sebastian <[EMAIL PROTECTED]> wrote:
> 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
>
> >
>
> # HG changeset patch
> # User Sebastian Krämer <[EMAIL PROTECTED]>
> # 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)
>
> 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:

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.
> +        """
>
> -           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)"
>

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"sympy-patches" group.
To post to this group, send email to sympy-patches@googlegroups.com
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/sympy-patches?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to