tofile(), tostring(), tolines() and a not-a-file class StringFile

Steven D. Majewski (sdm7g@elvis.med.virginia.edu)
Wed, 16 Mar 1994 16:58:05 -0500

function: tofile( file, func, *args )
temporarily redirects sys.stdout to file, and then applies
func to args, redirecting sys.stdout back again.

functions tostring and tolines, do sort of the same thing,
but use a "fake" file class to redirect the output into
a string. tostring returns a string, and tolines returns
a readlines() like list of line-strings.

I made this, rather than rewriting the python disassembler
( Lib/dis.py ) and changing all of the "print"-'s to append's .
I want to stuff source lines in between the disassembled
bytecode. Just another example of the wonders of software
reuse, Python style! :-)

Next: how about a dribble() function that logs input and
output ( and error ) for those of us who aren't/can't use
Python in emacs python-mode.

Note:
I am in the habit of often writing functions that take a file,
so that they can accept either a file-(like)-object or a filename.
This takes an extra line or two in the function, but it is worth
it when you are using it interactively -- a one time effort saves
hundreds of keystrokes when I'm actually using the functions.

I have used several different idioms in various pieces of code,
but after all of the many discussions we have had about typing,
I have come to the conclusion that this:

|*| if not hasattr( file, 'write' ) : file = open( file, 'w' )

is the correct idiom. For those of you who missed out on those
discussions, the point of it is:
If you test for type(file) == <type 'file'>, then it won't
work on sockets or other file-like classes. But the same goes
for testing the arg against "type('')".
So ( pace Tim Peters ) the "Right Thing" to do is to see if it
is a "writable" object.

- Steve Majewski (804-982-0831) <sdm7g@Virginia.EDU>
- UVA Department of Molecular Physiology and Biological Physics

# module <redirect>
#
# Redirects sys.stdout to file or string.
# - Steven D. Majewski <sdm7g@Virginia.EDU>
#

import sys

# apply func( args ), temporarily redirecting stdout to file
def tofile( file, func, *args ):
if not hasattr( file, 'write' ) : file = open( file, 'w' )
sys.stdout, file = file, sys.stdout
try:
apply( func, args )
finally:
sys.stdout, file = file, sys.stdout

# same as above, but redirect output to a string and return string.
def tostring( func, *args ):
string = StringFile()
apply( tofile, ( string, func ) + args )
return string.read()

# same as above, but return list of lines of output
def tolines( func, *args ):
string = StringFile()
apply( tofile, ( string, func ) + args )
return string.readlines()

from array import array

# A class that mimics a r/w file.
# strings written to the file are stored in a character array.
# a read reads back what has been written.
# Note that the buffer pointer for read is independent of write,
# which ALWAYS appends to the end of buffer.
# Not exactly the same as file semantics, but it happens to be
# what we want!
# Some methods are no-ops, or otherwise not very useful, but
# are included anyway: close(), fileno(), flush(),
# I haven't elaborately tested seek() and tell() to see if
# they are doing the _exactly_ right thing, but they look OK.
# I didn't really need them, so they have been minimally tested.
#

class StringFile:
def __init__( self ):
self._buf = array( 'c' )
self._bp = 0
def close(self):
return self
def fileno(self):
return None
def flush(self):
pass
def isatty(self):
return 0
def read(self, *howmuch ):
buf = self._buf.tostring()[self._bp:]
if howmuch:
howmuch = howmuch[0]
else:
howmuch = len( buf )
ret = buf[:howmuch]
self._bp = self._bp + len(ret)
return ret
def readline(self):
line = ''
for c in self._buf.tostring()[self._bp:] :
line = line + c
self._bp = self._bp + 1
if c == '\n' : return line
def readlines(self):
lines = []
while 'True' :
lines.append( self.readline() )
if not lines[-1] : return lines[:-1]
def seek(self, where, how ):
if how == 0 :
self._bp = where
elif how == 1 :
self._bp = self._bp + where
elif how == 2 :
self._bp = len(self._buf.tostring()) + where
def tell(self):
return self._bp
def write(self, what ):
self._buf.fromstring( what )
def writelines( self, lines ):
for eachl in lines:
self._buf.fromstring( eachl )