+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 -~----------~----~----~----~------~----~------~--~---