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

Implicit lists

2 views
Skip to first unread message

Dale Strickland-Clark

unread,
Jan 30, 2003, 9:41:33 AM1/30/03
to
In many places, often small utility functions, I find myself using the
form:

lst = maybeAnArg
if type(lst) not in (list, tuple)
lst = [lst]
for x in lst:
whatever

or

lst = maybeAnArg
if type(lst) not in (list, tuple)
lst = [lst]
mangle = [x for x in lst if whatever]


In other words, I want to be able to treat an argument of arbitrary
type as a list of 1 if it isn't already a list.

Has anyone got a neater way of doing this?

Thanks

--
Dale Strickland-Clark
Riverhall Systems Ltd

Alex Martelli

unread,
Jan 30, 2003, 10:19:30 AM1/30/03
to
Dale Strickland-Clark wrote:

> In many places, often small utility functions, I find myself using the
> form:
>
> lst = maybeAnArg
> if type(lst) not in (list, tuple)
> lst = [lst]
> for x in lst:
> whatever
>
> or
>
> lst = maybeAnArg
> if type(lst) not in (list, tuple)
> lst = [lst]
> mangle = [x for x in lst if whatever]
>
>
> In other words, I want to be able to treat an argument of arbitrary
> type as a list of 1 if it isn't already a list.
>
> Has anyone got a neater way of doing this?

Sure:

for x in aslist(list):
whatever(x)


How aslist is written is obviously quite secondary (at least,
I _hope_ that's obvious...) -- all of its USES will be just as
neat as the above example.

If it turns out (as would seem to be the case) that what you
need is actually an iterator (since you want to treat tuples
as "lists", you ain't gonna be able to append, sort, &c
anyway) then I'd name the auxiliary function "iteron" or
something like that. In my experience, what I generally
want is:
treat strings, and all string-like objects, as non-sequences
otherwise, iterate directly on sequences and other iterables
"other iterables" is debatable (files, dictionaries, ...) but
that's how I've generally found things work best -- easy enough
to tweak it to your liking otherwise. So, for example:

def iteron(something):
# string-like objects: treat as non-sequences
try: something+''
except TypeError: pass
else:
yield something
return
# other sequences
try:
for x in something: yield x
except TypeError:
yield something


Alex

Peter Hansen

unread,
Jan 30, 2003, 10:37:52 AM1/30/03
to
Dale Strickland-Clark wrote:
>
> In many places, often small utility functions, I find myself using the
> form:
>
> lst = maybeAnArg
> if type(lst) not in (list, tuple)
> lst = [lst]
> for x in lst:
> whatever
[...]
> In other words, I want to be able to treat an argument of arbitrary
> type as a list of 1 if it isn't already a list.
>
> Has anyone got a neater way of doing this?

Define "neater". :-)

This is probably a better approach in several ways:

def ensureList(thing):
try:
return list(thing)
except TypeError:
return [thing]


-Peter

Dale Strickland-Clark

unread,
Jan 30, 2003, 10:39:13 AM1/30/03
to
Alex Martelli <al...@aleax.it> wrote:

Thanks for that. I was hoping for something that didn't require an
extra module but I guess a single general purpose function wouldn't be
so bad.

Dale Strickland-Clark

unread,
Jan 30, 2003, 10:48:58 AM1/30/03
to
Peter Hansen <pe...@engcorp.com> wrote:

>This is probably a better approach in several ways:
>
>def ensureList(thing):
> try:
> return list(thing)
> except TypeError:
> return [thing]
>
>
>-Peter

Except it doesn't work :-)

>>> list('abc')
['a', 'b', 'c']

holger krekel

unread,
Jan 30, 2003, 10:07:42 AM1/30/03
to
Dale Strickland-Clark wrote:
> In many places, often small utility functions, I find myself using the
> form:
>
> lst = maybeAnArg
> if type(lst) not in (list, tuple)
> lst = [lst]
> for x in lst:
> whatever

hmmm. Maybe with a general-purpose

def elems(*args):
l = []
for arg in args:
try: l.extend(arg)
except TypeError: l.append(arg)
return l

you might like to write:

for x in elems(lst):

and use it also to iterate consecutively over the
elements of more than one list or value:

for x in elems(lst1, lst2, 42):

HTH,

holger

Mark McEahern

unread,
Jan 30, 2003, 10:37:08 AM1/30/03
to
[Alex Martelli]

> for x in aslist(list):
> whatever(x)
>
>
> How aslist is written is obviously quite secondary (at least,
> I _hope_ that's obvious...) -- all of its USES will be just as
> neat as the above example.

As a complement to what Alex wrote, I'd suggest writing unit tests that
document how you expect aslist to work with various types; e.g.,

import unittest

class test(unittest.TestCase):

def test_stringlike(self):
s = "foo"
alist = [for x in aslist(s)]
self.assertEquals(alist, [s])

...

etc.

// m

-


Peter Hansen

unread,
Jan 30, 2003, 11:20:21 AM1/30/03
to
Dale Strickland-Clark wrote:
>
> Peter Hansen <pe...@engcorp.com> wrote:
>
> >This is probably a better approach in several ways:
> >
> >def ensureList(thing):
> > try:
> > return list(thing)
> > except TypeError:
> > return [thing]
> >
> >
> >-Peter
>
> Except it doesn't work :-)
>
> >>> list('abc')
> ['a', 'b', 'c']

Of course it does that. A string is a sequence. So is
a tuple. The OP didn't specify what behaviour he wanted
in this case, so "it works" until he clarifies his
requirements. :-)

(Your example showed tuples and lists being treated as lists,
but your "in other words" description said "if it isn't already
a list", and that comment includes strings as much as it does
tuples.)

-Peter

Alex Martelli

unread,
Jan 30, 2003, 11:23:47 AM1/30/03
to
Mark McEahern wrote:
...

> As a complement to what Alex wrote, I'd suggest writing unit tests that
> document how you expect aslist to work with various types; e.g.,

VERY good suggestion. Yes yes yes. ALWAYS write unit tests. I'll
have to remember to add some next time I give some such example,
just to remind everybody -- so, thanks for making the point!


Alex

Alex Martelli

unread,
Jan 30, 2003, 11:29:25 AM1/30/03
to
holger krekel wrote:
...

> hmmm. Maybe with a general-purpose
>
> def elems(*args):
> l = []
> for arg in args:
> try: l.extend(arg)
> except TypeError: l.append(arg)
> return l

Alas, doesn't meet specs...:

[alex@lancelot mydata]$ python2.2
Python 2.2.2 (#1, Oct 24 2002, 11:43:01)
[GCC 2.96 20000731 (Mandrake Linux 8.2 2.96-0.76mdk)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> a=[]
>>> a.extend('ciao')
>>> a
['c', 'i', 'a', 'o']
>>>

basically, l.extend now accepts all iterables. Handy in
most cases, but hardly ever what one wants for strings.


Alex

Mike Meyer

unread,
Jan 30, 2003, 11:30:32 AM1/30/03
to
Alex Martelli <al...@aleax.it> writes:

> def iteron(something):
> # string-like objects: treat as non-sequences
> try: something+''
> except TypeError: pass
> else:
> yield something
> return
> # other sequences
> try:
> for x in something: yield x
> except TypeError:
> yield something

I always thought that exceptions were expensive. Which makes me wonder
why you aren't using "isinstance(something, str)" for that first test.

Thanks,
<mike
--
Mike Meyer <m...@mired.org> http://www.mired.org/home/mwm/
Independent WWW/Perforce/FreeBSD/Unix consultant, email for more information.

Laura Creighton

unread,
Jan 30, 2003, 11:48:29 AM1/30/03
to
> Alex Martelli <al...@aleax.it> writes:
>
> > def iteron(something):
> > # string-like objects: treat as non-sequences
> > try: something+''
> > except TypeError: pass
> > else:
> > yield something
> > return
> > # other sequences
> > try:
> > for x in something: yield x
> > except TypeError:
> > yield something
>
> I always thought that exceptions were expensive. Which makes me wonder
> why you aren't using "isinstance(something, str)" for that first test.
>
> Thanks,
> <mike

> Mike Meyer <m...@mired.org> http://www.mired.org/home/mwm/


> Independent WWW/Perforce/FreeBSD/Unix consultant, email for more information.

> --
> http://mail.python.org/mailman/listinfo/python-list

So when I pass it a string-like object I have made, the code just works.

Laura

Alex Martelli

unread,
Jan 30, 2003, 12:55:17 PM1/30/03
to
Mike Meyer wrote:

> Alex Martelli <al...@aleax.it> writes:
>
>> def iteron(something):
>> # string-like objects: treat as non-sequences
>> try: something+''
>> except TypeError: pass
>> else:
>> yield something
>> return
>> # other sequences
>> try:
>> for x in something: yield x
>> except TypeError:
>> yield something
>
> I always thought that exceptions were expensive. Which makes me wonder

Let's check, e.g. with this code:


import time

def timeit(func):
repts = [None] * 1000000
start = time.clock()
for x in repts:
func()
stend = time.clock()
return stend-start

def isin_y():
return isinstance('ciao', str)

def isin_n():
return isinstance(23, str)

def exce_y():
try:
return 'ciao'+''
except:
return False

def exce_n():
try:
return 23+''
except:
return False

for f in isin_y, isin_n, exce_y, exce_n:
t = timeit(f)
print "%.2f %s" % (t, f.__name__)


On my years-old PC, we measure:

[alex@lancelot ball]$ python -O tim.py
1.47 isin_y
1.44 isin_n
0.86 exce_y
13.14 exce_n
[alex@lancelot ball]$

So, yes: exceptions are expensive *when taken*
(they're cheaper than isinstance when NOT taken).
Specifically, a taken exceptions seems to cost
over 10 microseconds on my old PC.

How much of a problem are those 10 microseconds
going to be in the typical use of this function?

for item in iteron(whatever):
something(item)

my guess is -- probably not much. If profiling
later tells me this isn't the case, then I can
get into studying how often various kinds of
objects get passed to function iteron and tune
it accordingly -- specialcasing some kinds of
arguments, if need be -- no hurry.

Premature optimization is the root of all evil
in programming, remember...


> why you aren't using "isinstance(something, str)" for that first test.

That wouldn't catch unicode objects, instances of UserString,
and other such string-like objects.


Alex

holger krekel

unread,
Jan 30, 2003, 12:00:17 PM1/30/03
to
Alex Martelli wrote:
> holger krekel wrote:
> ...
> > hmmm. Maybe with a general-purpose
> >
> > def elems(*args):
> > l = []
> > for arg in args:
> > try: l.extend(arg)
> > except TypeError: l.append(arg)
> > return l
>
> Alas, doesn't meet specs...:

I presume you refer to this part

[dale]


In other words, I want to be able to treat an argument of arbitrary
type as a list of 1 if it isn't already a list.

which isn't an overly explicit specification. In particular,
it doesn't imply that strings are to be excluded.

But i agree that grouping into 'non-iterables except strings and
unicode' on one side and everything else on the other side most
often makes sense.

> [alex@lancelot mydata]$ python2.2
> Python 2.2.2 (#1, Oct 24 2002, 11:43:01)
> [GCC 2.96 20000731 (Mandrake Linux 8.2 2.96-0.76mdk)] on linux2
> Type "help", "copyright", "credits" or "license" for more information.
> >>> a=[]
> >>> a.extend('ciao')
> >>> a
> ['c', 'i', 'a', 'o']
> >>>
>
> basically, l.extend now accepts all iterables.

Sure. It's been like this since Python-2.1. Previously
it threw an TypeError.

ciao,

holger

Mark McEahern

unread,
Jan 30, 2003, 12:08:22 PM1/30/03
to
[Mike Meyer]

> I always thought that exceptions were expensive. Which makes me wonder
> why you aren't using "isinstance(something, str)" for that first test.

It's always best to test for performance assumptions. When I run the
following, I get:

$ junk.py
aslist_except: 0.12
aslist_isinstance: 0.22
.
----------------------------------------------------------------------
Ran 1 tests in 3.182s

OK

ymmv,

// m

#!/usr/bin/env python

from __future__ import generators
import unittest
from time import clock

def time_it(f, *args):
time_in = clock()
result = f(*args)
time_out = clock()
return result, time_out - time_in

def aslist_isinstance(item):
if isinstance(item, (list, tuple)):
for x in item:
yield x
else:
yield item

def aslist_except(item):
try:
item + ''
except TypeError:
for x in item:
yield x
else:
yield item

class test(unittest.TestCase):

def test(self):

items = (('s', ['s']),
(range(10), range(10)),
((1, 2, 3), [1, 2, 3]))

functions = (aslist_except, aslist_isinstance)
timing = {}

count = 10000

for c in xrange(count):
for f in functions:
timing.setdefault(f, 0)
for item, expected in items:
result_iterator, elapsed = time_it(f, item)
timing[f] += elapsed
result = [x for x in result_iterator]
self.assertEquals(result, expected)

for k, v in timing.items():
print '%s: %0.2f' % (k.func_name, v)

if __name__ == '__main__':
unittest.main()

-


Alex Martelli

unread,
Jan 30, 2003, 12:23:54 PM1/30/03
to
On Thursday 30 January 2003 06:00 pm, holger krekel wrote:
...

> > Alas, doesn't meet specs...:
>
> I presume you refer to this part
>
> [dale]
> In other words, I want to be able to treat an argument of arbitrary
> type as a list of 1 if it isn't already a list.
>
> which isn't an overly explicit specification. In particular,
> it doesn't imply that strings are to be excluded.

It says that strings are to be treated as a list of 1. Not sure what
you mean by "excluded". The OP also gave an example that did
not meet his own specs (he went out of his way to treat _tuples_
NOT as lists of one item).

> But i agree that grouping into 'non-iterables except strings and
> unicode' on one side and everything else on the other side most
> often makes sense.

Yes, as long as you also include "instances of UserString and
the like" among "strings and unicode". Personally, I prefer to
talk of "string-like objects", and my favourite way to check if
an object is string-like is to see if thatobject+'' raises an
exception -- if it doesn't, it's stringlike enough for me.


Alex


holger krekel

unread,
Jan 30, 2003, 12:53:21 PM1/30/03
to
Laura Creighton wrote:
> > Alex Martelli <al...@aleax.it> writes:
> >
> > > def iteron(something):
> > > # string-like objects: treat as non-sequences
> > > try: something+''
> > > except TypeError: pass
> > > else:
> > > yield something
> > > return
> > > # other sequences
> > > try:
> > > for x in something: yield x
> > > except TypeError:
> > > yield something
> >
> > I always thought that exceptions were expensive. Which makes me wonder
> > why you aren't using "isinstance(something, str)" for that first test.
> >
> > Thanks,
> > <mike
>
> > Mike Meyer <m...@mired.org> http://www.mired.org/home/mwm/
> > Independent WWW/Perforce/FreeBSD/Unix consultant, email for more information.
> > --
> > http://mail.python.org/mailman/listinfo/python-list
>
> So when I pass it a string-like object I have made, the code just works.

In fact, your class needs to provide a

def __add__(self, arg):

method which needs to accepts strings. If you are a true sequence (tm)
then instead you are forbidden to accept strings. This is too much
non-obvious impliciteness IMHO.

I think it's safer to skip "iteration" with an explicit

isinstance(arg, (str, unicode))

check. It's also simpler to read and understand.

regards,

holger

Alex Martelli

unread,
Jan 30, 2003, 1:12:57 PM1/30/03
to
holger krekel wrote:
...

> I think it's safer to skip "iteration" with an explicit
>
> isinstance(arg, (str, unicode))
>
> check. It's also simpler to read and understand.

And it breaks *EVERY* use of UserString -- a module in the
standard Python library, even!!! -- not to mention user-coded
string-like types and classes. *shudder*.

A class is string-like if it acts like a string. If a
class lets its instances be concatenated to strings, it's
pretty string-like -- all of str, unicode and UserString
do, and no other type that's either built-in or the standard
library. Making this easily checked feature the single
discriminant of "string-likeness" appears quite OK to me
for most purposes. You can wrap the test into highly readable
form by the simple trick of defining a function;-)...e.g.:

def isstringlike(x):
try: return x+'!'
except TypeError: return False


Alex

Mel Wilson

unread,
Jan 30, 2003, 1:10:01 PM1/30/03
to
In article <m4di3v4koufdbf4e2...@4ax.com>,

Dale Strickland-Clark <da...@riverhall.NOTHANKS.co.uk> wrote:
>In many places, often small utility functions, I find myself using the
>form:
>
>lst = maybeAnArg
>if type(lst) not in (list, tuple)
> lst = [lst]
>for x in lst:
> whatever
>
[ ... ]

>
>In other words, I want to be able to treat an argument of arbitrary
>type as a list of 1 if it isn't already a list.
>Has anyone got a neater way of doing this?

I don't see anything wrong with this as it stands; it
does just what it says and I presume it does just what you
want. Nobody has to page 10 or 15 screens up or down in the
source, or consult library docs, to see what's going to
happen.

The huge responsibility in using a strongly- and
dynamically-typed language is that the program has to line
its data up in the proper types right from the start.
Without that, any attempt later to patch things up is going
to look bizarre. (e.g. string: data item or sequence of
characters? Tune in at 11 ..)

Whether an arbitrary object with a __getitem__ method or
an iterator, or a __str__ method for that matter, is meant
to be taken as its discrete self or as mere wrapping to
deliver its various contents in a particular situation is an
open question. Application-dependent, I say.

Regards. Mel.

David Eppstein

unread,
Jan 30, 2003, 2:17:48 PM1/30/03
to
In article <5pWO+ks/KzLb...@the-wire.com>,
mwi...@the-wire.com (Mel Wilson) wrote:

> In article <m4di3v4koufdbf4e2...@4ax.com>,
> Dale Strickland-Clark <da...@riverhall.NOTHANKS.co.uk> wrote:
> >In many places, often small utility functions, I find myself using the
> >form:
> >
> >lst = maybeAnArg
> >if type(lst) not in (list, tuple)
> > lst = [lst]
> >for x in lst:
> > whatever
> >
> [ ... ]
> >
> >In other words, I want to be able to treat an argument of arbitrary
> >type as a list of 1 if it isn't already a list.
> >Has anyone got a neater way of doing this?
>
> I don't see anything wrong with this as it stands; it
> does just what it says and I presume it does just what you
> want.

One problem with it as it stands is that it won't work when lst is a
simple generator.

how about:

try:
lst = iter(lst)
except TypeError:
lst = [lst]

--
David Eppstein UC Irvine Dept. of Information & Computer Science
epps...@ics.uci.edu http://www.ics.uci.edu/~eppstein/

A. Lloyd Flanagan

unread,
Jan 30, 2003, 2:24:23 PM1/30/03
to
Dale Strickland-Clark <da...@riverhall.NOTHANKS.co.uk> wrote in message news:<m4di3v4koufdbf4e2...@4ax.com>...

> In many places, often small utility functions, I find myself using the
> form:
>
> lst = maybeAnArg
> if type(lst) not in (list, tuple)
> lst = [lst]
> for x in lst:
> whatever
>
> Has anyone got a neater way of doing this?
>

In python, if you ever try to explicitly check the type of something,
you're almost certainly doing it wrong. You don't care if the thing
is a list, usually, you want to know if it's a sequence, whether a
list, tuple, set, or some user-defined type. So try subscripting it:

def aslist(x):
try:
x[0]
except TypeError:
return [x]
return x

or just:

try:


mangle = [x for x in lst if whatever]

except TypeError:

holger krekel

unread,
Jan 30, 2003, 2:23:06 PM1/30/03
to
Alex Martelli wrote:
> holger krekel wrote:
> ...
> > I think it's safer to skip "iteration" with an explicit
> >
> > isinstance(arg, (str, unicode))
> >
> > check. It's also simpler to read and understand.
>
> And it breaks *EVERY* use of UserString -- a module in the
> standard Python library, even!!! -- not to mention user-coded
> string-like types and classes. *shudder*.

sorry, but fewer scream-emoticons would suffice to make
your point.

> A class is string-like if it acts like a string.

The problem is how to catch the string-likeness in
explicit terms. You propose to execute

obj + 'somestring'

and see if it throws a type error. To me this
definition of 'string-like' makes some sense but
is arbitrary. (At least you get problems with
iterable objects that define an __add__ method which
accepts strings, you know that).

However, it feels like an implicit kind of type-check.
The general pythonic "avoid checking for types, just
work with behaviour and catch exceptions" does not
really apply here. We want to *prevent* something
instead of react to erranonous behaviour.

> def isstringlike(x):
> try: return x+'!'
> except TypeError: return False

This will often work, as you said. But if this behaviour
is (in some way) exposed in a public API then users
might get strange surprises when passing in their own stuff.
You never know what strange classes might get passed in.
Maybe it's a "filename" class that allows iteration and
allows __add__ with strings?

regards,

holger

Dale Strickland-Clark

unread,
Jan 30, 2003, 3:01:36 PM1/30/03
to
David Eppstein <epps...@ics.uci.edu> wrote:

>
>One problem with it as it stands is that it won't work when lst is a
>simple generator.
>
>how about:
>
>try:
> lst = iter(lst)
>except TypeError:
> lst = [lst]

Unfortunately, iter(lst) will still convert a string into a list of
chars.

Dale Strickland-Clark

unread,
Jan 30, 2003, 3:03:25 PM1/30/03
to
alloydf...@attbi.com (A. Lloyd Flanagan) wrote:

>In python, if you ever try to explicitly check the type of something,
>you're almost certainly doing it wrong. You don't care if the thing
>is a list, usually, you want to know if it's a sequence, whether a
>list, tuple, set, or some user-defined type. So try subscripting it:
>
>def aslist(x):
> try:
> x[0]
> except TypeError:
> return [x]
> return x
>
>or just:
>
>try:
> mangle = [x for x in lst if whatever]
>except TypeError:
> mangle = [x for x in [lst] if whatever]

x[0] takes the first char of a string and iterating over a string
gives you each character.

Not what I'm after, I'm afraid.

Dale Strickland-Clark

unread,
Jan 30, 2003, 3:15:11 PM1/30/03
to
Alex Martelli <al...@aleax.it> wrote:

>A class is string-like if it acts like a string. If a
>class lets its instances be concatenated to strings, it's
>pretty string-like -- all of str, unicode and UserString
>do, and no other type that's either built-in or the standard
>library. Making this easily checked feature the single
>discriminant of "string-likeness" appears quite OK to me
>for most purposes. You can wrap the test into highly readable
>form by the simple trick of defining a function;-)...e.g.:
>
>def isstringlike(x):
> try: return x+'!'
> except TypeError: return False
>

There's a bit of a language deficiency here if you have to resort to
this type of obscure trick. To test for strings, I have always coded
either of:

type(var) in (str, unicode)
isinstance(var, (str, unicode))

But, where it mattered, I have also been able to make sure that my
string-like objects are subclasses of str.

It makes you think.

Dale Strickland-Clark

unread,
Jan 30, 2003, 3:23:05 PM1/30/03
to
Alex Martelli <al...@aleax.it> wrote:

>It says that strings are to be treated as a list of 1. Not sure what
>you mean by "excluded". The OP also gave an example that did
>not meet his own specs (he went out of his way to treat _tuples_
>NOT as lists of one item).
>

Sorry. I was using the term 'list' in it's general sense, rather than
the Python definition. In other words, anything that is normally
represented by items seperated by commas. So Python lists and tuples
are in, str and unicode (and anything else normally represented by an
unbroken string of characters) are out.

I'm sure even that definition has holes but maybe it's clearer.

Alex Martelli

unread,
Jan 30, 2003, 4:04:51 PM1/30/03
to
Dale Strickland-Clark wrote:

Actually quite clear, but finding holes is more fun:-).

A dictionary, {1:2, 3:4, 5:6}, is a list of items separated
by commas (the items are (1,2), (3,4), ...) -- how do you
want to treat it?


Alex

Chirayu Krishnappa

unread,
Jan 30, 2003, 4:13:28 PM1/30/03
to
Hi,

You've definitely received a lot of responses on how to make it a
general purpose function.

I'd like to ask how you get these maybe arg or list things in the
first place. i presume they are the result of a function call. do you
have too many such functions? (generally well written function which
might return a list will return a list even for a single element so
you can have uniformly written code. the cgi module's FieldStorage
class' getitem() function is a notable exception.)

if you find that you need to do this for just a small bunch of
functions - perhaps you could write wrappers for those functions to
always return a list and use them instead.

Chirayu.

On Thu, 30 Jan 2003 14:41:33 +0000, Dale Strickland-Clark
<da...@riverhall.NOTHANKS.co.uk> wrote:

>In many places, often small utility functions, I find myself using the
>form:
>
>lst = maybeAnArg
>if type(lst) not in (list, tuple)
> lst = [lst]
>for x in lst:
> whatever
>

>or


>
>lst = maybeAnArg
>if type(lst) not in (list, tuple)
> lst = [lst]

>mangle = [x for x in lst if whatever]
>
>

>In other words, I want to be able to treat an argument of arbitrary
>type as a list of 1 if it isn't already a list.
>

>Has anyone got a neater way of doing this?
>

>Thanks

Alex Martelli

unread,
Jan 30, 2003, 4:28:33 PM1/30/03
to
holger krekel wrote:

> Alex Martelli wrote:
>> holger krekel wrote:
>> ...
>> > I think it's safer to skip "iteration" with an explicit
>> >
>> > isinstance(arg, (str, unicode))
>> >
>> > check. It's also simpler to read and understand.
>>
>> And it breaks *EVERY* use of UserString -- a module in the
>> standard Python library, even!!! -- not to mention user-coded
>> string-like types and classes. *shudder*.
>
> sorry, but fewer scream-emoticons would suffice to make
> your point.

Not for an Italian forced to express himself _without_ moving
his hands about.

> However, it feels like an implicit kind of type-check.

Your feelings are a bit off here -- because types work
differently, e.g. UserString has NO type connection with
str -- but not by much: what it is is a PROTOCOL-check --
Python has no explicit notion of protocol but makes
substantial use of the notion under the cover (and if we
had protocol-adaptation in the language/library, life
would be sweeter).

Python does define (loosely) a sequence protocol, but,
alas for this case, strings and string-like thingies
also meet the sequence protocol and we DON'T want to
treat them as sequences in this context (and most other
application cases, in my limited experience and humble
opinion). So we need to synthesize a "nonstringlike sequence
protocol" -- and for THAT, we need a "stringlike" protocol.

Identifying "stringlike" with "can be catenated to a
string" may seem like a daring leap, but think about
it for a second: why WOULD a non-stringlike sequence
ever WANT to be catenable to a string? Your example:

> Maybe it's a "filename" class that allows iteration and
> allows __add__ with strings?

...seems somewhat far-fetched to me. For any two
catenable sequences X and Y, given:

def catall(*n):
for seq in n:
for item in seq:
yield item

surely catall(X, Y) should == catall(X + Y). How
would this reasonable protocol constraint on sequence
catenation be met when X instantiates your hypothetical
class and Y is a string?


Alex

David Eppstein

unread,
Jan 30, 2003, 4:33:25 PM1/30/03
to
In article <Trg_9.106594$0v.30...@news1.tin.it>,
Alex Martelli <al...@aleax.it> wrote:

> Actually quite clear, but finding holes is more fun:-).
>
> A dictionary, {1:2, 3:4, 5:6}, is a list of items separated
> by commas (the items are (1,2), (3,4), ...) -- how do you
> want to treat it?

My interpretation is sort of the opposite: a dictionary can represent
any function with a finite domain, and a list is a special case of this
where the domain of the function is a range of integers.

Of course this view breaks down once you start modifying dicts or lists,
since the ways they change are not so similar...

Dale Strickland-Clark

unread,
Jan 30, 2003, 5:03:57 PM1/30/03
to
Chirayu Krishnappa <thepho...@gmx.net> wrote:

>I'd like to ask how you get these maybe arg or list things in the
>first place. i presume they are the result of a function call. do you
>have too many such functions? (generally well written function which
>might return a list will return a list even for a single element so
>you can have uniformly written code. the cgi module's FieldStorage
>class' getitem() function is a notable exception.)
>
>if you find that you need to do this for just a small bunch of
>functions - perhaps you could write wrappers for those functions to
>always return a list and use them instead.
>
>Chirayu.
>

I've considered that but in most situations it is cleaner and more
natural to supply a single arg rather than a list of one item.

For a quickly invented example, consider a database class that accepts
a 'keyField' parameter. In most cases, a single value will suffice.
However some tables need two key values.

dbTable('tblMembers', keyField='memberID')
dbTable('tblPermissions', keyField=('memberID', 'permissionID'))

Turning the first keyField into a single item list (or tuple) looks
untidy.

Greg Ewing (using news.cis.dfn.de)

unread,
Jan 30, 2003, 7:46:09 PM1/30/03
to
Dale Strickland-Clark wrote:

> dbTable('tblMembers', keyField='memberID')
> dbTable('tblPermissions', keyField=('memberID', 'permissionID'))
>
> Turning the first keyField into a single item list (or tuple) looks
> untidy.


You could use two different keyword parameters
depending on whether you want to pass a single
item or a list. e.g.

dbTable('tblMembers', keyField = 'memberID')
dbTable('tblPermissions', keyFields = ('memberID', 'permissionID'))

--
Greg

holger krekel

unread,
Jan 30, 2003, 7:06:12 PM1/30/03
to
Alex Martelli wrote:
> holger krekel wrote:
> > Alex Martelli wrote:
> >> holger krekel wrote:
> >> ...
> >> > I think it's safer to skip "iteration" with an explicit
> >> >
> >> > isinstance(arg, (str, unicode))
> >> >
> >> > check. It's also simpler to read and understand.
> >>
> >> And it breaks *EVERY* use of UserString -- a module in the
> >> standard Python library, even!!! -- not to mention user-coded
> >> string-like types and classes. *shudder*.
> >
> > sorry, but fewer scream-emoticons would suffice to make
> > your point.
>
> Not for an Italian forced to express himself _without_ moving
> his hands about.

Still it can and actually does setup discussion hierarchies.

> > However, it feels like an implicit kind of type-check.
>
> Your feelings are a bit off here -- because types work
> differently, e.g. UserString has NO type connection with
> str -- but not by much: what it is is a PROTOCOL-check --

Right, that's what i meant with 'kind of'. Sometimes i try
to not introduce too many concepts at once. So let me
restate my key point from the last mail:

The general pythonic "avoid checking for types or existence
of methods. instead just try and catch exceptions" does
not always apply when you want to *prevent* something


instead of react to erranonous behaviour.

It's actually quite common to check for str/unicode
even in the standard lib and there is types.StringTypes
kind-of-stating that. Of course you could argue that
this is all wrong and everybody should check
for obj+"" not throwing an exception, instead.

Note, though, that you can not refer to common practice like

try:
return somedict[key]
except KeyError:
...

because this *positively* checks for a problem and
takes appropriate action. Whereas

try:
obj + ""
except TypeError:
# ...

tries to infer knowledge aka "must be some string"
from a *missing* problem/exception. That's a lot
weaker and can produce surprises.

holger

Bengt Richter

unread,
Jan 30, 2003, 8:51:13 PM1/30/03
to

It may be conventionally reasonable when X and Y are the same
type of sequence, but IMO it's too constraining to dictate how
a sequence-implementing class should implement '+' when the other
sequence is another type. And ISTM that is what Holger's example
is about. E.g, it may be that the class will want to coerce the
other argument to its own type before the addition. E.g.,
(this happens to be a list of strings, which may confuse somewhat,
but it could be a list of special ojects that can only be built
from certain elements, so I am limiting constructor args by
brute force type checks here (not even allowing unicode etc):

====< stringlist.py >===============================
from __future__ import generators
class Stringlist:
def __init__(self, x):
# check for string, list of strings, or Stringlist instance
if isinstance(x, str): x = [x]
if (isinstance(x, list) and
reduce(lambda n,item: n+isinstance(item,str), x, 0)==len(x)
):
self.sl = x[:]
elif isinstance(x, self.__class__):
self.sl = x.sl[:]
else:
raise ValueError, 'Stringlist ctor requires stringlists or strings'
def __len___(self): return len(self.sl)
def __getitem__(self, i): return self.sl[i]
def __setitem__(self, i, v): self.sl[i] = stringlist(v)[0]
def __add__(self, other):
if isinstance(other, self.__class__):
return self.__class__(self.sl+other.sl)
return self + self.__class__(other)
def __repr__(self): return '<Stringlist %s>' % `self.sl`

if __name__ == '__main__':

# [Alex Martelli]
#...seems somewhat far-fetched to me. For any two
#catenable sequences X and Y, given:

#( from __future__ import generators at top)


def catall(*n):
for seq in n:
for item in seq:
yield item

#surely catall(X, Y) should == catall(X + Y). How
#would this reasonable protocol constraint on sequence
#catenation be met when X instantiates your hypothetical
#class and Y is a string?

X = Stringlist('hypothetical instance data'.split())
Y = 'a string'

print 'X: %s, Y: %s' % (`X`, `Y`)
print 'X+Y: %s' % `X+Y`
print 'catall(X, Y): %s' % [x for x in catall(X, Y)]
print 'catall(X + Y): %s' % [x for x in catall(X + Y)]
assert [x for x in catall(X, Y)] == [x for x in catall(X + Y)]
====================================================
When run, the results do break the protocol as expected, but I don't
think a Stringlist class that coerces strings to its own type before
doing '+' is unreasonable:

[17:49] C:\pywk\clp>stringlist.py
X: <Stringlist ['hypothetical', 'instance', 'data']>, Y: 'a string'
X+Y: <Stringlist ['hypothetical', 'instance', 'data', 'a string']>
catall(X, Y): ['hypothetical', 'instance', 'data', 'a', ' ', 's', 't', 'r', 'i', 'n', 'g']
catall(X + Y): ['hypothetical', 'instance', 'data', 'a string']
Traceback (most recent call last):
File "C:\pywk\clp\stringlist.py", line 47, in ?
assert [x for x in catall(X, Y)] == [x for x in catall(X + Y)]
AssertionError

(I didn't assume you meant "catall(X, Y) should == catall(X + Y)" literally,
so I changed the assert)

There might be bugs or better ways to implement the above, but I hope it
illustrates the main point.

Welcome back BTW ;-)

Regards,
Bengt Richter

Erik Max Francis

unread,
Jan 30, 2003, 9:27:00 PM1/30/03
to
Dale Strickland-Clark wrote:

> In other words, I want to be able to treat an argument of arbitrary
> type as a list of 1 if it isn't already a list.
>
> Has anyone got a neater way of doing this?

Unfortunately, this technique excludes things that have sequences
interfaces but aren't literally lists or tuples.

--
Erik Max Francis / m...@alcyone.com / http://www.alcyone.com/max/
__ San Jose, CA, USA / 37 20 N 121 53 W / &tSftDotIotE
/ \ The only way to get rid of a temptation is to yield to it.
\__/ Oscar Wilde
Bosskey.net: Quake III Arena / http://www.bosskey.net/q3a/
A personal guide to Quake III Arena.

Bengt Richter

unread,
Jan 30, 2003, 9:51:59 PM1/30/03
to
On 31 Jan 2003 01:51:13 GMT, bo...@oz.net (Bengt Richter) wrote:
[...]
>====< stringlist.py >===============================
[...]

> def __setitem__(self, i, v): self.sl[i] = stringlist(v)[0]
[...]

>There might be bugs or better ways to implement the above, but I hope it
Predictably enough ;-/

[18:51] C:\pywk\clp>diff stringlist.py~ stringlist.py
16c16


< def __setitem__(self, i, v): self.sl[i] = stringlist(v)[0]

---
> def __setitem__(self, i, v): self.sl[i] = self.__class__(v)[0]

I Should have left it out or tested. Fortunately it was misspelled, so
it showed up (don't like the global name route). There may be more ;-)

Regards,
Bengt Richter

Christian Tismer

unread,
Jan 30, 2003, 10:28:47 PM1/30/03
to
holger krekel wrote:

...

> But i agree that grouping into 'non-iterables except strings and
> unicode' on one side and everything else on the other side most
> often makes sense.

In my opinion, the crux of this all is the decision
that strings are iterable.
The number of use cases which I have seen so far is much
shorter than this thread.
Strings are much more often used as solid objects.
I'm pretty sure Guido would remove the sequence-ness
of strings in Python 3000, even to avoid this kind
of problems.

ciao - chris


Christian Tismer

unread,
Jan 30, 2003, 10:36:51 PM1/30/03
to
Alex Martelli wrote:

...

>>But i agree that grouping into 'non-iterables except strings and
>>unicode' on one side and everything else on the other side most
>>often makes sense.
>
>

> Yes, as long as you also include "instances of UserString and
> the like" among "strings and unicode". Personally, I prefer to
> talk of "string-like objects", and my favourite way to check if
> an object is string-like is to see if thatobject+'' raises an
> exception -- if it doesn't, it's stringlike enough for me.

Besides th fact that I believe strings as sequences
should be deprecated, since nobody makes use of it,
but everybody fights it...
... I can't see the relevance of UserString.
Wasn't getting rid of UserAnything one of the reasons
for the introduction of the unified type/class system?
UserString is only a compatibility module, IMHO.

But maybe we could simplify matters by providing something
like a stringlike() inquiry function.
Strings being seen as sequences always have been a PITA
for me, especially in user argument lists vs non lists.

ciao - chris


Christian Tismer

unread,
Jan 30, 2003, 10:50:16 PM1/30/03
to

That's a very clean proposal, avoiding all the mess.
While I have to repeat that I'd really like to
see the sequence-ness of strings to be deprecated.
I have no objection to accept tuple(string) or
list(string) as valid operations to create
sequences from strings.
But I almost never have seen a really useful
application of string's default sequenceness.
Compared to all the mess raised in this thread,
it isn't worth it.

Guido, please make strings into atoms.

why-is-an-integer-not-the-sequence-of-its-digits- ly y'rs - chris


Erik Max Francis

unread,
Jan 30, 2003, 11:19:49 PM1/30/03
to
Dale Strickland-Clark wrote:

> Except it doesn't work :-)

For "doesn't work," you mean "didn't do what I expected it to do for my
own purposes but never indicated to the audience I'm posing the question
to, so they couldn't possibly know." Python emphasizes interfaces more
than types; a string _is_ a sequence (in that it presents a sequence
interface to its users), so if you pass it to a function that expects a
sequence, it should treat it as a sequence.

As I mentioned in an earlier post, I think the problem here is that
you're trying too hard to implement type-based dispatched in a dynamic
language. That generally isn't a good idea (though there's some cases
where it's appropriate), because of the very nature of dynamic languages
-- you should be concentrating not on the _types_ of the objects, but
their behavior.

Wouldn't it be more appropriate to have _two_ functions, one which takes
a sequence, and one which takes a single object (and possibly simply
wraps it in a one-element list and passes it to the sequence-taking
function)?

--
Erik Max Francis / m...@alcyone.com / http://www.alcyone.com/max/
__ San Jose, CA, USA / 37 20 N 121 53 W / &tSftDotIotE

/ \ The great artist is the simplifier.
\__/ Henri Amiel
Sade Deluxe / http://www.sadedeluxe.com/
The ultimate Sade encyclopedia.

Erik Max Francis

unread,
Jan 30, 2003, 11:42:51 PM1/30/03
to
Dale Strickland-Clark wrote:

> Alex Martelli <al...@aleax.it> wrote:
>
> > A class is string-like if it acts like a string. If a
> > class lets its instances be concatenated to strings, it's
> > pretty string-like -- all of str, unicode and UserString
> > do, and no other type that's either built-in or the standard
> > library. Making this easily checked feature the single
> > discriminant of "string-likeness" appears quite OK to me
> > for most purposes. You can wrap the test into highly readable
> > form by the simple trick of defining a function;-)...e.g.:
> >
> > def isstringlike(x):
> > try: return x+'!'
> > except TypeError: return False
>
> There's a bit of a language deficiency here if you have to resort to
> this type of obscure trick.

I disagree. I think having to resort to that "obscure trick" too often
reveals a program design problem, not a language deficiency.

In dynamically-typed languages like Python, overloading functions (which
is I presume the crux of where you're doing this test) based on type
usually isn't a great idea. Usually it's better to have two
functions/methods instead.

I will freely admit to on occasion breaking this dictum myself, but
usually I do it in the reverse way: I'll check if it it's a certain
type, and if not, make the more general assumption; e.g., for a function
intended to include another file in some capacity, I'll might check if
it's a string, then treat that as a filename; if it's not a string, I'll
presume it's a filelike object and just continue on.

Delaney, Timothy

unread,
Jan 30, 2003, 11:01:46 PM1/30/03
to
> From: Christian Tismer [mailto:tis...@tismer.com]

>
> In my opinion, the crux of this all is the decision
> that strings are iterable.
> The number of use cases which I have seen so far is much
> shorter than this thread.
> Strings are much more often used as solid objects.
> I'm pretty sure Guido would remove the sequence-ness
> of strings in Python 3000, even to avoid this kind
> of problems.

Whilst I've always liked that strings are iterable in Python, it does lead
to complexity when you want to treat them specially.

Additionally, the new semantics of 'in' for strings in 2.3 does suggest a
lessening of the sequence-like behaviour of strings.

However, there has been a move to making *more* things iterable, not less.
Whilst in general this is nice, it does mean that there are more
opportunities for invalid objects to be processed by library routines.

I would say, if you absolutely *must* have a list, document as such and do a
typecheck. If someone wants to pass a non-list, they must convert it to a
list by whatever method is appropriate.

But in general, wider interfaces are preferable.

Tim Delaney

Christian Tismer

unread,
Jan 30, 2003, 11:17:28 PM1/30/03
to
Delaney, Timothy wrote:
> From: Christian Tismer [mailto:tis...@tismer.com]
>
> In my opinion, the crux of this all is the decision
> that strings are iterable.
> The number of use cases which I have seen so far is much
> shorter than this thread.
> Strings are much more often used as solid objects.
> I'm pretty sure Guido would remove the sequence-ness
> of strings in Python 3000, even to avoid this kind
> of problems.
>
>
> Whilst I've always liked that strings are iterable in Python, it does lead
> to complexity when you want to treat them specially.

How many of your programs *do* use an iteration over strings?
I have one or two, from the early first contact with Python.
Then I always was busy to defend strings as sequences.

> Additionally, the new semantics of 'in' for strings in 2.3 does suggest a
> lessening of the sequence-like behaviour of strings.

Absolutely.

> However, there has been a move to making *more* things iterable, not less.
> Whilst in general this is nice, it does mean that there are more
> opportunities for invalid objects to be processed by library routines.

I'm not against having more that makes sense.
But as stated elsewhere, we should at the same
time have less of the non-sense.
We are probably deprecating apply in favor of "*"/"**"
function calling, since it simplifies matters.
Strings as sequences do not simplify at all.
Without them, this thread would be non-existant
(as it should be).

> I would say, if you absolutely *must* have a list, document as such and do a
> typecheck. If someone wants to pass a non-list, they must convert it to a
> list by whatever method is appropriate.

Sure. Explicit, simple arguments are better than
implicit coercions.
If you need to have lists or tuples, I prefer to
do positive tests for them, instead of allowing
any iterable. It is then on the user's side to
supply the correct type or a coercion.

> But in general, wider interfaces are preferable.

Explicit intefaces would be a delight, btw.

ciao - chris

Christian Tismer

unread,
Jan 30, 2003, 11:30:44 PM1/30/03
to
Chad Netzer wrote:

> On Thu, 2003-01-30 at 19:28, Christian Tismer wrote:
>
>
>>In my opinion, the crux of this all is the decision
>>that strings are iterable.
>
>
> Ah, but I work with Numeric arrays a lot, which are also iterable, and
> which I like to package into tuple or lists for various reasons. These
> same problems can crop up as well (must be careful to pass a sequence of
> arrays, rather than just an array, etc). So it isn't just strings that
> present a problem.

Well, I don't take that argument.
Strings are used in an atomic manner
most of the time.
Lists/tuples as arguments are also typical
to be one dimensional. If they are not, the
user will probably take the time to read the
interface docs; (s)he should anyway.

With numeric arrays, it is *typical* to have
a multi dimensional array. In most cases, you
will want to have an array of a given dimensionality
as an argument, less likely a list of arrays.
IMHO, Numeric lacks a distinction between objects to be
seen as a whole and sequences, since this applies so
very recursively.

> Using variable parameters, you could build an interface that will accept
> single arguments (ie. foo(x) ), and would accept iterable objects or
> sequences as long as you explicitly specified that (ie. foo(*seq) ),
> while rejecting single arg sequences. A bit ugly, but could be useful
> with lots of string/list/tuple mixes.

I think, with Numeric objects, which are iterable on so many
levels, it is insane to provide ambiguous function interfaces.
These objects are so overloaded with flexibility, that I think
it would be better to do all the pre-shaping inside these objects
and then pass singletons of them to functions.

This appears to be a situations where two concepts of
flexibility are fighting each other.

if-the-interface-is-difficult-then-it's-wrong - ly y'rs - chris


Chad Netzer

unread,
Jan 30, 2003, 11:11:54 PM1/30/03
to
On Thu, 2003-01-30 at 19:28, Christian Tismer wrote:

> In my opinion, the crux of this all is the decision
> that strings are iterable.

Ah, but I work with Numeric arrays a lot, which are also iterable, and
which I like to package into tuple or lists for various reasons. These
same problems can crop up as well (must be careful to pass a sequence of
arrays, rather than just an array, etc). So it isn't just strings that
present a problem.

Using variable parameters, you could build an interface that will accept


single arguments (ie. foo(x) ), and would accept iterable objects or
sequences as long as you explicitly specified that (ie. foo(*seq) ),
while rejecting single arg sequences. A bit ugly, but could be useful
with lots of string/list/tuple mixes.

so,

def foo( *args ):
for a in args: print a,

a_string = "baked beans"
a_list_of_strings = [ "spam", "spam", "spam", "spam", "baked beans",
"spam" ]

foo( a_string ) # okay
foo( *a_list_of_strings ) #okay
foo( a_list_of_strings ) # could be made to NOT be okay

Messy, but it forces the user to consider the difference between using a
single arg, or using a list of arguments.

Anyway, it is just an (possibly lack-of-sleep-induced) idea.

--
Bay Area Python Interest Group - http://www.baypiggies.net/

Chad Netzer
(any opinion expressed is my own and not NASA's or my employer's)

Delaney, Timothy

unread,
Jan 30, 2003, 11:27:50 PM1/30/03
to
> From: Erik Max Francis [mailto:m...@alcyone.com]

>
> Wouldn't it be more appropriate to have _two_ functions, one
> which takes
> a sequence, and one which takes a single object (and possibly simply
> wraps it in a one-element list and passes it to the sequence-taking
> function)?

Indeed. I can almost guarantee that at some time in the future, you will
pass a list, and then wonder why it didn't get wrapped in a list. At some
time, you will want this behaviour (treating the passed list as atomic).

Tim Delaney

Chad Netzer

unread,
Jan 30, 2003, 11:59:14 PM1/30/03
to
On Thu, 2003-01-30 at 20:30, Christian Tismer wrote:

> With numeric arrays, it is *typical* to have
> a multi dimensional array. In most cases, you
> will want to have an array of a given dimensionality
> as an argument, less likely a list of arrays.

I see what you are saying. But for example, tonight I'm working on some
plotting utilities, to plot 1-d vectors. I want to be able to plot 1
vector or n vectors. But I don't want to plot a matrix (a 2d array);
that's a different plot, in my mind.

So, dealling with tuples of vectors for plotting is just an easier
concept to deal with (in my mind) than putting things into a matrix
form, and in fact, I really want to differentiate the two (in this
case). There are certain interface issues I have to deal with in my
plotting routines because of it.

If I just made my list of vectors into a matrix, I'd perhaps have less
interface issues (ie. I can just expect a matrix), but then users would
have to take steps to assemble the vectors with the right orientation
(ie. as columns, or as rows) and that makes more work and inconvenience
when using the routines.

> This appears to be a situations where two concepts of
> flexibility are fighting each other.

Exactly right; I'm happy that flexibility gives me a simpler to use
interface, but I have to be more careful as an implementer as to what
should be reasonably expected by the user when they call my routines (at
least in this case; and I'm the primary user so I can fit my own style
pretty easily :) )


> if-the-interface-is-difficult-then-it's-wrong - ly y'rs - chris

And easy to use can be difficult to implement (of course)

Alex Martelli

unread,
Jan 31, 2003, 2:15:52 AM1/31/03
to
On Friday 31 January 2003 04:36 am, Christian Tismer wrote:
...

> Besides th fact that I believe strings as sequences
> should be deprecated, since nobody makes use of it,

This is an overbid, IMHO: some (small) fraction of the time,
say 10%, I'm quite happy that strings are sequences for
looping purposes; and far more often than that, for purposes
of slicing, concatenation, repetition. Deprecation can maybe
be mooted for Python 3.0, of course, but no earlier.

> but everybody fights it...

In the relatively-rare case where I need to _distinguish_
between sequences and non-sequences, then it's indeed
more often the case that I want strings to be atoms. But
the need to draw such distinctions is not exactly an
everyday task, anyway -- hardly worth something as
drastic as deprecating strings' sequence status, IMHO,
unless many more use-cases can be collected.


> ... I can't see the relevance of UserString.
> Wasn't getting rid of UserAnything one of the reasons
> for the introduction of the unified type/class system?

You may think that about the UserList module (though
one might find counter-arguments), but I think it's clearly
off-base for UserString -- how is inheritance going to
give you *mutable* strings, for example?

> UserString is only a compatibility module, IMHO.

M own HO differs. The *ability* to inherit built-ins must
not turn into a *constraint* that you _have_ to inherit from
built-ins, even *without* using your base class's data
and method machinery (for example, you couldn't use
them to develop a mutable-string). This would confuse
the role of inheritance as a code-reuse device with the
role of inheritance as a "type-tag" (a common enough
confusion, to be sure, but still a deleterious one).

In other words, there's still a space for the idiom "wrap
and delegate", and even for reimplementation without
even any wrapping. It's a narrower space than it was
up to 2.1, when inheritance from built-ins wasn't allowed,
because now if you're HAPPY to get the base class's
data and machinery inheritance becomes natural -- but
too often you DON'T want to get them, though you DO
want to "be usable interchangeably with". Forcing you
to inherit implementation (and then override it all) in
order to get polymorphic substitutability would be BAD.


> But maybe we could simplify matters by providing something
> like a stringlike() inquiry function.

One more special case? Sure, that's what I proposed as
isstringlike. But then do you also want isfilelike, isdictlike,
etc, for other such cases? Protocol adaptation (the PEP
that refuses to die...) would be the right general framework
to use for framing all of such issues, IMHO.

> Strings being seen as sequences always have been a PITA
> for me, especially in user argument lists vs non lists.

If we were designing a Python-like language from scratch I
might agree (perhaps strings could be non-iterable but still
sliceable etc, for example, and an iterator might be gotten
by some explicit call to e.g. an .iter() method for those rare
instances where it's needed). But in terms of modifications
to the existing Python language, I'm just not sure this issue
is compelling enough to be worth any modification.


Alex


Max M

unread,
Jan 31, 2003, 3:54:19 AM1/31/03
to
Erik Max Francis wrote:

> In dynamically-typed languages like Python, overloading functions (which
> is I presume the crux of where you're doing this test) based on type
> usually isn't a great idea. Usually it's better to have two
> functions/methods instead.


This is a bad idea in many cases.

Very often you want a function that has this interface:

def getWhere(options): # options is a list
"generates query"


it could generate a query like:

SELECT * FROM table WHERE option='spam' or option='ni'

So the easiest way to do this is to write a function like this:

def getWhere(options):
return 'SELECT * FROM table WHERE %s' % ' or '.join(
['option=%s' for opt in options])

But frequently you will only call getWhere() with one argument. So
instead of having to call it like:

def getWhere(['spam']):
"generates query"

You want to call it like

def getWhere('spam'):
"generates query"

If you do not have polymorphic interfaces you will need to typecheck
your argument everytime you call the function:

if type(options) == type(''): # I *know* this sucks!
getWhere([options])
else:
getWhere(options)

If you want to make 2 functions:

def getWhereByOne('spam'):
"generates query"

def getWhere(['spam']):
"generates query"

You will need to check if the argument is a list or a string in all the
code wich calls one of the function.

if type(options) == type(''):
getWhereByOne(options)
else:
getWhere(options)

This is no better than the first option.

So the sane approach is to do the check inside the function:

def getWhere(options):
if type(options) == type(''): # I *know* this sucks!
options = [options]
return 'SELECT * FROM table WHERE %s' % ' or '.join(
['option=%s' for opt in options])

This will lead to far the most simple use of the function.

--

hilsen/regards Max M Rasmussen, Denmark

http://www.futureport.dk/
Fremtiden, videnskab, skeptiscisme og transhumanisme

Christian Tismer

unread,
Jan 31, 2003, 8:41:47 AM1/31/03
to
Christian Tismer wrote:
> Delaney, Timothy wrote:
>
>> From: Christian Tismer [mailto:tis...@tismer.com]
>>
>> In my opinion, the crux of this all is the decision
>> that strings are iterable.
>> The number of use cases which I have seen so far is much
>> shorter than this thread.
>> Strings are much more often used as solid objects.
>> I'm pretty sure Guido would remove the sequence-ness
>> of strings in Python 3000, even to avoid this kind
>> of problems.

Sorry about that statement!
It doesn't make sense, since sequence
behavior is needed for string indexing
and slicing, and these are of course necessary.

The real difference between strings IMHO is
that strings are no containers for objects.
I think this is similar to array.array objects,
which also would be considered a single parameter.

What we really need seems to be a criterion if
something is a container or not.

ciao - chris


Christian Tismer

unread,
Jan 31, 2003, 9:46:16 AM1/31/03
to
Alex Martelli wrote:
> On Friday 31 January 2003 04:36 am, Christian Tismer wrote:
> ...
>
>>Besides th fact that I believe strings as sequences
>>should be deprecated, since nobody makes use of it,
>
>
> This is an overbid, IMHO: some (small) fraction of the time,
> say 10%, I'm quite happy that strings are sequences for
> looping purposes; and far more often than that, for purposes
> of slicing, concatenation, repetition. Deprecation can maybe
> be mooted for Python 3.0, of course, but no earlier.

Sorry, I didn't really express what I meant.
A reply from Guido first reminded me that
we of course need string slicing and indexing.
And then I realized that we are not seeking
for "sequenceness" or "stringness" at all.

What people really want to know is whether
"is this argument meant as a single argument
or is it a container of arguments".
That turns the overall question into
"is something a container"?

This still gives some ambiguities with multi-
purpose chamaeleans like the Numeric arrays,
but strings and array.array objects are very well
covered: They *are* sequences, but you don't
want them to be taken as argument collections,
since they are no containers.

Here the same, written in some bad Python style,
but as a builtin, it would do quite much
simplification for argument checking:

>>> def iscontainer(obj):
... try:
... obj[0:0] # is sequence
... try:
... buffer(obj)
... return False
... except:
... pass
... return True
... except:
... return False
...
>>> iscontainer("")
0
>>> iscontainer(1)
0
>>> iscontainer(())
1

I'm (ab)using the fact that sequences which are
no containers usually support the buffer interface.

ciao - chris


Thomas Heller

unread,
Jan 31, 2003, 10:32:30 AM1/31/03
to
Christian Tismer <tis...@tismer.com> writes:

> Here the same, written in some bad Python style,
> but as a builtin, it would do quite much
> simplification for argument checking:
>
> >>> def iscontainer(obj):
> ... try:
> ... obj[0:0] # is sequence
> ... try:
> ... buffer(obj)
> ... return False
> ... except:
> ... pass
> ... return True
> ... except:
> ... return False
> ...
> >>> iscontainer("")
> 0
> >>> iscontainer(1)
> 0
> >>> iscontainer(())
> 1
>
> I'm (ab)using the fact that sequences which are
> no containers usually support the buffer interface.

This is worse, IMO.

ctypes' instances support the buffer interface, whether
they are sequences (or containers) or not.

ctypes' sequence instances (Array, for example)
support indexing, but not slicing.

The latter point could be viewed as a bug, although I'm not sure.

---

All this speaks for Alex'

try:
obj+''
except TypeError:
return

construct, although I can think of cases where it would
be problematic. Think of huge 200 MB strings...

Thomas

Thanos Vassilakis

unread,
Jan 31, 2003, 10:16:14 AM1/31/03
to

A long time ago I wrote a

class IsLike:
def __init__(self, *likeWhat):
self.likeWhat = likeWhat

def like(self, other):
"maybe overridde this"
return type(other) in self.likeWhat

So you could do

ListLike = IsLike(list, tuple)

from mytypes import ListLike

lst = maybeAnArg
if not ListLike.like(lst):


lst = [lst]
for x in lst:
whatever


thanos


Christian Tismer
<tis...@tismer.com To: Alex Martelli <al...@aleax.it>
> cc: holger krekel <py...@devel.trillke.net>, pytho...@python.org
Sent by: Subject: Re: Implicit lists
python-list-admin@
python.org


01/31/2003 09:46
AM

Here the same, written in some bad Python style,


but as a builtin, it would do quite much
simplification for argument checking:

>>> def iscontainer(obj):
... try:
... obj[0:0] # is sequence
... try:
... buffer(obj)
... return False
... except:
... pass
... return True
... except:
... return False
...
>>> iscontainer("")
0
>>> iscontainer(1)
0
>>> iscontainer(())
1

I'm (ab)using the fact that sequences which are
no containers usually support the buffer interface.

ciao - chris


--
http://mail.python.org/mailman/listinfo/python-list

Mel Wilson

unread,
Jan 31, 2003, 10:38:08 AM1/31/03
to
In article <XOq_9.48875$Hl6.6...@news010.worldonline.dk>,

Max M <ma...@mxm.dk> wrote:
>Very often you want a function that has this interface:
>
> def getWhere(options): # options is a list
> "generates query"
>
>it could generate a query like:
>
> SELECT * FROM table WHERE option='spam' or option='ni'
>
>So the easiest way to do this is to write a function like this:
>
> def getWhere(options):
> return 'SELECT * FROM table WHERE %s' % ' or '.join(
> ['option=%s' for opt in options])
>
>But frequently you will only call getWhere() with one argument. So
>instead of having to call it like:
>
> def getWhere(['spam']):
> "generates query"
>
>You want to call it like
>
> def getWhere('spam'):
> "generates query"

Then there is the possibility for confusion. This
Do-What-I-Mean feature has already injected lumps into base
Python:


Python 2.1.3 (#35, Apr 8 2002, 17:47:50) [MSC 32 bit (Intel)] on win32
Type "copyright", "credits" or "license" for more information.
>>> print 1
1
>>> print (1,2)
(1, 2)
>>> print '%s' % 1
1
>>> print '%s' % (1,2)


Traceback (most recent call last):

File "<stdin>", line 1, in ?
TypeError: not all arguments converted
>>>

by giving a possibly unwanted, or unwonted, meaning to
tuples vs. other types in doing string modulus. (Strictly,
the unwonted meaning is being given to the other types, by
relaxing the requirement that '%' take a tuple. Either way
though, forget what you're doing and KABOOM!)

I hit a similar thing with a wxTreeCtrl method. There is
a method that creates a tree item and optionally associates
a data instance of the programmer's choosing with the item.
I chose a triple containing some database keys and the
method screamed back that it was made to be called with 3
arguments, and I was calling it with 5. I guess there's a
*arg in there somewhere.

The container/contents distnction is not going to be
swept aside anytime soon. If you hand me a box of raisins,
do you want price tags on the box or on the raisins? How
about a box of soup cans? A box of apples?


>If you do not have polymorphic interfaces you will need to typecheck
>your argument everytime you call the function:
>
>if type(options) == type(''): # I *know* this sucks!
> getWhere([options])
>else:
> getWhere(options)
>
>If you want to make 2 functions:
>
> def getWhereByOne('spam'):
> "generates query"
>
> def getWhere(['spam']):
> "generates query"
>
>You will need to check if the argument is a list or a string in all the
>code wich calls one of the function.

The closer you are to the source of the data, the better
chance that you will know the form it's in. If the caller
has forgotten the form then the callee's chances are even
worse. It's best that the input validation code line things
up right at the start, for example:

line = infile.readline().strip()
try:
query_args = line.split()
except ValueError:
query_args = [line]

and from now on, we call

result = GetWhere (query_args)

with a clear conscience, at least as far as the type of
query_args is concerned.

I'm actually living this problem now. I started out on a
wxPython app. Since I didn't know wxPython, or much of
Windows, I relaxed my design methods; I was 'exploring'.
Now I have a thousand lines of coded coincidences that
produce a result that isn't obviously wrong, and I have to
carve them into a program. (not complaining.. I call this
'tuition fees'.)

Regards. Mel.

Donnal Walter

unread,
Jan 31, 2003, 11:14:55 AM1/31/03
to

"Alex Martelli" wrote:
> Dale Strickland-Clark wrote:
>
> > In many places, often small utility functions, I find myself using the
> > form:
> >
> > lst = maybeAnArg
> > if type(lst) not in (list, tuple)

> > lst = [lst]
> > for x in lst:
> > whatever
> >
> > or
> >
> > lst = maybeAnArg
> > if type(lst) not in (list, tuple)
> > lst = [lst]
> > mangle = [x for x in lst if whatever]

> >
> >
> > In other words, I want to be able to treat an argument of arbitrary
> > type as a list of 1 if it isn't already a list.
> >
> > Has anyone got a neater way of doing this?
>
> Sure:
>
> for x in aslist(list):
> whatever(x)
>
>
> How aslist is written is obviously quite secondary (at least,
> I _hope_ that's obvious...) -- all of its USES will be just as
> neat as the above example.
> ...

I have followed this thread with interest because I need the same kind of
functionality. The iteron definition using generators doesn't really work
for me for some reason. (I confess I don't fully understand generators.) I
would settle for a function aslist that would meet the following unit tests:

class MyObject(object): pass

class test_aslist(unittest.TestCase):

def test_string(self):
x = 'spam' # arg
y = aslist(x) # test function
z = [x] # target
self.assertEquals(y, z)

def test_tuple(self):
x = ('spam', 'eggs') # arg
y = aslist(x) # test function
z = ['spam', 'eggs'] # target
self.assertEquals(y, z)

def test_list(self):
x = ['spam', 'eggs'] # arg
y = aslist(x) # test function
z = x # target
self.assertEquals(y, z)

def test_instance(self):
x = MyObject() # arg
y = aslist(x) # test function
z = [x] # target
self.assertEquals(y, z)

if __name__ == '__main__':
unittest.main()

Thanks.

Donnal Walter
Arkansas Children's Hospital


Christian Tismer

unread,
Jan 31, 2003, 11:47:04 AM1/31/03
to
Thomas Heller wrote:
> Christian Tismer <tis...@tismer.com> writes:

[iscontainer implementation using buffer interface]

>>I'm (ab)using the fact that sequences which are
>>no containers usually support the buffer interface.

> This is worse, IMO.
>
> ctypes' instances support the buffer interface, whether
> they are sequences (or containers) or not.

After second thought, this is not worse, but great!
Nobody wants to see ctypes instances to be
used as containers for multiple arguments.
This is the domain of relatives of lists and tuples
and other, "plain Python" types.

Is there an object in ctypes which is meant as
a candidate for this domain? If not, then the
buffer approach is not so bad.
A buffer means something to access the interior
of an object, which means that the object has
some flat memory somewhere. If you support
buffer, I think you say you are not a container.
Even if this is cheating, I'd take this as honest
and won't take you as a parameter list. :-)

ciao - chris


Christian Tismer

unread,
Jan 31, 2003, 11:17:19 AM1/31/03
to

ctypes is no standard module, and nobody can
foresee arbitrary implementations in extensions.
I said that this is bad Python style, since
I had no other way to express what I mean.
Surely a better concept is needed.

My point is not the implementation but
the fact that we want to decide between
objects which are meant as parameter lists
and which are not.

> All this speaks for Alex'
>
> try:
> obj+''
> except TypeError:
> return
>
> construct, although I can think of cases where it would
> be problematic. Think of huge 200 MB strings...

No, this only covers strings and alikes.
This is all sort of hackish and not exactly
expressing what we want to say.

On the 200 MB thing: for strings, forget it, we're lucky.
>>> a="hallo"
>>> a+"" is a

ciao - chris


Thomas Heller

unread,
Jan 31, 2003, 12:38:55 PM1/31/03
to
Christian Tismer <tis...@tismer.com> writes:

> Thomas Heller wrote:
> > Christian Tismer <tis...@tismer.com> writes:
>
> [iscontainer implementation using buffer interface]
>
> >>I'm (ab)using the fact that sequences which are
> >>no containers usually support the buffer interface.
>
> > This is worse, IMO.
> > ctypes' instances support the buffer interface, whether
>
> > they are sequences (or containers) or not.
>
> After second thought, this is not worse, but great!
> Nobody wants to see ctypes instances to be
> used as containers for multiple arguments.
> This is the domain of relatives of lists and tuples
> and other, "plain Python" types.


>
> Is there an object in ctypes which is meant as
> a candidate for this domain? If not, then the
> buffer approach is not so bad.

Sure, something very similar to array.array('i'),
but with fixed length.
The way I see it, this is a container for integers,
but stored more efficiently maybe, and 'meaningful'
to C code at the same time.

> A buffer means something to access the interior
> of an object, which means that the object has
> some flat memory somewhere. If you support
> buffer, I think you say you are not a container.

No, I don't think so. 'buffer' only says something
about the internal implementation of the object.
Being able to iterate over array.array instances, for
example, is really useful. Image you would always
have to convert it to a list or tuple before.

> Even if this is cheating, I'd take this as honest
> and won't take you as a parameter list. :-)

Back to the original problem, the OP wanted to decide
IIRC if he's got a single object, or a container.

Only Python strings are a problem here, so IMO a stringlike
test like Alex suggested is what is wanted.

Thomas

Alex Martelli

unread,
Jan 31, 2003, 12:40:32 PM1/31/03
to
Donnal Walter wrote:

> functionality. The iteron definition using generators doesn't really work
> for me for some reason. (I confess I don't fully understand generators.) I

Maybe you don't have a:
from __future__ import generators
at the start of your module?

> I have followed this thread with interest because I need the same kind of

> would settle for a function aslist that would meet the following unit
> tests:
>
> class MyObject(object): pass
>
> class test_aslist(unittest.TestCase):
>
> def test_string(self):
> x = 'spam' # arg
> y = aslist(x) # test function
> z = [x] # target
> self.assertEquals(y, z)
>
> def test_tuple(self):
> x = ('spam', 'eggs') # arg
> y = aslist(x) # test function
> z = ['spam', 'eggs'] # target
> self.assertEquals(y, z)
>
> def test_list(self):
> x = ['spam', 'eggs'] # arg
> y = aslist(x) # test function
> z = x # target
> self.assertEquals(y, z)
>
> def test_instance(self):
> x = MyObject() # arg
> y = aslist(x) # test function
> z = [x] # target
> self.assertEquals(y, z)

If this is indeed *ALL* you need then the simplest way to
code aslist is probably:

def aslist(x):
if isinstance(x, (list, tuple)): return x
return [x]

It's hard to imagine a simpler or more concise way to
express the exact set of functionality given by these
unit tests.

My objection to this approach, based on type-testing,
is that it fails to do what I consider sensible for
args of many OTHER types, such as UserString and the
like. But if you are adamant that the ONLY types you
want to be left alone are tuples and lists, and EVERY
other type MUST be wrapped up as a 1-item list, which
is at least one way to read these unit tests, then
it's hard to do better than just saying so outright
in the Python source code of function aslist.


Alex

holger krekel

unread,
Jan 31, 2003, 12:37:04 PM1/31/03
to
> I have followed this thread with interest because I need the same kind of
> functionality. The iteron definition using generators doesn't really work
> for me for some reason. (I confess I don't fully understand generators.) I
> would settle for a function aslist that would meet the following unit tests:
>
> class MyObject(object): pass
>
> class test_aslist(unittest.TestCase):
>
> def test_string(self):
> x = 'spam' # arg
> y = aslist(x) # test function
> z = [x] # target
> self.assertEquals(y, z)
>
> def test_tuple(self):
> x = ('spam', 'eggs') # arg
> y = aslist(x) # test function
> z = ['spam', 'eggs'] # target
> self.assertEquals(y, z)
>
> def test_list(self):
> x = ['spam', 'eggs'] # arg
> y = aslist(x) # test function
> z = x # target
> self.assertEquals(y, z)
>
> def test_instance(self):
> x = MyObject() # arg
> y = aslist(x) # test function
> z = [x] # target
> self.assertEquals(y, z)
>
> if __name__ == '__main__':
> unittest.main()

that's a nice approach to ask for coding. Here is
one possibility to satisfy the four tests.

def _isatom(arg):
if isinstance(arg, (str, unicode)):
return 1

def aslist(arg, isatom=_isatom):
if not isatom(arg):
try:
return list(arg)
except TypeError:
pass
return [arg]

You can also pass in an Martellian 'isstringlike'
check to determine 'atomicity'. See earlier posts.

regards,

holger

Donnal Walter

unread,
Jan 31, 2003, 1:43:18 PM1/31/03
to

"Alex Martelli" <al...@aleax.it> wrote:
> Donnal Walter wrote:
>
> > functionality. The iteron definition using generators doesn't really
work
> > for me for some reason. (I confess I don't fully understand generators.)
I
>
> Maybe you don't have a:
> from __future__ import generators
> at the start of your module?

No, I did put the import statement in. The part about generators that I
don't understand is how does one determine if they are empty or not?

def __init__(self, ref=[]):
self.__ref = iteron(ref)
if len(self.__ref) > 0:
# do something

produces TypeError: len() of unsized object

Alex Martelli

unread,
Jan 31, 2003, 3:09:56 PM1/31/03
to
Donnal Walter wrote:
...

> don't understand is how does one determine if they are empty or not?
>
> def __init__(self, ref=[]):
> self.__ref = iteron(ref)
> if len(self.__ref) > 0:
> # do something
>
> produces TypeError: len() of unsized object

The ONLY way you can use a generator is by looping on it (normally
with a for statement, though you may choose to explicitly call
.next() and check for StopIteration being raised -- or, have the
loop be done implicitly for you from builtin functions).

Looping on a generator "consumes" it, so you get to do it only
once. If you need to loop more than once, you can build a list:

self.__ref = list(iteron(ref))


Of course, this will consume (potentially) a lot of memory.

To check the number of items in a generator, you basically
need to loop on the generator (you could think of tricks
based on wrapping the generator just to determine if it's
empty or not, but while clever they're unlikely to be worth
it) and thus here you may want to turn it into a list.


Alex

Bengt Richter

unread,
Jan 31, 2003, 5:00:38 PM1/31/03
to

A container can be a single object for some purposes, so purpose
has to be involved in the decision. Unfortunately that tends to
involve the universal DWIM function ;-) If the purpose of an
object is to act like a container only in the aspect of being
able to produce a series of objects, you can't infer anything
about the type of the objects you will get or how they were
produced. Or else "container" has to be a pretty narrow concept.

>Only Python strings are a problem here, so IMO a stringlike
>test like Alex suggested is what is wanted.

IMO using a particular behaviour probe to infer general behavior
and/or type info is fraught with pitfalls. E.g., that innocent '+'
in Alex's probe changes the question from "will you act like a string"
to "do you implement '+' with the other arg specifically being "''"
without raising an exception. It is not the same question. Not to
mention what an unknown object might do in terms of side effects
and/or execution time when probed.

I think there is more thinking to do ;-)

Regards,
Bengt Richter

Erik Max Francis

unread,
Jan 31, 2003, 10:26:10 PM1/31/03
to
Max M wrote:

> You will need to check if the argument is a list or a string in all
> the
> code wich calls one of the function.
>
> if type(options) == type(''):
> getWhereByOne(options)
> else:
> getWhere(options)
>
> This is no better than the first option.

Why do you _have_ to check? You're doing precisely the thing that
you're not supposed to do in dynamic languages; you're limiting the
types that can be passed to them unnecessarily.

Having two interfaces where one takes "a thing" and the other takes "a
sequence of things" is totally reasonable, since one will be treated as
things, and the other will be treated as a sequence of things.

In your specific case those things are strings, but that analysis
doesn't generalize fully.

> So the sane approach is to do the check inside the function:
>
> def getWhere(options):
> if type(options) == type(''): # I *know* this sucks!
> options = [options]
> return 'SELECT * FROM table WHERE %s' % ' or '.join(
> ['option=%s' for opt in options])
>
> This will lead to far the most simple use of the function.

That isn't totally unreasonable, provided you're sure that you always
want them to be strings, or something else (which will be interpreted as
a sequence of strings). (But even then, what about Unicode strings?
They'd fail the test.)

Again, this is the only sort of polymporphic interface that makes any
sort of sense -- where you know that you either want a simple,
fundamental type (like a numeric or a string), or something else. In
this case, it's either a string, or a sequence of things (which are
presumably strings).

Note still that this case is limiting; your sequence case works properly
on objects that implement a __str__ method that does something
meaningful, but you can't pass a single one of those objects in, as it
will be treated as a sequence, with unpredictable results.

This, as I mentioned earlier, isn't totally unreasonable, since you're
checking against a fundamental type first, and then generalizing if that
wasn't it; the original poster was talking about testing to see if it's
a _sequence_ first (by testing whether it's a list or tuple) and then
behaving differently otherwise. This is bad, because there are many
more types of sequences than just lists and tuples.

--
Erik Max Francis / m...@alcyone.com / http://www.alcyone.com/max/
__ San Jose, CA, USA / 37 20 N 121 53 W / &tSftDotIotE

/ \ It is much safer to obey than to rule.
\__/ Thomas a Kempis

Laura Creighton

unread,
Jan 31, 2003, 11:06:45 PM1/31/03
to
> >Only Python strings are a problem here, so IMO a stringlike
> >test like Alex suggested is what is wanted.
> IMO using a particular behaviour probe to infer general behavior
> and/or type info is fraught with pitfalls. E.g., that innocent '+'
> in Alex's probe changes the question from "will you act like a string"
> to "do you implement '+' with the other arg specifically being "''"
> without raising an exception. It is not the same question. Not to
> mention what an unknown object might do in terms of side effects
> and/or execution time when probed.
>
> I think there is more thinking to do ;-)
>
> Regards,
> Bengt Richter

Some more thoughts.

Which do you think is more likely to happen a) Somebody creates an
object which implements <something> with an other arg of <something
else> which when fed to that function does something which isn't
wanted or b) somebody creates an object that would have worked
perfectly with the object?

What will be easier to understand and fix as an error c) ooops, I
implemented <something> in this object but it doesn't behave like the
author of <that module> intended, or d) ooops, I am not a real
<whatever> and I got typetest. I wonder which methods, properties,
and other behaviours I need to implement before I am sufficiently
<whatever>-like for the module writer?

I don't think there is a one-size-fits-all answer.

The last one d) is the one that I always want to know, and I would be
happy if it happened automatically. But this might be the job for
the refactoring browser, not the language.

Laura


Christian Tismer

unread,
Feb 1, 2003, 12:27:57 PM2/1/03
to
Erik Max Francis wrote:
> Max M wrote:
>
>
>>You will need to check if the argument is a list or a string in all
>>the
>>code wich calls one of the function.
>>
>>if type(options) == type(''):
>> getWhereByOne(options)
>>else:
>> getWhere(options)
>>
>>This is no better than the first option.
>
>
> Why do you _have_ to check? You're doing precisely the thing that
> you're not supposed to do in dynamic languages; you're limiting the
> types that can be passed to them unnecessarily.
>
> Having two interfaces where one takes "a thing" and the other takes "a
> sequence of things" is totally reasonable, since one will be treated as
> things, and the other will be treated as a sequence of things.
>
> In your specific case those things are strings, but that analysis
> doesn't generalize fully.

You are right, in general. The problem is simply that
strings can be treated as objects or sequences all
the time, and that a common error is to pass a string
in a sequence context, and your function iterates over
the single chars. This especially hurts when your
function expects a list of filenames, and suddenly it
creates a bunch of single char named files :-)

The problem with this polymorphy is that the code has
to decide what the user most probably has thought.
In this context, it would be nice to be able to spell
"this parameter is a string, nothing else". But there
is no simple way to do this.
To be able to draw a clear decision about this, we
wouzld need a property/method/whatever that tells
us what the intent of this parameter usage has been.
Unfortunately, there seems to be no simple way.

ciao - chris


Erik Max Francis

unread,
Feb 1, 2003, 5:40:29 PM2/1/03
to
Christian Tismer wrote:

> You are right, in general. The problem is simply that
> strings can be treated as objects or sequences all
> the time, and that a common error is to pass a string
> in a sequence context, and your function iterates over
> the single chars. This especially hurts when your
> function expects a list of filenames, and suddenly it
> creates a bunch of single char named files :-)

I don't see how you can eliminate strings' sequence interface without
either 1. breaking a huge amount of code, or 2. ending up with a string
presenting a very mixed and fragmented interface. Individual characters
are routinely indexed from strings, the lengths of strings (via len) are
routinely retrieved, and slicing (really a special case of the sequence
protocol) is exceptionally common.

So how do you make the string type support all these behaviors but _not_
act as a standard sequence? Why would you really want to?

--
Erik Max Francis / m...@alcyone.com / http://www.alcyone.com/max/
__ San Jose, CA, USA / 37 20 N 121 53 W / &tSftDotIotE

/ \ Then you give me that Judas Kiss / Could you hurt me more than this
\__/ Lamya
Bosskey.net: Aliens vs. Predator 2 / http://www.bosskey.net/avp2/
A personal guide to Aliens vs. Predator 2.

Christian Tismer

unread,
Feb 1, 2003, 11:18:47 PM2/1/03
to
Erik Max Francis wrote:
> Christian Tismer wrote:
>
>
>>You are right, in general. The problem is simply that
>>strings can be treated as objects or sequences all
>>the time, and that a common error is to pass a string
>>in a sequence context, and your function iterates over
>>the single chars. This especially hurts when your
>>function expects a list of filenames, and suddenly it
>>creates a bunch of single char named files :-)
>
>
> I don't see how you can eliminate strings' sequence interface without
> either 1. breaking a huge amount of code, or 2. ending up with a string
> presenting a very mixed and fragmented interface. Individual characters
> are routinely indexed from strings, the lengths of strings (via len) are
> routinely retrieved, and slicing (really a special case of the sequence
> protocol) is exceptionally common.

I don't see how your reply relates to my message.

> So how do you make the string type support all these behaviors but _not_
> act as a standard sequence? Why would you really want to?

I did not say that I want to remove the sequence protocol
in this message. Maybe in a couple before, but nobody can
hinder me to become smarter in a couple of days. :-)
I was trying to spell the problem, but you are still stuck
with implementation details, like sequence-ness.

This is most probably not the distinguishing property that
we are searching for, so please drop that idea that I want
to remove the sequence protocol for strings. This has not
been in my message.

The summary is this:
How can I express that I want to pass a string or some other
object as a single parameter to a function that both accepts
singletons and sequences? How can I prevend that Joe User
ignores my interface and passes a string, and I misinterpret
it as a bunch of characters?

No solution so far was sufficient.
All the attempts to figure out what the user wants
have side effects, put implications or restrictions
on objects which aren't intuitive or are application
specific. I.E., At my taste, I would never accept an
array.array as a multiple argument, but others might.

Python's flexibility is great. At the same time, it is
its own enemy. I can have atomic objects, lists, tuples
and general sequences, and I can pass them to functions
which accept atoms, lists, tuples and sequences, but there is
no way to tell the function whether I meant to pass something
like an atomic object or like a sequence.

Is the consequence to always try to avoid polymorphic interfaces,
or do we need a new construct to express our intent?
I could imagine an inquiry function that would yield such information,
and in the case of strings, this would say "treat me as an atomic".

ciao - chris


Bengt Richter

unread,
Feb 2, 2003, 8:00:21 PM2/2/03
to
On Sun, 02 Feb 2003 05:18:47 +0100, Christian Tismer <tis...@tismer.com> wrote:
[...]

>The summary is this:
>How can I express that I want to pass a string or some other
>object as a single parameter to a function that both accepts
>singletons and sequences? How can I prevend that Joe User
>ignores my interface and passes a string, and I misinterpret
>it as a bunch of characters?
>
>No solution so far was sufficient.
>All the attempts to figure out what the user wants
>have side effects, put implications or restrictions
>on objects which aren't intuitive or are application
>specific. I.E., At my taste, I would never accept an
>array.array as a multiple argument, but others might.
>
>Python's flexibility is great. At the same time, it is
>its own enemy. I can have atomic objects, lists, tuples
>and general sequences, and I can pass them to functions
>which accept atoms, lists, tuples and sequences, but there is
>no way to tell the function whether I meant to pass something
>like an atomic object or like a sequence.
>
>Is the consequence to always try to avoid polymorphic interfaces,
>or do we need a new construct to express our intent?
>I could imagine an inquiry function that would yield such information,
>and in the case of strings, this would say "treat me as an atomic".
>
I can see inquiry functions, but then there has to be a default inquiry
method or a default when there is none. CORBA and COM type things come
to mind. But "treat me as atomic" also reminds me of (quote x) or 'x.
IOW, maybe there could be a quote-with-respect-to-serial-decomposition
that could generally wrap anything and be recognized and unquoted once
by anything looking for a sequence.

Just a thought, not explored very far ;-)

Regards,
Bengt Richter

Martin Maney

unread,
Feb 2, 2003, 11:38:37 PM2/2/03
to
Christian Tismer <tis...@tismer.com> wrote:
> The summary is this:
> How can I express that I want to pass a string or some other
> object as a single parameter to a function that both accepts
> singletons and sequences?

I know it isn't the answer you want, but I have had occasion to try on
teeshirts from this drawer, and I don't think there is a better answer
than something like this in Python:

foo(['my string here'])

or it might look like this, although it's IMO a bit less clear for
this simple example:

foo(('this string available'))

> How can I prevend that Joe User ignores my interface and passes a
> string, and I misinterpret it as a bunch of characters?

You can't, not with certainty. This is generally true, as the old saw
about how they're making better fools all the time (hence the futility
of trying to make your widget completely foolproof) points out. If
this proves to be a common error, then perhaps having an interface that
tries to accomodate different types that require different handling yet
are so easily confused is not an optimum solution.

> All the attempts to figure out what the user wants
> have side effects, put implications or restrictions
> on objects which aren't intuitive or are application
> specific.

Yes. When you are reduced to guessing, you have to expect to be right
less than 100% of the time, no? If accuracy is all-important, then
perhaps you need a solution that removes the need to guess what is
intended.

> Is the consequence to always try to avoid polymorphic interfaces,

Why do you think it has to be all or nothing? Polymorphism is neither
a magic bullet nor a child of Satan. It's a tool, and like all tools
it is sometimes applicable and other times not. At least that's been
my experience.

Alex Martelli

unread,
Feb 3, 2003, 2:17:16 AM2/3/03
to
Martin Maney wrote:
...

> or it might look like this, although it's IMO a bit less clear for
> this simple example:
>
> foo(('this string available'))

The doubling up of the parentheses here has absolutely no effect.

Maybe you mean to pass a singleton tuple, in which case you
need to use the following syntax:

foo(('this string available',))

The easy-to-miss trailing comma is what denotes the singleton
tuple -- unpleasant, but I know of no better alternatives.


Alex

holger krekel

unread,
Feb 3, 2003, 3:28:55 AM2/3/03
to
Christian Tismer wrote:

> Erik Max Francis wrote:
> > Christian Tismer wrote:
> >
> >
> >>You are right, in general. The problem is simply that
> >>strings can be treated as objects or sequences all
> >>the time, and that a common error is to pass a string
> >>in a sequence context, and your function iterates over
> >>the single chars. This especially hurts when your
> >>function expects a list of filenames, and suddenly it
> >>creates a bunch of single char named files :-)
> >
> >
> > I don't see how you can eliminate strings' sequence interface without
> > either 1. breaking a huge amount of code, or 2. ending up with a string
> > presenting a very mixed and fragmented interface. Individual characters
> > are routinely indexed from strings, the lengths of strings (via len) are
> > routinely retrieved, and slicing (really a special case of the sequence
> > protocol) is exceptionally common.
>
> I don't see how your reply relates to my message.
>
> > So how do you make the string type support all these behaviors but _not_
> > act as a standard sequence? Why would you really want to?
>
> I did not say that I want to remove the sequence protocol
> in this message. Maybe in a couple before, but nobody can
> hinder me to become smarter in a couple of days. :-)
> I was trying to spell the problem, but you are still stuck
> with implementation details, like sequence-ness.
>
> This is most probably not the distinguishing property that
> we are searching for, so please drop that idea that I want
> to remove the sequence protocol for strings. This has not
> been in my message.
>
> The summary is this:
> How can I express that I want to pass a string or some other
> object as a single parameter to a function that both accepts
> singletons and sequences? How can I prevend that Joe User

> ignores my interface and passes a string, and I misinterpret
> it as a bunch of characters?

I'd call upon

"In the face of ambiguity, refuse the temptation to guess."
and
"Explicit is better than implicit"

So I'd still propose to do

def myfunc(args, noiter=(str, unicode)):
...
if isinstance(args, noiter):
# don't iterate
...

this cures exactly the problem that the string types
in python suffer from: you often want them as an atom
and not iterable 99% of the time. If somebody
makes his own string type, then usually they
either inherit from str/unicode or completely
make up their own class/type and probably won't even
implement a character-iteration. And if they do then
they have to make this explicit to 'myfunc'. If that
'myfunc' is deep inside a module (not directly accessed
from the outside) then we can make 'noiter' a module
global so that users can append their class to it.

The above is also the way it is done in the standard lib
anyway.

cheers,

holger

Donn Cave

unread,
Feb 3, 2003, 12:38:40 PM2/3/03
to
Quoth Christian Tismer <tis...@tismer.com>:
...

| Python's flexibility is great. At the same time, it is
| its own enemy. I can have atomic objects, lists, tuples
| and general sequences, and I can pass them to functions
| which accept atoms, lists, tuples and sequences, but there is
| no way to tell the function whether I meant to pass something
| like an atomic object or like a sequence.
|
| Is the consequence to always try to avoid polymorphic interfaces,
| or do we need a new construct to express our intent?
| I could imagine an inquiry function that would yield such information,
| and in the case of strings, this would say "treat me as an atomic".

Do you think this is polymorphism, when a parameter may be either
an atom or a collection of same? I don't. Don't know what I'd
call it, maybe it doesn't deserve a name.

I've run into the problem, all right, but in my opinion it's more
of a symptom of sloppily engineered software that relies too much
on Python's introspection. Some of that software is Python itself -
for example, if you try to handle exceptions somewhat generically
at the top level of an interactive application, I think you'll find
yourself trying to sniff out various object properties. Maybe it's
an inevitable fact of life, and like I say I've sure brought it on
myself plenty of times, but it's also a losing game, and I wonder
if it's better to feel the pain of losing and be motivated towards
better defined interfaces.

Donn Cave, do...@u.washington.edu

Martin Miller

unread,
Feb 3, 2003, 3:08:41 PM2/3/03
to
Here's a working documented version of asList, for any of those to whom
it might not be so "obvious". It can easily be customized to handle
various types a special cases (as shown for tuples and the value None).

HTH,
Martin

> def asList(arg):
> """Makes sure the argument it is passed is a Python list. If it is
> it is just returned, otherwise a degenerate list is created and
> returned with the single item in it.
>
> asList() can used to create flexible interfaces which allow
> arguments to be passed to them that are either single items or lists
> of items. By applying this function before using the values in
> arguments, single and multi-valued cases can be handled by general
> list-handling code in the function or method.
>
> As a special case, a single argument with the value None is converted
> into an empty list (instead of converted into the list [None]).
>
> asList(arg) ==> list
> """
> import types # Standard Python types module
>
> if type(arg) is types.ListType: # Nothing to do
> return arg
> elif type(arg) is types.TupleType:# Convert tuples to lists
> return list(arg)
> elif type(arg) is types.NoneType: # Special case, treat None as []
> return []
> else: # Convert item to list of one item
> return [arg]
>
> if __name__ == "__main__":
>
> def example(items=None):
> """Sample function that can be called with an argument
> that can be a individual or list of items.
> """
> itemList = asList(items)
> if itemList:
> print "example() called with argument containing", len(itemList), "thing(s)"
> else:
> print "example() called with empty list or None argument"
> i = 0
> for item in itemList:
> print " item[" + str(i) + "] = " + repr(item)
> i += 1
> print
>
> example(42)
> example((1,2,3))
> example([4,5,6,7])
> example(["aaa", 111, (4,5), 2.01])
> example(None) # Note that this will become an empty list
> example() # same in this case


====================


Alex Martelli wrote:
> Dale Strickland-Clark wrote:
>
>
>>In many places, often small utility functions, I find myself using the
>>form:
>>
>>lst = maybeAnArg
>>if type(lst) not in (list, tuple)
>> lst = [lst]
>>for x in lst:
>> whatever
>>
>>or
>>
>>lst = maybeAnArg
>>if type(lst) not in (list, tuple)
>> lst = [lst]
>>mangle = [x for x in lst if whatever]
>>
>>
>>In other words, I want to be able to treat an argument of arbitrary
>>type as a list of 1 if it isn't already a list.
>>
>>Has anyone got a neater way of doing this?
>
>
> Sure:
>
> for x in aslist(list):
> whatever(x)
>
>
> How aslist is written is obviously quite secondary (at least,
> I _hope_ that's obvious...) -- all of its USES will be just as
> neat as the above example.
>

> If it turns out (as would seem to be the case) that what you
> need is actually an iterator (since you want to treat tuples
> as "lists", you ain't gonna be able to append, sort, &c
> anyway) then I'd name the auxiliary function "iteron" or
> something like that. In my experience, what I generally
> want is:
> treat strings, and all string-like objects, as non-sequences
> otherwise, iterate directly on sequences and other iterables
> "other iterables" is debatable (files, dictionaries, ...) but
> that's how I've generally found things work best -- easy enough
> to tweak it to your liking otherwise. So, for example:
>
> def iteron(something):
> # string-like objects: treat as non-sequences
> try: something+''
> except TypeError: pass
> else:
> yield something
> return
> # other sequences
> try:
> for x in something: yield x
> except TypeError:
> yield something
>
>
> Alex
>


Beni Cherniavsky

unread,
Feb 3, 2003, 5:57:30 PM2/3/03
to
On 2003-01-31, Mel Wilson wrote:

> In article <XOq_9.48875$Hl6.6...@news010.worldonline.dk>,
> Max M <ma...@mxm.dk> wrote:
> >
> >But frequently you will only call getWhere() with one argument. So
> >instead of having to call it like:
> >
> > def getWhere(['spam']):
> > "generates query"
> >
> >You want to call it like
> >
> > def getWhere('spam'):
> > "generates query"
>

Since you only need one such "magic" argument and it's the last argument,
let me borrow Guido's time machine::

def getWhere(*foods):
return list(foods) # insert useful code here

getWhere('spam')
getWhere('spam', 'eggs')
getWhere(*foodlist)

The point is to "refuse the temptation to guess". In my experience,
any [1]_ attempt to dispatch on the argument being a sequence / atom
always *reduces* the utility of the function. A single interface is
clearest. One of the interfaces must be written differently so that
your intent is unambiguous. If you are lucky, you get a chance to
choose which of them should get the simpler syntax (which is the idea
above) but then the other suffers. (Or have two different
functions/keywords as someone suggested).

.. [1] The only exception is when you build a complete "mini-language"
using the builtin types (lists, strings, etc.) for the
structure. It's a nice LISPish thing to do (but applicable in
Python two, actually lists + dicts + tuples give you lot of
flexibility). However, if you do such a thing, always remember
to provide a "quoting" syntax which allows to embed any data
structure without it being auto-interpreted...

> Then there is the possibility for confusion. This
> Do-What-I-Mean feature has already injected lumps into base
> Python:
>

> >>> print '%s' % 1
> 1
> >>> print '%s' % (1,2)
> Traceback (most recent call last):
> File "<stdin>", line 1, in ?
> TypeError: not all arguments converted
> >>>
>
> by giving a possibly unwanted, or unwonted, meaning to
> tuples vs. other types in doing string modulus. (Strictly,
> the unwonted meaning is being given to the other types, by
> relaxing the requirement that '%' take a tuple. Either way
> though, forget what you're doing and KABOOM!)
>

Sure, this is a wart. Since writing singleton tuples is awkward [2]_,
I suggest adding a __div__ method to strings that will accept a single
arg and slowly deprecating the __mod__ exception for a single arg::

"%d" / 1 # My suggestion.
"%d,%d" % (1,2) # Stays as now.
"%d" % (1,) # Perfectly legal, should stay (but will be
# rarely used, mainly when the tuple is
# computed).
"%d" % 1 # To be deprecated.

--
Beni Cherniavsky <cb...@tx.technion.ac.il>

My first thought upon hearing the company name "22 Design":
That's not a valid identifier (starts with a digit)!

Paul Rubin

unread,
Feb 3, 2003, 6:34:22 PM2/3/03
to
Beni Cherniavsky <cb...@techunix.technion.ac.il> writes:
> Sure, this is a wart. Since writing singleton tuples is awkward [2]_,
> I suggest adding a __div__ method to strings that will accept a single
> arg and slowly deprecating the __mod__ exception for a single arg::
>
> "%d" / 1 # My suggestion.
> "%d,%d" % (1,2) # Stays as now.
> "%d" % (1,) # Perfectly legal, should stay (but will be
> # rarely used, mainly when the tuple is
> # computed).
> "%d" % 1 # To be deprecated.

The whole string modulo thing is a wart and should be replaced with a
sprintf function like every other language uses.

The one operator overloading idea for strings that I like is a unary
minus sign for interpolation:

print -"foo is $foo"

But that idea got no response at all last time I posted it.

holger krekel

unread,
Feb 3, 2003, 6:56:09 PM2/3/03
to
Beni Cherniavsky wrote:
> On 2003-01-31, Mel Wilson wrote:
>
> > In article <XOq_9.48875$Hl6.6...@news010.worldonline.dk>,
> > Max M <ma...@mxm.dk> wrote:
> > >
> > >But frequently you will only call getWhere() with one argument. So
> > >instead of having to call it like:
> > >
> > > def getWhere(['spam']):
> > > "generates query"
> > >
> > >You want to call it like
> > >
> > > def getWhere('spam'):
> > > "generates query"
> >
> Since you only need one such "magic" argument and it's the last argument,
> let me borrow Guido's time machine::
>
> def getWhere(*foods):
> return list(foods) # insert useful code here
>
> getWhere('spam')
> getWhere('spam', 'eggs')
> getWhere(*foodlist)
>
> The point is to "refuse the temptation to guess". In my experience,
> any [1]_ attempt to dispatch on the argument being a sequence / atom
> always *reduces* the utility of the function.

True but the OP wanted to have a pattern that allowed him
to pass objects around which can be lists or not and simply wants
to use

for i in whatever(arg):

and have 'whatever' sort it out. If you have to know *at calling time*
what kind 'arg' is then the whole point is mute because you
can simply use

for i in ['where']:
...

for i in foodlist:
...

which is as short and concise as it gets.

holger

Martin Maney

unread,
Feb 4, 2003, 11:08:20 AM2/4/03
to
Alex Martelli <al...@aleax.it> wrote:
> Maybe you mean to pass a singleton tuple, in which case you
> need to use the following syntax:
>
> foo(('this string available',))
>
> The easy-to-miss trailing comma is what denotes the singleton
> tuple -- unpleasant, but I know of no better alternatives.

Yes, that's what I was thinking, but the fingers didn't execute it.
Another reason to prefer the list in this context. After long enough
that I think of myself as quite comfortable with Python, that comma is
one thing I still glitch up relatively often.

Mel Wilson

unread,
Feb 5, 2003, 12:25:44 AM2/5/03
to
In article <7xisw1o...@ruckus.brouhaha.com>,

Paul Rubin <phr-n...@NOSPAMnightsong.com> wrote:
>The whole string modulo thing is a wart and should be replaced with a
>sprintf function like every other language uses.
>
>The one operator overloading idea for strings that I like is a unary
>minus sign for interpolation:
>
> print -"foo is $foo"
>
>But that idea got no response at all last time I posted it.

The feature that made perl what it is today.
I'm not wild about commingling text and variable names.
It makes it awkward to say things like

-"The text control to edit the value of $foo is called $foo_ctrl."

C programs have to turn handsprings through hoops to get
this string splicing effect in their macros. And the given
example doesn't hint at any control of formatting, eg.
'%.3f'%foo .

I imagine we'd have to escape $ signs:

-"The input $foo must be less than $$100.00."

etc.

I like string modulo operations, myself. They're
succinct. They give a reasonable view of how the string is
structured and/vs. what gets inserted into it. They have a
strict definition so careful people can get what they want,
even if they also have a shaky alternate definition so
newbies and even-more-careful people can get what they want
some or all of the time.

Regards. Mel.

0 new messages