Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Emulating Pascal input

1 view
Skip to first unread message

Michael Williams

unread,
May 22, 2002, 7:03:20 AM5/22/02
to
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.

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?

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.

--
Michael Williams


Jeremy Yallop

unread,
May 22, 2002, 8:08:02 AM5/22/02
to
* Michael Williams

| 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.

Would something like the following be of any use? It returns a tuple
of values, read from a file, converted to int, float, or string as
appropriate. I'm afraid I don't know any Pascal, so I'm not sure if
the behaviour is exactly what you want.

import sys

def readln(fin=sys.stdin):
if fin is sys.stdin:
line = raw_input()
else:
line = fin.readline()
rv = ()
for item in line.split():
try:
rv += (int(item),)
except ValueError:
try:
rv += (float(item),)
except ValueError:
rv += (item,)
return rv

Jeremy.

Peter Hansen

unread,
May 22, 2002, 8:27:02 AM5/22/02
to
Michael Williams wrote:
>
> 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.

Does that mean if I were to type "i have 3 out of 6 on the test"
that x would equal 3 and y would equal 6? Or what would I get
if I typed "3 6 blah blah"?

It looks like readln() is a convenience function without directly
compatible equivalents in many other languages. Maybe you could
spend a brief time having the students work with the raw data
as you have in your examples, then present them with a nice
utility method that works in a non-readln manner (more Pythonic
would be good, with exceptions) and let them use that the rest
of the time. You could even stick it in _builtins_ via site.py
or something, so they never know it isn't standard (until they
try to use it elsewhere).

I remember writing such little programs in school at various
times, but in reality I don't think real programs ever do such
things. They parse command line arguments (maybe you could
teach them about Optik) or they handle input from a GUI,
but reading non-trivial data directly from stdin seems uncommon
outside of contrived assignments in school.

-Peter

Grant Edwards

unread,
May 22, 2002, 11:25:30 AM5/22/02
to
In article <mailman.102206541...@python.org>, Michael Williams wrote:

[...]

> 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'>

That looks pretty decent to me. What about it makes error
detection difficult?

> 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.

You could write a function that scans and converts an entire
line and returns however many numbers it finds (somebody
already posted something similar, so I'll leave the
implimentation as an exercise for the reader):

x,y = readNumLine()

If readNumLine() returns something of length other than 2 (in
our example), you get an exception which you can trap. Or you
can check the length explicitly:

v = readNumLine()
if len(v) != 2:
bonk()
x,y = v

> 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?

Python doesn't really work that way. If you change it so it
does, then it's not Python any more. :)

It's possibly to fake something by digging into Python's
internals and looking up the names of the parameters and
rebinding them in the global namespace, but that will break if
the parameters are not global. It will also earn the scorn of
the PSA underground, and that's too horrible to contemplate.

If you really want to do it the Pascal way, then you should
teach Pascal. If you want to teach Python, then you're going
to be far better off in the long run learning to do things the
"Python way".

That said, from the "here's some more rope" department...

Another way to fake Pascals "var" parameters is to pass a
reference to a list, and modify that list:

>>> def foo(vec):
... vec[0] = 1
... vec[1] = 3
...
>>> v = [9,8]
>>> foo(v)
>>> print v
[1, 3]
>>>

IMO, this is also evil, though maybe less so than looking up
the names of parameters and changing their bindings in the
global namespace (which is what you describe).

> 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.

It would be a fundamental change in the philosophy, structure,
and character of the language. My first order estimate of the
chances for such a change being incorporated are 0. But, I'm
not the one who decides such things...

> 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.

--
Grant Edwards grante Yow! Am I accompanied by
at a PARENT or GUARDIAN?
visi.com

Quinn Dunkan

unread,
May 23, 2002, 2:45:01 PM5/23/02
to
On Wed, 22 May 2002 12:03:20 +0100, Michael Williams
<michael....@st-annes.oxford.ac.uk> wrote:
>######### Python code ###########
># Either: stdin
>linestring = raw_input()
>
># Or: file
>linestring = f.readline()

I would suggest:

f = sys.stdin
f.readline()
f = open('input')
f.readline()

to emphasize that they are the same operation.

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

This is easier to write as:

x, y = map(int, f.readline().split())

If the input is in the wrong format, python will automatically throw a nice
error message for you. I'm not sure what the pascal will do in case of error.

>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

classes are not likely to be helpful, I think you just want a utility function
:)

>(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.):

Falling over is what error handling is all about, isn't it? I thought you
didn't have error handling when errors were silently ignored, and your program
blew up later (as C and perl tend to do), and you did have error reporting when
errors were detected and reported in an orderly fashion, and had to be handled.

>############ 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]

Wow, that's complicated. I'd write this as something like:

def read_floats(fp):
return map(float, fp.readline().split())

I'd suggest that it's better style to only return one type rather than
make the caller figure it out for itself.

>####################################
>
>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.

In this version, the user specifies how many results he expects on the left
side of the '='. If there is an error (too many, too few), that will be
flagged and reported. If you want to handle it, catch ValueError:

while 1:
try:
x, y = read_floats(sys.stdin)
except ValueError, x:
print 'bad input:', x
else:
break

>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?

Yeah, just leave out the numerical argument :)

Python is one of those languages that espouses returning values by returning
them, rather than returning them with "out" parameters (personally I've never
quite understood the logic of that one, except in a prolog-ish situation), so
if you're thinking in terms of "out" parameters, it's going to fight you.
Give in. Use return values. C'mon, everyone's doing it.

>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.

Writing a few utility functions doesn't relegate you to dialect backwoods.
Everyone has a library of generic utilities they collect over time. Small
reusable utilities are one of the things dynamic languages like lisp and python
are about. Just make a mike_util.py and have your students import it, or
import it in site.py.

Bengt Richter

unread,
May 23, 2002, 4:33:19 PM5/23/02
to
On Wed, 22 May 2002 12:03:20 +0100, Michael Williams <michael....@st-annes.oxford.ac.uk> wrote:

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.

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

Carel Fellinger

unread,
May 23, 2002, 9:31:49 PM5/23/02
to
On Fri, May 24, 2002 at 02:58:26AM +0200, Carel Fellinger wrote:
...
> >>> def readln(from, *types, seperator=None):

argh, this used to work (I think:), but now seperator must preceed
*types, and that means it's useless. So extra trickery is needed:(

>>> def readln(from, *types, **kws):
... assert kws.keys() in [["seperator"], []]
... seperator = kws.get("seperator", None)


Jeff Shannon

unread,
May 24, 2002, 2:44:52 PM5/24/02
to
In article <acjjmf$rgf$0...@216.39.172.122>, Bengt Richter says...

> 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.

Just skimming, so I may have missed something, but ISTM that this
method of doing things is very fragile. You can use this to
rebind *global* variables... but once you put those variables
(and the input call) inside of another function, suddenly it
breaks for no apparent reason. This will be very surprising to
students who probably won't understand all of the hidden
complexities of global vs local namespaces.

As Bengt says, this is shoehorning ideas that don't fit well into
Python. I think that it is better for the students if they learn
Python conventions instead of Pascal conventions poorly
translated into Python. (If you really want them to learn the
Pascal conventions, then keep teaching them Pascal. <wink>)

My loose translation of the readin() function would be something
like this:

def readin(prompt, numtype=int):
data = raw_input(prompt)
numbers = data.split()
return map(numtype, numbers)

This will allow any number of whitespace-separated numbers to be
entered, and will return all of them in a list. It defaults to
returning ints, but can be used to return floats or longs or
complex by using the optional second argument. I would argue
that this teaches *better* programming practice than the Pascal
version, since it doesn't depend on side-effects to the
function's arguments. <wink>

Some modification could be made to this to allow it to read from
a file, too. Personally, though, I'd be tempted to separate the
(string) input retrieval from the processing. This makes it a
two-step operation, but it's clear what's going on.

def process_input(data, numtype=int):
numbers = data.split()
return map(numtype, numbers)

x, y = process_input(raw_input("Enter two numbers: "))
I, j, k = process_input(infile.readline())

(As a disclaimer, I'm not really familiar with Pascal, so please
take all my comments regarding it in the lighthearted vein in
which they are intended.)

--

Jeff Shannon
Technician/Programmer
Credit International

Andrei Kulakov

unread,
May 24, 2002, 11:05:32 PM5/24/02
to
In article <mailman.102206541...@python.org>,
Michael Williams wrote:
Here's my implementation:


# Note def. args should follow equal sign without a space
def n(f=None, prompt='> ', sep=' '):
"""Read in and convert properly some number of numeric
variables.
"""

if not f:
line = raw_input(prompt)
else:
line = f.readline()

vals = line.split(sep)

newvals = []
for val in vals:
try:
val = int(val)
except ValueError:
val = float(val)
newvals.append(val)

if len(newvals) == 1:
return newvals[0]
else:
return newvals

>>> x = test.n()
> 5
>>> x
5
>>> x = test.n()
> 5 10
>>> x
[5, 10]
>>> x, z
([5, 10], 101)
>>> x, z = test.n()
> 5
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: unpack non-sequence
>>> x, z = test.n()
> 5 5 10
Traceback (most recent call last):
File "<stdin>", line 1, in ?
ValueError: unpack list of wrong size

The only part where it's worse than pascal version is that if you ask
for one var but enter more than one you'll get a list instead of
an error. But I don't think it's a big deal because you'll see
immediately you've got a list.

I'm pretty sure something like this won't make it into standart
distribution because it's a niche function. It simply don't
belong there. I never used Pascal but afaik it was specifically
created for the education niche; Python is an omnivore.

It *does* belong in a python directory like Vaults of Parnassus
at www.vaults.ca or could be included in some sort of educational package.

HTH,

- Andrei


>
> 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?
>
> 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.
>


--
Cymbaline: intelligent learning mp3 player - python, linux, console.
get it at: cy.silmarill.org

Giles Brown

unread,
May 25, 2002, 9:10:29 AM5/25/02
to
Andrei Kulakov <a...@silmarill.org> wrote in message news:<slrnaetl...@ak.silmarill.org>...

> In article <mailman.102206541...@python.org>,
> Michael Williams 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 ###########
[OP code snipped]

Heres another variant where you specify the types of the input as a list
of conversion functions.

def readln(file_, converters, *splitargs):
fields = file_.readline().split(*splitargs)
assert len(fields) == len(converters)
result = []
for field, converter in zip(fields, converters):
result.append(converter(field))
return result

# Example usage
x, y = readln(open("myfile.txt"), [int, float])
a, b, c = readln(sys.stdin, [str, float, long], ',')

Steven Majewski

unread,
May 25, 2002, 11:19:30 AM5/25/02
to

On 25 May 2002, Giles Brown wrote:

> result = []
> for field, converter in zip(fields, converters):
> result.append(converter(field))
> return result

The above is done better as:

return map(converters, fields)


-- Steve Majewski


Kragen Sitaker

unread,
May 25, 2002, 2:05:39 PM5/25/02
to

In what version of Python?

In 2.1.1 I get an error trying that:
>>> map([str, str, lambda x: x * 3], [1, 2, 3])


Traceback (most recent call last):
File "<stdin>", line 1, in ?

TypeError: object of type 'list' is not callable

I guess you meant map(lambda x, y: x(y), converters, fields), and I
agree that would be better. I'm quite surprised to find that lambda
x, y: x(y) doesn't have a name in the operator module; 'apply' is
almost right, but not quite.

The listcomp equivalent would be
[x(y) for x, y in zip(converters, fields)]
and I think that's even better.

Michael Williams

unread,
May 29, 2002, 9:20:39 AM5/29/02
to
On Wed, 22 May 2002 12:03:20 +0100, Michael Williams wrote:
> 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.

Dear all,

Sorry about my total failure to respond to any of your kind and helpful
suggestions. I can adsure you that I have read them all, and will be
collating the results in the dissertation I am producing on Python's
suitability as a teaching language. However, a time-crunch has meant It has
not been possible for me to respond in detail to all of your comments.

In summary, I tend to agree with most of you when you say that we should
teach the ``Python way'', but I need to think about it in more detail.

So this is just to let you know that I'm still alive, and will be posting in
couple of weeks with a) the URL of my dissertation and b) some specific
points that I've asked for comments on this newsgroup.

See you on the other side,
--
Michael

0 new messages