Emulating Pascal input

Bengt Richter bokr at oz.net
Thu May 23 16:33:19 EDT 2002


On Wed, 22 May 2002 12:03:20 +0100, Michael Williams <michael.williams at st-annes.oxford.ac.uk> wrote:

>Hi,
>
>We're currently running a trial implementation of a teaching course in
>Python here at the University of Oxford Physics department. The current
>course uses Pascal and, although it works well, is obviously sub-optimal
>for a modern undergraduate education.
>
>Details of the results of the trial will be posted to EDU-sig when it is
>complete, but for now we're trying to address what we see as a
>limitation of Python for introductory programming, namely input.
>
>Consider the following Pascal code:
>
>(******** Pascal code **********)
>(* Pascal ascii input of numbers: stdin and file *)
>readln(x, y);
>readln(fin, x, y);
>
>The first line is for stdin, the second for from file. The variables
>x and y will then contain the first two white space separated numbers
>the program encounters when that function is called.
>
>Here's how we're currently getting students to do the equivalent in
>Python:
>
>######### Python code ###########
># Either: stdin
>linestring = raw_input()
>
># Or: file
>linestring = f.readline()
>
>linelist = string.split(linestring)
>x = int(linelist[0])
>y = int(linelist[1])
>
># Or perhaps
>linestring = raw_input()
>x,y = string.split(linestring)  # x and y are now strings eg. '1' 
>                                # and '2'
>x = int(x)
>y = int(y)
>
>Having read through the many threads on this topic on comp.lang.python I
>realise and understand that modern programs' input generally requires
>thought and customization from the program author. However, we strongly
>feel there should be an equivalent to Pascal's readln (which is
>basically C's scanf but without the formatting strings) for simple,
>generally numerical, whitespace-separated input. 
>
ISTM the key problem is that you are asking for type-driven input,
but not wanting to tell Python about it explicitly. You visualize
typed Pascal variables being filled with numeric values taken from
an input character sequence according to their type, but a Python
'variable' is just a name. There is no static type associated with
a name. You have to go to the value object it refers to to get any
indication of type, and the value object is not updated by "assignment"
to a name: instead, a new value is computed from the expression on the
right hand side of the '=' and the name is bound to the new value,
abandoning the old (which may persist if it's also bound to something
else, or be left for the garbage collector if not).

Pascal's readln is not really C's scanf w/o formatting strings. Instead,
ISTM it would be more accurate to say Pascal's readln is like C's scanf
with implicit formatting strings based on the compiler's knowledge
of the variable type declarations.

>In the two weeks out trial has been running (one to go), students'
>attempts to read in simple two column tables from a file has
>consistently caused problems due to the (relative!) complexity of string
>splitting.
>
>However, There is no need for a scanf (if your input is sufficiently
>complex to require formatting strings then either string or regex
>splitting is definitely a more elegant solution).
>
>I have been attempting to write such a function in Python but have been 
>struggling with (a) the lack of pointers, and (b) my total ignoarnce of
>classes, which I suspect would be helpful. Here's what I have so far
>(although note there is no error handling--give it a string response and
>it falls over and dies, give it the wrong number of responses and it
>falls over and dies, etc., etc.):
>
>############ ninput.py ############
>
>import string
>
>def ninput(n, fin = None, prompt = '', sep = None):
>    
>    # If not file input then get string from stdin, else get from file
>    if fin == None: linestring = raw_input(prompt)
>    else: linestring = fin.readline()
>    
>    # Split up into list with optional seperator argument
>    responses = string.split(linestring, sep)
>    
>    for i in range(len(responses)):
>        if '.' in responses[i] or 'e' in responses[i] or \ 
>        'E' in responses[i]:
>            # i.e. if it looks like a float
>            responses[i] = float(responses[i])
>        else:
>            # i.e if it doesn't look like a float    
>            responses[i] = int(responses[i])
>            
>    # If there was only one response then don't return a list.
>    if len(responses) == 1: responses = responses[0] 
>    
>    return responses[:n]
>
>####################################
>
>And here is a usage example:
>>>> x, y  = ninput(2)
>5 6.0
>>>> print x, type(x)
>5 <type 'int'>
>>>> print y, type(y)
>6.0 <type 'float'>
>
>This has the limitation that the user must pass the number of results
>they expect back. This is done implicitly in the Pascal version too (the
>number of variables passed to the function). However, error checking is 
>difficult as this version stands.
>
>If the student using the ninput function does, e.g.
>
>>>> x = ninput(2)
>5 6.0
>[5, 6.0] <type 'list'>
>
>This is possibly what should be expected. However, passing the numerical
>argument representing the number of results expected does not strike me
>as a particularly graceful solution. We would prefer something like the
>Pascal way, where the global variables in which the results are to be
>stored are passed as parameters. Can anyone suggest a way of doing this?
>
If you pass a _string_ containing an ordered arg name list (e.g., comma-separated
as below), you can get the names bound in the global name space to converted values.

You can put the following definition at the top of your program, or you can put it
in a file, e.g., readln.py and excecfile it (an ordinary import statement doesn't use
the right globals() -- variables would appear assigned in the module global space
instead of your importing program's global space).

I don't recommend this as-is (see security dangers below), but it shows a way
of shoehorning some badly-fitting ideas into Python ;-)

So, assume we have files
---< readln.py >---------
def readln(names, **kw):
    linestr = kw and 'inp' in kw and kw['inp'].readline() or raw_input(kw.get('prompt',''))
    exec('\n'.join([('%s=%s' % pair) for pair in zip(names.split(','),linestr.split())]), globals())
-------------------------
and
---< twocols.txt >-------
123 4.5
678 9.0
-------------------------

in the current directory where we start python. Then we can do this
(not tested much, no guarantees ;-) :

 >>> execfile('readln.py')
 >>> infile = file('twocols.txt','r')
 >>> readln('x,y',inp=infile)
 >>> x,y
 (123, 4.5)
 >>> readln('x,y',inp=infile)
 >>> x,y
 (678, 9.0)
 >>> readln('x,y,z',prompt='Enter 3 values: ')
 Enter 3 values: 1111 22.22 'three'
 >>> x,y,z
 (1111, 22.219999999999999, 'three')
 >>> print x,y,z
 1111 22.22 three

Note that this actually generates code that is compiled and executed,
(and the input is split with whitespace, so there can't be embedded blanks
in an input string), but there are potentially dangerous possibilities, as
the following might indicate:

 >>> readln('a,b',prompt='hm? ')
 hm? __import__('os') a.popen('dir').read()
 >>> print b
  Volume in drive C is System
  Volume Serial Number is 14CF-C4B9

  Directory of C:\pywk\pascal

 02-05-23  12:37         <DIR>          .
 02-05-23  12:37         <DIR>          ..
 02-05-23  11:54                    221 readln.py
 02-05-23  11:58                     18 twocols.txt
                4 File(s)            239 bytes
                               5,265,920 bytes free

 >>> a
 <module 'os' from 'D:\python22\lib\os.pyc'>

popen('dir') is obviously benign compared to what you could do, especially
with root/admin priviliges. IOW, this is dangerous like input() is dangerous.
You could check that only simple names and constants get compiled, etc., But
I'm going have to leave that as an exercise ... ;-)

>And if such an equivalent to the Pascal construct was implemented, what
>would be the chances of getting it included in, say, the string module
>of core Python. We would like such a function but would be reluctant to
>use it as we would then be teaching our students a ``dialect'' of
>Python.
>
My guess would be that the chances for a directly translated implementation
of readln would be low, because there are no statically typed variables to
dictate the conversion as in Pascal. ;-)

Regards,
Bengt Richter



More information about the Python-list mailing list