How to limit *length* of PrettyPrinter

Stavros Macrakis macrakis at alum.mit.edu
Fri Jul 24 18:52:02 EDT 2020


dn, Thanks again.

For background, I come from C and Lisp hacking (one of the MIT developers
of Macsyma <https://en.wikipedia.org/wiki/Macsyma>/Maxima
<https://sourceforge.net/p/maxima/wiki/Home/>) and also play with R, though
I haven't been a professional developer for many years. I know better than
to Reply to a Digest -- sorry about that, I was just being sloppy.

The reason I wanted print-length limitation was that I wanted to get an
overview of an object I'd created, which contains some very long lists. I
expected that this was standard functionality that I simply couldn't find
in the docs.

I'm familiar with writing pretty-printer ("grind") functions with string
output (from way back: see section II.I, p. 12
<http://bitsavers.trailing-edge.com/pdf/mit/ai/aim/AIM-279.pdf>), but I'm
not at all familiar with Python's type/class system, which is why I'm
trying to understand it by playing with it.

I did try looking at the Python Standard Library docs, but I don't see
where it mentions the superclasses of the numerics or of the collection
types or the equivalent of *numberp*. If I use *type(4).__bases__*, I get
just* (<class 'object'>,)*, which isn't very helpful. I suspect that that
isn't the correct way of finding a class's superclasses -- what is?

BTW, where do I look to understand the difference between *dir(type(4)) *(which
does not include *__bases__*) and *type(4).__dir__(type(4)) *(which does)?
According to Martelli (2017, p. 127), *dir(*x*)* just calls *x.**__dir__()*;
but *type(4).__dir__() *=> ERR for me. Has this changed since 3.5, or is
Martelli just wrong?

There's nothing else obvious in dir(0) or in dir(type(0)). After some
looking around, I find that the base classes are not built-in, but need to
be added with the *numbers* and *collections.abc *modules? That's a
surprise!

You suggested I try *pp.__builtins__.__dict__()* . I couldn't figure out
what you meant by *pp* here (the module name *pprint*? the class
*pprint.PrettyPrint*? the configured function
*pprint.PrettyPrinter(width=20,indent=3).pprint*? none worked...). I
finally figured out that you must have meant something like
*pp=pprint.PrettyPrinter(width=80).print;
pp(__builtins__.__dict__)*. Still not sure which attributes could be useful.

With bottom-up prototyping it is wise to start with the 'standard' cases!
(and to 'extend' one-bite at a time)


Agreed! I started with lists, but couldn't figure out how to extend that to
tuples and sets.  I was thinking I could build a list then convert it to a
tuple or set. The core recursive step looks something like this:

      CONVERT( map( lambda i: limit(i, length, depth-1) , obj[0:length] ) +
( [] if len(obj) <= length else ['...'] ) )

... since map returns an iterator, not a collection of the same type as its
input -- so how do I convert to the right result type (CONVERT)?

After discovering that typ.__new__(typ,obj) doesn't work for mutables, and
thrashing for a while, I tried this:

      def convert(typ,obj):
           newobj = typ.__new__(typ,obj)
           newobj.__init__(obj)
           return newobj

which is pretty ugly, because the *__new__* initializer is magically
ignored for a mutable (with no exception) and the *__init__* setter is
magically ignored for an immutable (with no exception). But it seems to
work....

Now, on to dictionaries! Bizarrely, the *list()* of a dictionary, and its
iterator, return only the keys, not the key-value pairs. No problem! We'll
create yet another special case, and use *set.items()* (which for some
reason doesn't exist for other collection types). And *mirabile
dictu*, *convert
*works correctly for that!:

dicttype = type({'a':1})
test = {'a':1,'b':2}
convert(dicttype,test.items()) => {'a':1,'b':2}

So we're almost done. Now all we have to do is slice the result to the
desired length:

convert(dicttype,test.items()[0:1])      # ERR


But *dict.items() *is not sliceable. However, it *is* iterable... but we
need another count variable (or is there a better way?):


c = 0
convert(dicttype, [ i for i in test.items() if (c:=c+1)<2 ])


Phew! That was a lot of work, and I'm left with a bunch of special cases,
but it works. Now I need to understand from a Python guru what the Pythonic
way of doing this is which *doesn't* require all this ugliness.

(This doesn't really work for the original problem, because there's no way
of putting "..." at the end of a dictionary object, but I still think I
learned something about Python.)

I did take a look at the pprint source code, and could no doubt modify it
to handle print-length, but at this point, I'm still trying to understand
how Python code can be written generically. So I was disappointed to see
that *_print_list, _print_tuple, *and* _print_set *are not written
generically, but as three separate functions. I also wonder what the '({'
case is supposed to cover.

A lot of questions -- probably based on a lot of misunderstandings!

Thanks!

           -s


More information about the Python-list mailing list