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