Say you put this task to a Lisp and a Python programmer: Come up with
a good, generic, reusable way to compare two lists. What are their
respective trains of thought?
Lisp programmer:
Well, there is a standard function called mismatch that does it, but I
can't recommend it. First of all, you don't know where that
function's been. Anyone and their mother could have worked on it, did
they have good, sound programming practice in mind when they wrote
it? Of course not. Let's be real here, we have to implement this by
hand.
(defun lists-are-equal (a b)
(or (and (not a) (not b))
(and (= (car a) (car b)) (lists-are-equal (cdr a) (cdr b))))
There, much better than the standard function, and better yet, it's in
the *absolute minimal form possible*. There is no way to express list
comparison in a more reduced form. It's almost erotic how awesome it
is. I'm---whoa, ok, I'm getting a little excited now, settle down.
Well, come to think of it, that's really not that good. First of all
it's a function. I mean, it just sits there and does nothing till you
call it. How boring is that? It can't react to the current
situation. Plus it compares all lists the same way, and that is
really inefficient. Every list compare is a new problem. Different
lists need different comparative strategies. This function simply
won't do. I need a macro that can intelligently compile the right
list compare methodology in. For instance, if we want to compare two
lists that are known at compile time, we don't want to waste time
comparing them at runtime. No, the macro must detect constant
arguments and special case them. Good start. Now, we have to
consider the conditions this comparison is being done under. If the
user is passing the result of a sort to this macro, it's almost
certain that they are trying to see whether the lists have the same
elements. We can do that a lot more efficiently with a countset. So
let's have the macro check to see if the forms passed to it are all
sort calls. Better yet, let's check for my own powerful sort macro.
Hmm. Wait... I think my 4600-line sort macro already checks its
calling context to see if its results are being fed to a list
comparison. I'll have to refactor that together with this macro. Ok,
good, now I am sure other users will eventually want to customize list
comparison for their own use, after all every list comparison is
different and I can't possibly anticipate all of them. A user needs
to be able to adapt to the situation, so it's vitally important to
create a plug-in infrastructure to give them that flexibility. Now,
what about exceptions, there's a millions ways to deal with that...
...and so on until eyelids can no longer stay open....
Python programmer:
a == b. Next question.
Carl Banks, who might be exaggerating
...a little.
Ah well. I guess this explains Emacs.
Cheers,
Emm
> Ah well. I guess this explains Emacs.
So how would you explain someone who uses Emacs to write Python code? Like
me?
Well, if you opened the Pandora's box, lets see how can we
implement this both ways...
The Scheme way (a little more verbose but clearer I would say):
--------
(define (compare a b (comp eq?))
(cond
((and (null? a) (null? b) #t))
((or (null? a) (null? b) #f))
((and (comp (first a) (first b)))
(compare (rest a) (rest b) comp))
(else #f)))
(compare '(1 2 3) '(1 2 3))
(compare '(1 "a" 3) '(1 "a" 3) equal?)
(compare '(1 2 3) '(1 2 4) <=)
---------
Python way:
---------
def eq (a, b) :
return a == b
def compare (a, b, comp = eq) :
if len (a) != len (b) :
return False
for i in xrange (len (a)) :
if not comp (a[i], b[i]) :
return False
return True
compare ([1, 2, 3], [1, 2, 3])
---------
I don't see another way... (Or at least another more different way...)
Which is best? None:
* they both work in linear time (except the Python implementation
checks the length first);
* they are both compact and readable;
* they are both expandable;
Personally I like best the Scheme version, because for example I
can use it like (compare '(1 2 3) '(1 2 4) <=) to compare for less or
equal in case of vectors. In the case of Python I would have needed to
write compare ([1, 2, 3], [1, 2, 4], lambda a, b : a <= b)
Ciprian Craciun.
P.S.: I think of this as a challenge for hackers in both languages
to come up with the most estetic, optimal and useful implementation...
(My implementations are just toys...)
I don't get that impression from Lisp programmers. I suppose it's
only fair that I disclose (1) I admire Lisp, especially Scheme, (2) I
hardly know Lisp at all, and (3) I don't frequent any Lisp forums/
newsgroups/etc.
I do get the impression that Lispers tend to feel Lisp is superior to
all other languages, and I agree that in some ways it is. I don't
think most Lispers' main objection to Python is about "only one
obvious way" but rather things like the limitations, compromises, and
impurities in the language. Certainly compared to Scheme, Python
sacrifices a lot of purity for practicality. (And I guess some fans
of Scheme would argue that Common Lisp does the same!)
Ultimately, Lisp is first and foremost academic (Scheme especially so)
while Python is first and foremost practical. I think Paul Graham's
essays on Lisp exemplify the Lisp mentality.
The kind of Lisp programmer you go on to describe (escalating a simple
problem into a gigantic framework) sounds to me very much like plenty
of Python programmers. A lot of beginners in this very newsgroup ask
"can I just do this simple thing?" and get responses like "well, that
does handle what you need it to, but what if your data is this?
Better add some checking or exception handling. Also, you could make
it run faster by tripling the amount of code as follows..." etc.
John
That's not "pythonic".
Bye,
bearophile
Ok... Then what's pythonic? Please give a pythonic implementation...
Ciprian Craciun.
P.S.: Also, I'm tired of hearing about the pythonic way... Where
do I find a definitive description about the pythonic way? I think
that this word is used only when someone sees something that he
doesn't like, he doesn't know what he doesn't like at it, and just
goes to say its un-pythonic, without saying what would be... Wouldn't
be just easier to say "I don't know" or "I doesn't feel right to me"?
in lisp you'd use (equal a b)
Use the builtin a==b, similar to (equal a b)
But how about extensibility?
== is extensible. To compare two things for equality, use ==.
This is what Carl Banks referred in his original posting. You just
*don't* implement a function that compares two lists, not even as
an exercise for estetic, optimal, and useful implementations -
because you'll know that it won't be useful, anyway, if you can already
use the builtin == in the first place.
I see that you allow for a different comparison function. I do wonder
what the use case for this is - in what application do you have to
compare two lists for equality, and the item's __eq__ is inappropriate?
What would break if you fix the item's __eq__, instead of writing
your own comparison algorithm?
If I would ever run into such a case, I would inline the loop, rather
than writing a function that receives a callable:
for index, v1 in enumerate(l1):
if not comp(v1, l2[index]):
res = False
break
else:
res = True
Regards,
Martin
> In answering the recent question by Mark Tarver, I think I finally hit
> on why Lisp programmers are the way they are (in particular, why they
> are often so hostile to the "There should only be one obvious way to
> do it" Zen).
That's not what the Zen says. The statement you're mis-quoting says,
minus the parenthetical:
There should be one obvious way to do it.
It's only in a parenthetical (“and preferably only one”) that the word
“only” appears. The emphasis is not on having only one way, but on
having one *obvious* way.
--
\ “The optimist thinks this is the best of all possible worlds. |
`\ The pessimist fears it is true.” —J. Robert Oppenheimer |
_o__) |
Ben Finney
> On Sat, Apr 25, 2009 at 11:04 AM, Paul Rubin
> <http://phr...@nospam.invalid> wrote:
> > Use the builtin a==b, similar to (equal a b)
>
> But how about extensibility?
Easily extensible. If you want types that compare in different ways,
just define your new type so that the comparison methods do what you
want them to do.
--
\ “It is the fundamental duty of the citizen to resist and to |
`\ restrain the violence of the state.” —Noam Chomsky, 1971 |
_o__) |
Ben Finney
I observe a phenomenon on language fanatics, no matter what language,
but in particular for the less-than-mainstream language: the desire
to formulate trivial algorithms over and over again, just to enjoy the
beauty of the result (and sometimes the mere fact of being able to do
so).
I suppose people writing real applications in LISP have better things
to spend time on.
Regards,
Martin
A practical example: I have lists that contain strings, but I want
to compare them in an case-insensitive way... Should I update the
__eq__ method (for str class) and break almost everything? Can I write
now a == b? Nop... I need the loop you've just mentioned in all the
places where the comparison changes just in the operator, not in the
algorithm... (I would say this is bad coding practice...)
Regards,
Marek
Yes, you're right, Lisp people have better things to do, because
these algorithms are already implemented for them: just see ormap and
andmap functions that do just what we wanted...
http://download.plt-scheme.org/doc/html/reference/pairs.html#(def._((quote._~23~25kernel)._ormap))
http://download.plt-scheme.org/doc/html/reference/pairs.html#(def._((lib._scheme/private/map..ss)._andmap))
Ciprian.
P.S.: I'm not a language fanatic... I programmed both in Python
and Scheme (mostly in Python)...
P.P.S.: I'm just trying to see why is Python better than Lisp or
vice-versa... I would say they are almost the same: both usable for
day-to-day real world applications...
"a==b" is like (equal a b). "a is b" is like (eq a b).
In Lisp I think you'd use (equal (mapcar upcase a) (mapcar upcase b))
or something like that. In Python, a.upper() == b.upper().
Really, Python uses its object system more heavily than old-school
Lisp did, but in other regards (despite the howls of partisans on both
sides) they are really not that different from each other.
Python tries to be simple and pragmatic while not aiming for as
heavy-duty applications as Common Lisp. Scheme is more of a research
language that's way past its prime. If you like Scheme, you should
try Haskell. Python has the motto "practicality beats purity".
With Haskell, it's exactly the opposite ;-).
I'd claim that this is still theoretical: what are these strings, and
why do you have lists of them that you want to compare?
Why don't you try to lower-case the strings in the list as you
add them?
Also, are you sure a list is the best data structure for your problem?
Perhaps using a set would be better?
> Should I update the
> __eq__ method (for str class) and break almost everything? Can I write
> now a == b? Nop... I need the loop you've just mentioned in all the
> places where the comparison changes just in the operator, not in the
> algorithm... (I would say this is bad coding practice...)
If you want to compare the same lists in many places, this is indeed
bad coding practice. You should try to centralize whatever reasons
you have for comparing the lists into a few methods of the objects
holding the lists.
Regards,
Martin
Indeed the example I've given is purely theoretical. But still, I
could find a use case for such a thing: just imagine we are building a
small shell-like application that reads one line (the commands),
splits it by spaces and always expects to have 4 elements and that
each respects a given regular expression, one specific for each index.
In this case I could syntactically check for correctness by doing
this:
compare (regular_expressions, splitted_line, re.match)
Of course I could have just created a big regular expression for
the entire line. But maybe my 4 elements come from variables obtained
from a web-server query, or the regular expressions are not static but
dynamically generated at run-time.
>> Should I update the
>> __eq__ method (for str class) and break almost everything? Can I write
>> now a == b? Nop... I need the loop you've just mentioned in all the
>> places where the comparison changes just in the operator, not in the
>> algorithm... (I would say this is bad coding practice...)
>
> If you want to compare the same lists in many places, this is indeed
> bad coding practice. You should try to centralize whatever reasons
> you have for comparing the lists into a few methods of the objects
> holding the lists.
>
> Regards,
> Martin
I like object oriented programming, but most of the times we are
just throwing together code and data even when the data has no
behavior and the code is in fact just one possible processing
algorithm. Like in the case you are mentioning, if I tie the
comparison code to the actual data structure, then I'll never be able
to reuse it... But if I leave the code as a standalone function, and
just use the data (or any data that resembles the original structure)
then maybe I'll be able to reuse it...
Ciprian.
Ok, in this case I would write a function:
def validate_commandline(rexes, line):
if len(rexes) != len(line):
raise ValueError("Incorrect number of arguments, expected %d,"
"got %d" % (len(rexes), len(line)))
for i in range(len(line)):
if not re.match(rexes[i], line[i]):
raise ValueError, "Incorrect argument %d" % i
IOW, in this specific case, I would not only want a true/false result,
but also an indication of the actual error to report to the user.
Your universal compare function would be no good here.
Regards,
Martin
Well in fact I would have written it like:
def validate_commandline(rexes, line) :
if not compare (rexes, line, re.match) :
if len (rexes) != len (line) :
raise ValueError ("mismatch len")
mismatch = find_index (rexes, line, re.match, negate = True)
raise ValueError ("mismatch at %d" % (mismatch))
Assuming, that I would have the function find_index.
Ciprian.
> "Ciprian Dorin, Craciun" <ciprian...@gmail.com> writes:
>> A practical example: I have lists that contain strings, but I want
>> to compare them in an case-insensitive way... Should I update the
>> __eq__ method (for str class) and break almost everything? Can I write
>> now a == b? Nop... I need the loop you've just mentioned in all the
>> places where the comparison changes just in the operator, not in the
>> algorithm... (I would say this is bad coding practice...)
>
> In Lisp I think you'd use (equal (mapcar upcase a) (mapcar upcase
> b))
Or simply (equalp a b), since equalp comparisons happen to compare
strings case-insensitively. But that's Common Lisp... overflowing
kitchen sink.
> or something like that. In Python, a.upper() == b.upper().
I guess you meant map(a, str.upper) == map(b, str.upper)? a and b are
lists of strings.
Oh, sorry. Yes, either
map(str.upper, a) == map(str.upper, b)
or
all(str.upper(x)==str.upper(y) for x,y in zip(a,b))
No it doesn't really apply to Scheme. (The Scheme programmer would
have stopped after implementing the new function.)
Some people seem to want to hypertarget everything. Common Lisp seems
to attract these people in big numbers because it has powerful
metaprogramming facilities and expressivity on steroids, making
hypertargeting easy. It's like the Ultimate Enabler language.
> I do get the impression that Lispers tend to feel Lisp is superior to
> all other languages, and I agree that in some ways it is. I don't
> think most Lispers' main objection to Python is about "only one
> obvious way" but rather things like the limitations, compromises, and
> impurities in the language.
I totally disagree. Scheme might be a pure language with no
compromises and impurities, but Common Lisp is certainly not. The
"One Obvious Way" philosophy isn't their main objection so much as the
most emblematic difference.
> Certainly compared to Scheme, Python
> sacrifices a lot of purity for practicality. (And I guess some fans
> of Scheme would argue that Common Lisp does the same!)
>
> Ultimately, Lisp is first and foremost academic (Scheme especially so)
> while Python is first and foremost practical. I think Paul Graham's
> essays on Lisp exemplify the Lisp mentality.
I don't agree. I agree that Lisp programmers think that's their
mentality; I doubt many can actually take fullest advantage of Lisp
the way Graham has. I think Paul Graham is a freak of nature whose
brain is hardwired to notice patterns in places different from where
most peoeple see patterns. Graham, for his part, doesn't seem to
appreciate that what he does is beyond hope for average people, and
that sometimes reality requires average people to write programs.
Carl Banks
The practical way to deal with this issue is to write your own
function when you encounter a situation where == doesn't suffice.
Carl Banks
I think you've hit on the definition of "unpythonic". (No, I don't
have a dictionary definition for you, sorry).
Using a function called "compare" to run a list of regexes against
another list of regexes to get a boolean? And then another find_index
function doing the same where you pass in negate? What is even going
on here?
I, for one, would take Martin's any day of the week. It reads like
good pseudocode as much "proper" Python does.
Brett
From your comments I understand that the only problem with my code
proposal are the function names... Well instead of compare (which was
kept from the beginning of the post) we could just rename it to
"matches". Does the name "matches" matches what it does? :) (If not we
can keep discussing for a proper name...)
And about the find_index, we could rename it to
first_matching_index. About the negation optional parameter, we could
eliminate it if we allow either: to have another function
first_missmatching_index, but this leads to namespace bloat, or we
have a function named negate, that takes another function, and negates
it meaning. (Although i don't see anything wrong in the negate
argument... It's just like having order by asc | desc in SQL...)
Thus the code would have been rewritten as: (we also put the
function on the first argument as its the most important argument)
def validate_commandline(rexes, line) :
if not matches (re.match, rexes, line) :
if len (rexes) != len (line) :
raise ValueError ("mismatch len")
mismatch = first_matching_index (negate (re.match), rexes, line)
raise ValueError ("mismatch at %d" % (mismatch))
Ciprian.
>>> Should I update the
>>> __eq__ method (for str class) and break almost everything? Can I write
>>> now a == b?
Should you instead, perhaps, write a case-insensitive string class, since
seems to be what your data is asking for?
> I like object oriented programming, but most of the times we are
> just throwing together code and data even when the data has no
> behavior and the code is in fact just one possible processing
> algorithm. Like in the case you are mentioning, if I tie the
> comparison code to the actual data structure, then I'll never be able
> to reuse it... But if I leave the code as a standalone function, and
> just use the data (or any data that resembles the original structure)
> then maybe I'll be able to reuse it...
I'd say the reverse, personally. A lot of what you've demonstrated is
varying algorithms, which are rarely going to reuse standalone code.
They still need to deal with case-insensitive strings, though.
--
Rhodri James *-* Wildebeeste Herder to the Masses
> Graham, for his part, doesn't seem to appreciate that what he does is
> beyond hope for average people, and that sometimes reality requires
> average people to write programs.
I think he understands that perfectly well. But I think he believes
that the sorts of tools which help average people write programs get in
the way of true wizards.
I think I agree.
On the other hand, I don't think Python actually does get in the way
very much.
-- [mdw], Lisp hacker.
Actually, Graham doesn't have particularly strong objection to
Python. Partly this is because he sees it as being largely as capable
and expressive as Lisp (mainly sans macros, of course); partly because
he sees that Python tends to attract good programmers (chief among
them Trevor Blackwell).
In my view, what is remarkable about Python is that it is so
accessible to average programmers (and frankly, even rather poor
programmers) while still managing to stay appealing to top-notch
programmers.
That said, my experience with Lisp programmers has mainly been with
people who like Scheme, which may explain why Carl Banks and I have
different impressions of Lisp programmers. (We also seem to differ on
how accurate it is to refer to Scheme as Lisp.) But in my experience,
Lisp in any form tends not to attract average programmers, and
certainly not poor programmers. I don't mean to say Banks is wrong; I
said up front my exposure to the Lisp community is limited. I am just
giving my own impressions.
Python is easily powerful enough and expressive enough to be an
"enabler language". I guess not Ultimate, but close enough that
Graham isn't particularly turned off by it!
John
I see you walk both sides. :)
He's having fun with it, not writing it to meet a deadline. Who never
reimplemented things for the sheer fun of it? The fun of writing it to
top the current implementation, to learn how it works or merely as
example to fellow programmers?
Oh, I know who never did it: programmers who are into programming only
for the paycheck and who otherwise think it's a bore.
> Lisp programmer:
>
> Well, there is a standard function called mismatch that does it, but I
> can't recommend it. First of all, you don't know where that function's
> been. Anyone and their mother could have worked on it, did they have
> good, sound programming practice in mind when they wrote it? Of course
> not. Let's be real here, we have to implement this by hand.
[snip]
That's great stuff! An unfair and untrue caricature, but still great :)
--
Steven
I can assure you that even Scheme is a language full
of compromises and inconsistencies :-/
Michele, who is now writing a book about Scheme
http://www.phyast.pitt.edu/~micheles/scheme/TheAdventuresofaPythonistainSchemeland.pdf
> On Sat, Apr 25, 2009 at 10:43 AM, <bearoph...@lycos.com> wrote:
>> Ciprian Dorin, Craciun:
>>> Python way:
>>> ---------
>>> def eq (a, b) :
>>> return a == b
>>>
>>> def compare (a, b, comp = eq) :
>>> if len (a) != len (b) :
>>> return False
>>> for i in xrange (len (a)) :
>>> if not comp (a[i], b[i]) :
>>> return False
>>> return True
>>
>> That's not "pythonic".
>>
>> Bye,
>> bearophile
>> --
>> http://mail.python.org/mailman/listinfo/python-list
>
> Ok... Then what's pythonic? Please give a pythonic implementation...
Don't re-invent the wheel. Instead of creating your own functions, use
existing tools to your advantage.
import operator
def compare(a, b, comp=operator.eq):
if len(a) != len(b):
return False
for a, b in zip(a, b):
if not comp(a[i], b[i]):
return False
return True
But we can re-write that to be even more pythonic, by using the built-in
all():
def compare(a, b, comp=operator.eq):
if len(a) != len(b):
return False
return all(comp(x, y) for (x, y) in zip(a, b))
or even:
def compare(a, b, comp=operator.eq):
return (len(a) == len(b)) and all(comp(*t) for t in zip(a, b))
(All the above are untested, so please excuse any errors.)
> Ciprian Craciun.
>
> P.S.: Also, I'm tired of hearing about the pythonic way... Where
> do I find a definitive description about the pythonic way?
There is no such thing as a definitive description of pythonic -- it is
like art, and pornography: you can recognise it when you see it (except
when you can't).
However, you can get close by doing:
import this
in the Python interactive interpreter. Or from a shell prompt:
python -m this
> I think that
> this word is used only when someone sees something that he doesn't like,
> he doesn't know what he doesn't like at it, and just goes to say its
> un-pythonic, without saying what would be... Wouldn't be just easier to
> say "I don't know" or "I doesn't feel right to me"?
I think that there's a risk that people over-use unpythonic when they
mean "I don't like it", but that doesn't mean that pythonic isn't a
meaningful concept. However, it is a matter of degree, not kind: like
mole-hills and mountains, unpythonic and pythonic are very different
things, but there's no precise dividing line between them.
--
Steven
>>>> Ok... Then what's pythonic? Please give a pythonic
>>>> implementation...
>>> Use the builtin a==b, similar to (equal a b)
>>
>> But how about extensibility?
>
> == is extensible. To compare two things for equality, use ==.
>
> This is what Carl Banks referred in his original posting. You just
> *don't* implement a function that compares two lists, not even as an
> exercise for estetic, optimal, and useful implementations - because
> you'll know that it won't be useful, anyway, if you can already use the
> builtin == in the first place.
>
> I see that you allow for a different comparison function. I do wonder
> what the use case for this is - in what application do you have to
> compare two lists for equality, and the item's __eq__ is inappropriate?
The above doesn't really compare for equality, it's a generic element-by-
element comparison function, and so it is inappropriate to contrast it to
__eq__ alone. Defaulting to equality testing is misleading, and if I were
writing such a function I'd remove the default.
compare(a, b, operator.eq) gives the same result as the simpler a == b,
but compare(a, b, operator.lt) does something very different to a < b. I
can't think of an application for element-by-element comparisons off the
top of my head, but if the numpy people use it, there must be a need :)
--
Steven
(equal a b) or (equalp a b)
Next question??
I understand where you are going with the analogy, but I think a lot
of what you describe as 'over-thinking' of the problem in lisp comes
from people having an honest desire to answer the /right/ question.
Lisp gives you a bit of granularity with regard to certain things
(like comparison), that in many languages amount to a single '=='
operator. I /believe/ that this is because of its origins in symbolic
programming.
You don't compare numeric functions in Perl and C, and then say 'Oh
those silly C programmers over-thinking things, they must have 10
different types of numbers!'
Going from Scheme to Haskell is about the same as going from Python to
Ruby: you get far more concise and obfuscated syntax and get boggled
down in tons of different ways to do the same thing.
I liked very much your implementation for the compare function, it
is very short and at the same time readable:
> def compare(a, b, comp=operator.eq):
> return (len(a) == len(b)) and all(comp(*t) for t in zip(a, b))
But I have only one problem, it is suboptimal, in the sense that:
* it constructs two intermediary lists (the list comprehension and
the zip call);
* it evaluates all the elements, even if one is false at the beginning;
So, could you overcome these problems?
About the pythonic vs unpythonic words, I agree with you, there
are ofter overused and misused...
Ciprian.
I agree with your comment, the presented function compare does
more than this, in fact checks if the elements in the two functions
match a given binary predicate. (In Scheme this is named "andmap")
About the compare (a, b, operator.lt) it does the same as a < b,
where a and b are lists of numbers.
An usage for the compare function (as I've shown in a previous
post to this thread) could also have been checking a list of strings
if they match to a list of regular expressions (we rename the function
compare to the name "match" as it is much general):
match (re.match, regexps, strings)
About the defaults, I really, really, agree with your comment:
"Defaulting to equality testing is misleading"... Most of the times
equality means equality by value, but sometimes you need the equality
to be more relaxed (maybe ignore case, maybe ignore spaces, or maybe
for real numbers to ignore a difference smaller than a chosen delta
(those writing scientific applications know this too well))...
Ciprian.
This evaluates just until finding one that is false:
return (len(a) == len(b)) and not any(not comp(*t) for t in
(zip(a, b)))
plus the zip call enclosed in parentheses got turned into an iterator.
>>> def compare(a, b, comp=operator.eq):
... return (len(a) == len(b)) and not any(not comp(a,b) for (a,b) in
(zip(a, b)))
...
>>> compare( [1,2,3], [1,3,3] )
False
>>> compare( [1,2,3], [1,2,3] )
True
> I liked very much your implementation for the compare function, it
> is very short and at the same time readable:
>
>> def compare(a, b, comp=operator.eq):
>> return (len(a) == len(b)) and all(comp(*t) for t in zip(a, b))
>
> But I have only one problem, it is suboptimal, in the sense that: *
> it constructs two intermediary lists (the list comprehension and
> the zip call);
If you look closely, there is no list comprehension. The argument to
all() is a generator expression, which does not construct an intermediary
list.
However, you are right that zip produces a list, at least in Python 2.x.
In Python 3 it produces a generator-like object.
> * it evaluates all the elements, even if one is false at the
> beginning;
No, all() uses short-cut evaluation. It will return as soon as it hits a
False element.
> So, could you overcome these problems?
In Python 2.4 or better, I can remove the intermediate list produced by
zip with one line:
from itertools import izip as zip
(This may even work in 2.3, but I don't have 2.3 to test it.)
--
Steven
If I'm reading that correctly, I think I'd write it as:
from itertools import imap, izip
return (len(a) == len(b)) and all(imap(comp, izip(a, b)))
That is more concise and avoids building an intermediate list with zip.
Maybe we need something like zipWith ...
zip in python 2.x always makes a list. You want itertools.izip.
You could also use itertools.starmap.
> Ciprian Dorin, Craciun wrote:
>> On Sun, Apr 26, 2009 at 7:54 AM, Steven D'Aprano
>> <st...@remove-this-cybersource.com.au> wrote:
>> I liked very much your implementation for the compare function, it
>> is very short and at the same time readable:
>>
>>> def compare(a, b, comp=operator.eq):
>>> return (len(a) == len(b)) and all(comp(*t) for t in zip(a, b))
>>
>> But I have only one problem, it is suboptimal, in the sense that:
>> * it constructs two intermediary lists (the list comprehension and
>> the zip call);
No, it only constructs one list (the zip() one) and only in Python 2.x -
in Python 3.x, zip return a special 'zip object'. There is no list
comprehension. It's a generator expression [1].
To avoid the list created by zip in python 2.x (for x large enough!),
just do:
from itertools import izip as zip
Another way to define the function that may appeal to you, as a lisper.
def compare(a, b, comp=operator.eq):
return len(a) == len(b) and all(map(comp, a, b))
The same remark applies to map() here as to zip() above.
>
>> * it evaluates all the elements, even if one is false at the beginning;
It does not [2].
> This evaluates just until finding one that is false:
>
> return (len(a) == len(b)) and not any(not comp(*t) for t in
> (zip(a, b)))
>
> plus the zip call enclosed in parentheses got turned into an iterator.
not any(not i for i in iterable) is not an optimisation of
all(iterable). Refer to [2]. Moreover, putting a list in parenthesis
does not magically turn it into a generator.
--
Arnaud
[1] http://www.python.org/dev/peps/pep-0289/
[2] http://docs.python.org/library/functions.html
> namekuseijin <namekusei...@gmail.com> writes:
>> ... return (len(a) == len(b)) and not any(not comp(a,b) for (a,b)
>> in (zip(a, b)))
>
> If I'm reading that correctly, I think I'd write it as:
>
> from itertools import imap, izip
> return (len(a) == len(b)) and all(imap(comp, izip(a, b)))
Do you mean imap(comp, a, b)?
--
Arnaud
No, the problem is that you are using way too many functions, that do
too little. The problem with that is then that you have to give names
to all the functions, which then find people difficult to read because
they don't just need to the code in question itself; they also need to
dozen of helper functions that it relies on.
> And about the find_index, we could rename it to
> first_matching_index. About the negation optional parameter, we could
> eliminate it if we allow either: to have another function
> first_missmatching_index, but this leads to namespace bloat, or we
> have a function named negate, that takes another function, and negates
> it meaning.
Or you could avoid introducing the function altogether, to make it more
readable. This makes it more pythonic, also: readability counts (from
the Zen of Python).
Regards,
Martin
Oh yes, I forgot you can do that. Thanks.
So if I'm reading right you are saying something in the lines:
"using too many functions is bad just because it is unreadable and
non-understandable to average (could I say mediocre?) programmers"...
Unfortunately I thought that delegating responsibilities to other
functions, and thus writing small chunks of code, is what good
software engineering is... Well my bad...
(As a side-note, maybe this is a reason why some of my students
find it hard to understand functional programming -- too many
functions that is -- I shall have to revise my teaching, and tell the
students to inline everything, maybe they'll understand it like this
:) )
Although you have a point -- that of being hard to comprehend by
average programmers -- but this doesn't mean it is a wrong (as in
ugly) solution... Also, with respects, but the "pythonic" solution
involving generators (or iterators) and "zip" or "all" function --
although I appreciate it as it comes close to FP -- is not what I
would call readable and understandable by non-guru programmers...
Ciprian Craciun.
I've noticed that every one of you is wrong about programming.
Since I can't say it effectively, here's someone who can:
http://www.youtube.com/watch?v=XHosLhPEN3k
That's the answer.
That works and is nice and readable:
import operator
from itertools import imap
def equal_sequences(a, b, comp=operator.eq):
"""
a and b must have __len__
>>> equal_sequences([1, 2, 3], [1, 2, 3])
True
>>> L1, L2 = [1, 2, 3], [1, -2, 3]
>>> equal_sequences(L1, L2)
False
>>> equal_sequences(L1, L2, lambda x,y: abs(x) == abs(y))
True
>>> L3, L4 = ["hello", "HALLO"], ["hello", "hallo"]
>>> equal_sequences(L3, L4)
False
>>> equal_sequences(L3, L4, lambda x,y: x.lower() == y.lower())
True
"""
return len(a) == len(b) and all(imap(comp, a, b))
if __name__ == "__main__":
import doctest
doctest.testmod()
print "Doctests finished.\n"
But both sequences must have a len. Otherwise you may use (if you
don't have izip_longest the folllowing code gets longer):
"""
>>> equal_items([1, 2], [1, 2, 3])
False
>>> equal_items([1, 2, 3], [1, 2, 3])
True
>>> equal_items([1, 2, 3], [1, -2, 3])
False
>>> equal_items([1, 2, 3], [1, -2, 3], abs)
True
>>> equal_items([1, 2, 3], [1, -2, 4], abs)
False
>>> L1, L2 = ["hello", "HALLO"], ["hello", "hallo"]
>>> equal_items(L1, L2)
False
>>> equal_items(L1, L2, str.lower)
True
>>> equal_items(xrange(3), (i for i in xrange(3)))
True
>>> equal_items([0, 1, 2], (i for i in xrange(3)))
True
>>> equal_items([0, 1, 2, 3], (i for i in xrange(3)))
False
>>> equal_items([-0, -1, -2], (i for i in xrange(3)), key=abs)
True
>>> equal_items([-0, -1, -2, -3], (i for i in xrange(3)), key=abs)
False
>>> x = []
>>> equal_items( (x for i in range(3)), (x for i in range(3)) )
True
>>> equal_items( (x for i in range(3)), (x for i in range(4)) )
False
>>> equal_items( (x for i in range(3)), (x for i in range(3)), key=id)
True
>>> equal_items( (x for i in range(3)), (x for i in range(4)), key=id)
False
>>> equal_items( (x for i in range(3)), (x for i in range(3)), key=lambda x:x)
True
>>> equal_items( (x for i in range(3)), (x for i in range(4)), key=lambda x:x)
False
"""
from itertools import izip_longest
def equal_items(iter1, iter2, key=None):
try:
len_iter1 = len(iter1)
len_iter2 = len(iter2)
except TypeError:
pass
else:
if len_iter1 != len_iter2:
return False
class Guard(object): pass
if key is None:
for x, y in izip_longest(iter1, iter2, fillvalue=Guard()):
if x != y:
return False
else:
try:
for x, y in izip_longest(iter1, iter2, fillvalue=Guard()):
if key(x) != key(y):
return False
except TypeError: # intercepts key(guard)
return False
return True
if __name__ == "__main__":
import doctest
doctest.testmod()
print "Doctests finished.\n"
You can write hairy code in Python too, not just in CLisp :-)
Bye,
bearophile
The solution would be to use itertools.izip() instead of the
stock zip(). The all() function short-circuits at the first
non-True value. Thus, using izip() instead will (1) not create
any new lists (it's a generator, not a list) and (2) the all()
will only look until it fails.
Just make sure that your comp() function returns equality for
this to work, or otherwise use "not comp()" (which is germane if
you use a __cmp__ function that returns 0 for equality)
-tkc
def equal_items(iter1, iter2, key=lambda x: x):
class Guard(object): pass
try:
for x, y in izip_longest(iter1, iter2, fillvalue=Guard()):
if key(x) != key(y):
return False
except TypeError: # intercepts key(guard)
return False
return True
Bye,
bearophile
I think you're exaggerating. Go ask this question in c.l.l and the
first answer you'll get is mismatch.
But, from the other point of view your exaggeration makes sense: Lisp
unlike Python, IMO, is the language, where it's pleasant to program
not only applications, but the internals as well. So some people may
find interest in reprogramming what's already there. In lisp
programmer's mentality it's good to know, that you have that ability.
And let's look at my recent experience with Python: I wanted to
implement a daemon process and stumbled at a simplest problem with
threading: neither Thread, nor Threading module provides thread-
killing possibility. Surely, I'm not so experienced in Python as in
Lisp (in which I'd definitely be able to solve this problem by
extending the library), but I don't see an obvious solution, which
will stay inside the language: I have to either use the shell or stick
to the limited set of provided options and skew my program design to
work with them. Any other suggestions?
P.S. Btw the other issue with CL's mismatch is that it provides a
possibility to use any test and keyword extraction function.
Best regards,
Vsevolod Dyomkin
You don't want to silence TypeErrors that may arise from with key() when
x or y is not a Guard, as it could hide bugs in key(). So I would write
something like this:
def equal_items(iter1, iter2, key=lambda x: x, _fill = object()):
for x, y in izip_longest(iter1, iter2, fillvalue=_fill):
if x is _fill or y is _fill or key(x) != key(y):
return False
return True
(untested)
Another way would be:
def equal_items(iter1, iter2, key=lambda x: x):
iter1, iter2 = iter(iter1), iter(iter2)
for x, y in izip(iter1, iter2):
if key(x) != key(y):
return False
for x, y in izip_longest(iter1, iter2):
return False
return True
(untested)
--
Arnaud
> Another way would be:
>
> def equal_items(iter1, iter2, key=lambda x: x):
> iter1, iter2 = iter(iter1), iter(iter2)
> for x, y in izip(iter1, iter2):
> if key(x) != key(y):
> return False
> for x, y in izip_longest(iter1, iter2):
> return False
> return True
>
> (untested)
Or even:
def equal_items(iter1, iter2, key=lambda x: x):
iter1, iter2 = iter(iter1), iter(iter2)
for x, y in izip(iter1, iter2):
if key(x) != key(y):
return False
return not any(izip_longest(iter1, iter2))
(untested)
Or even:
def equal_items(iter1, iter2, key=lambda x: x):
iter1, iter2 = iter(iter1), iter(iter2)
if any(key(x) != key(y) for x, y in izip(iter1, iter2)):
return False
return not any(izip_longest(iter1, iter2))
--
Arnaud
> def equal_items(iter1, iter2, key=lambda x: x):
> iter1, iter2 = iter(iter1), iter(iter2)
> for x, y in izip(iter1, iter2):
> if key(x) != key(y):
> return False
> for x, y in izip_longest(iter1, iter2):
> return False
> return True
>
> (untested)
This will fail when iter1 yields one more item than iter2. izip() then
consumes one extra item:
>>> from itertools import izip
>>> a = iter([1,2])
>>> list(izip(a, "b"))
[(1, 'b')]
>>> a.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Peter
>CDC> About the compare (a, b, operator.lt) it does the same as a < b,
>CDC> where a and b are lists of numbers.
>>> a=[1, 2, 3]
>>> b=[1, 2, 4]
>>> compare (a, b, operator.lt)
False
>>> a < b
True
--
Piet van Oostrum <pi...@cs.uu.nl>
URL: http://pietvanoostrum.com [PGP 8DAE142BE17999C4]
Private email: pi...@vanoostrum.org
You are right, thank you. Darn exceptions. You are often able to find
bugs in my code.
Here is a new version then (this is designed to be usable in practice,
that's why it's longer):
from itertools import izip_longest
def equal_iter(iter1, iter2, key=None):
"""
>>> equal_iter([1, 2], [1, 2, 3])
False
>>> equal_iter([1, 2, 3], [1, 2, 3])
True
>>> equal_iter([1, 2, 3], [1, -2, 3])
False
>>> equal_iter([1, 2, 3], [1, -2, 3], abs)
True
>>> equal_iter([1, 2, 3], [1, -2, 4], abs)
False
>>> L1, L2 = ["hello", "HALLO"], ["hello", "hallo"]
>>> equal_iter(L1, L2)
False
>>> equal_iter(L1, L2, str.lower)
True
>>> equal_iter(xrange(3), (i for i in xrange(3)))
True
>>> equal_iter([0, 1, 2], (i for i in xrange(3)))
True
>>> equal_iter([0, 1, 2, 3], (i for i in xrange(3)))
False
>>> equal_iter([-0, -1, -2], (i for i in xrange(3)), key=abs)
True
>>> equal_iter([-0, -1, -2, -3], (i for i in xrange(3)), key=abs)
False
>>> x = []
>>> equal_iter( (x for i in range(3)), (x for i in range(3)) )
True
>>> equal_iter( (x for i in range(3)), (x for i in range(4)) )
False
>>> equal_iter( (x for i in range(3)), (x for i in range(3)),
key=id)
True
>>> equal_iter( (x for i in range(3)), (x for i in range(4)),
key=id)
False
>>> equal_iter( (x for i in range(3)), (x for i in range(3)),
key=lambda x:x)
True
>>> equal_iter( (x for i in range(3)), (x for i in range(4)),
key=lambda x:x)
False
>>> # bug found by Arnaud Delobelle
>>> def k(x): raise TypeError
>>> equal_iter( (x for i in range(3)), (x for i in range(3)),
key=k)
Traceback (most recent call last):
...
TypeError
"""
try:
len_iter1 = len(iter1)
len_iter2 = len(iter2)
except TypeError:
pass
else:
if len_iter1 != len_iter2:
return False
class Guard(object): pass
guard = Guard()
if key is None:
for x, y in izip_longest(iter1, iter2, fillvalue=guard):
if x != y:
return False
else:
for x, y in izip_longest(iter1, iter2, fillvalue=guard):
if x is guard or y is guard or key(x) != key(y):
return False
return True
if __name__ == "__main__":
import doctest
doctest.testmod()
print "Doctests finished.\n"
Bye,
bearophile
> Arnaud Delobelle wrote:
>
>> def equal_items(iter1, iter2, key=lambda x: x):
>> iter1, iter2 = iter(iter1), iter(iter2)
>> for x, y in izip(iter1, iter2):
>> if key(x) != key(y):
>> return False
>> for x, y in izip_longest(iter1, iter2):
>> return False
>> return True
>>
>> (untested)
>
> This will fail when iter1 yields one more item than iter2. izip() then
> consumes one extra item:
Ah yes! And this is why I should have tested it.
--
Arnaud
The problem is that thread-killing (in the literal sense) doesn't work.
Unlike processes, there's no thread-environment encapsulation at the OS
level, which means that things don't get cleaned up properly. Even Java
has mostly given up on thread-killing. The only way to kill threads
safely is to have them terminate themselves. Your other option is to use
multiple processes.
--
Aahz (aa...@pythoncraft.com) <*> http://www.pythoncraft.com/
"If you think it's expensive to hire a professional to do the job, wait
until you hire an amateur." --Red Adair
That is a wonderful link. Thanks for sharing.
--Scott David Daniels
Scott....@Acm.Org
import mentality? help( mentality ).
Oh, you mean Blub?
http://www.paulgraham.com/avg.html
;-)
--
Mikael Jansson
http://mikael.jansson.be
No, this style is also unreadable to advanced programmers, in
particular when you fail to include the functions you use, and
leave the reader with guesswork.
> Unfortunately I thought that delegating responsibilities to other
> functions, and thus writing small chunks of code, is what good
> software engineering is... Well my bad...
You misunderstood indeed. Splitting a large algorithm into smaller
parts is good if the original algorithm cannot be easily understood
in a single piece. By splitting it, you introduce new abstractions
that can help to simplify the original algorithm. OTOH, adding
these abstractions also requires the reader to familiarize himself
with the abstractions. There is a tradeoff, and the optimum is
somewhere in the middle.
Splitting code into tiny functions certainly is *not* good software
engineering.
> Although you have a point -- that of being hard to comprehend by
> average programmers -- but this doesn't mean it is a wrong (as in
> ugly) solution... Also, with respects, but the "pythonic" solution
> involving generators (or iterators) and "zip" or "all" function --
> although I appreciate it as it comes close to FP -- is not what I
> would call readable and understandable by non-guru programmers...
Please go back to my formulation - it doesn't use any of this, and
I agree that the formulations with zip, generators, etc are indeed
more difficult to read (perhaps *precisely* because they come
closer to FP).
Regards,
Martin
hmm, somehow I thought putting any sequence into parenthesis would
always yield a generator...
Writing software is writing for a reader: the maintainer or extender
of the code you are writing. That that person may be you in three
years is incidental. You should struggle to write clearly, and take
any clues you can get about how to make the code clearer. This doesn't
mean you must explain the language; you may assume the reader knows
(or can quickly find in manuals) what any particular language feature
means. What you must explain is how you are solving the problem at
every point in the code where the code itself does not make that clear.
I don't remember who, but something famously said, in effect:
Debugging is hard, maybe twice as hard as writing the code in
the first place. Unless you are one of those nonexistent few
who always write correct programs from the word go, you will
have to debug your own code before it works fully correctly.
Therefore, you had better write code so simple that you can
know what is going wrong with it. If your code is too hard
to understand for the average programmer, you are either four
times as brilliant as those "average" programmers or you are
in big trouble.
--Scott David Daniels
Scott....@Acm.Org
Hmm, perhaps it was the answer by the time that song was written? ;)
cool anyway... :)
Cheers,
Vsevolod
Fom my .sig database:
"Debugging is twice as hard as writing the code in the first place.
Therefore, if you write the code as cleverly as possible, you are, by
definition, not smart enough to debug it." --Brian W. Kernighan
What could have made you think I was exaggerating. Could it be the
line where I said "Carl, Banks who mighe exaggerating"?? :)
> But, from the other point of view your exaggeration makes sense: Lisp
> unlike Python, IMO, is the language, where it's pleasant to program
> not only applications, but the internals as well. So some people may
> find interest in reprogramming what's already there. In lisp
> programmer's mentality it's good to know, that you have that ability.
And yet, the impression I get from Lisp programmers is that they
prefer to program internals. They will say it's an advantage of Lisp
that their list type is handled with low-level primitives like car,
cdr, and cons, rather than high-level operations like indexing,
appending, etc. Why would that be an advantage unless they are doing
that kind of stuff all the time?
> And let's look at my recent experience with Python: I wanted to
> implement a daemon process and stumbled at a simplest problem with
> threading: neither Thread, nor Threading module provides thread-
> killing possibility. Surely, I'm not so experienced in Python as in
> Lisp (in which I'd definitely be able to solve this problem by
> extending the library), but I don't see an obvious solution, which
> will stay inside the language: I have to either use the shell or stick
> to the limited set of provided options and skew my program design to
> work with them. Any other suggestions?
Say you are running a thread and you want the power to be able to kill
it at any time. The thread is either communicating with the rest of
the program periodically, or it isn't. If it is, then there are ample
opportunities to tell the thread to terminate itself. If it isn't,
then you might as well use a separate process which you can kill.
Next question.
Carl Banks
That's not so satisfying. If you use a separate process, it can't
share Python objects with the main process, isn't under the same
memory management, etc. With the standard modules that comes with
Python, you can't share memory at all (except with mmap, which gives
no synchronization mechanisms). You can't pass open sockets from one
process to another with the standard library, making it harder to
implement typical multi-threaded servers. You do get better
scalability, but at the expense of having to serialize all IPC data
and use heavyweight communication mechanisms. Threads exist because
they are useful.
Which is "communicating with the rest of the program periodically".
Presumably you have to protect objects to share them? There you go:
anytime you try to acquire a lock have the thread check to see whether
to abort.
Carl Banks
--Scott David Daniels
Scott....@Acm.Org
Normally, acquiring a lock doesn't require running code in other
threads, at least in the uncontended case. If you have to switch
threads twice in order to acquire a lock, your implementation could
use some improvement.
Right, it's better to recognize this sort of problem than to brush it
off. There was a discussion in sourceforge a long time ago about
adding a way to raise exceptions in threads, and I think there is some
way to do it through the C API.
> On Sun, Apr 26, 2009 at 12:08 PM, "Martin v. Löwis" <mar...@v.loewis.de>
> wrote:
>> No, the problem is that you are using way too many functions, that do
>> too little. The problem with that is then that you have to give names
>> to all the functions, which then find people difficult to read because
>> they don't just need to the code in question itself; they also need to
>> dozen of helper functions that it relies on.
>> Or you could avoid introducing the function altogether, to make it more
>> readable. This makes it more pythonic, also: readability counts (from
>> the Zen of Python).
> So if I'm reading right you are saying something in the lines:
> "using too many functions is bad just because it is unreadable and
> non-understandable to average (could I say mediocre?) programmers"...
> Unfortunately I thought that delegating responsibilities to other
> functions, and thus writing small chunks of code, is what good software
> engineering is... Well my bad...
Also from the Zen: flat is better than nested. One of the aspects of
flatter call trees and object hierarchies is that I hit the bottom
(language features or the standard library) sooner, and I "should"
already have the language and its standard library in my brain. That
said, I also tend to break my programs into layers, but I do try to make
each layer as thin as possible (but no thinner).
> Although you have a point -- that of being hard to comprehend by
> average programmers -- but this doesn't mean it is a wrong (as in ugly)
> solution... Also, with respects, but the "pythonic" solution involving
> generators (or iterators) and "zip" or "all" function -- although I
> appreciate it as it comes close to FP -- is not what I would call
> readable and understandable by non-guru programmers...
I finally got it through my thick head that I've been *doing* functional
programming with [*nix] shells and pipes for years, well back into my non-
guru days.
Dan
--
Dan Sommers A death spiral goes clock-
<http://www.tombstonezero.net/dan/> wise north of the equator.
Atoms are not things. -- Werner Heisenberg -- Dilbert's PHB
> Paul Rubin:
>> Arnaud Delobelle:
>> > Do you mean imap(comp, a, b)?
>>
>> Oh yes, I forgot you can do that. Thanks.
>
> That works and is nice and readable:
>
>
> import operator
> from itertools import imap
>
> def equal_sequences(a, b, comp=operator.eq):
[snip]
Sorry, this is worse than unreadable. It is *misleading*. It doesn't test
for equal sequences except as a special case. What it does is perform a
generic element-by-element comparison, not necessarily an equality test.
What was wrong with the name compare() used in previous posts in this
thread?
--
Steven
Does Lisp even have OS-level threads? What Lisp are you using, on what
OS? Are they perhaps Erlang-style cooperative threads instead?
Come on, you're just making stuff up. How the *hell* do you get
switching threads twice out of that? I'm saying that if threads have
to synchronize data, then it's no big deal to have the thread check a
flag over whether to terminate when synchronizing. Take a look at
this recipe for an example:
http://code.activestate.com/recipes/576461/
That concept, I say, can be applied to any thread that shares data
with the main thread, with any sort of regularity, even if it's
doesn't use queues specifically.
The only time it's any trouble to notify a thread to terminate is when
that thread's running code that's not aware it's in a threaded app
(which implies that it's not sharing any data, aside from read-only).
But since it's not communicating with another threads you might as
well run it in another process; communication overhead is not going to
be a big deal in that case.
Carl banks
I agree with your opinion about keeping the abstraction layers
shallow, but in my view high-order and helper functions do not
comprise a new abstraction layer. For example in Lisp, using map,
reduce (fold), or any other high-order function is just like using
for, or while in a normal imperative language.
Another example is the simple Observer pattern. How many think of
it as a new layer? Instead anytime we see a method that is named
add_listener we already have an idea of what it does... So we could
say that in FP world there are some patterns that involve "delegating
control" to them.
And also I would like to point out that "hitting the language
sooner", means to know every function in the standard Python library
(which is by far uncomprehensible, its huge) and most of the times you
also need the documentation. And if we go this path, when debugging we
could use a smart IDE, which should show as tool-tip-text for function
names their documentation, and in this way all we have to do to
understand a particular function is just to point it out.
Ciprian.
Different Lisp implementations provide different solutions. SBCL
provides OS-level threads (on Linux), which I personally use, while
CMUCL offers green threads. Allegro, LispWorks, Clozure CL, Sceineer
CL and ECL as well have threading, but I don't use them, so won't
speak, which implementation of threading they have. There's a common
unification library -- bordeaux-threads -- that abstracts away
implementation specifics. It's API includes the function destroy-
thread.
As well I'd like to outline, that, IMO, your answer exhibits the
common attitude among pythonistas: everything should be done in one
true way, which is the best option (and that is how it's implemented
in the current version of the language). As of PEP-20: "There should
be one-- and preferably only one --obvious way to do it. Although that
way may not be obvious at first unless you're Dutch." And if someone
disagrees -- he just doesn't understand...
Cheers,
Vsevolod
> There's a common unification library -- bordeaux-threads --
> that abstracts away implementation specifics. It's API includes
> the function destroy-thread.
Which is deprecated, like the Java one. It's not hard to provide
a kill thread call, if you don't mind it having undefined semantics.
"This should be used with caution: it is implementation-defined
whether the thread runs cleanup forms or releases its locks first."
This doesn't mean deprecated. It means: implementation-dependent. For
example in SBCL: "Terminate the thread identified by thread, by
causing it to run sb-ext:quit - the usual cleanup forms will be
evaluated". And it works fine.
Best regards,
Vsevolod
> I don't remember who, but something famously said, in effect:
> Debugging is hard, maybe twice as hard as writing the code in
> the first place. Unless you are one of those nonexistent few
He would be the K in K&R.
I think I mis-read your suggestion. I thought you meant to query
another thread before acquiring a lock.
Anyway, the usual reason to want to kill a thread is that it's become
unresponsive for some reason, while holding onto some resource like a
lock or a bound socket. This would be in the category of fault
recovery from conditions that weren't expected ahead of time.
Another situation is when you want to just stop the program
altogether, similar to kill -9'ing it, with no attempt to shut down
gracefully. Kind of a global ctrl-C. Right now there is no way to do
that for a multithreaded program except by literally sending a process
kill, which is pretty ugly.
Hahaha. I love it.
Did you see my comment about Java? This particular issue has little to
do with Python. I won't disagree that what you're describing is
sometimes a problem in the Python community, but you're picking the
wrong issue to claim its relevance.
--
Aahz (aa...@pythoncraft.com) <*> http://www.pythoncraft.com/
OK, I'm not a big expert in Python. That was just the thing, that was
important to me recently. I won't claim, that it's regularly a problem
with Python, although from reading Guido's blog I get that impression.
(Well, I understand, that's how BDFLs should behave :)
Yet there was no response to my point, that the original example was
not realistically depicting the Lisp world, while more characteristic
of the Python one.
Best regards,
Vsevolod
That's because there's no response to make; the original post was a joke,
and trying to have a serious discussion about it rarely excites people.
If you want to talk about Python and problems you're running into, you
should start a new thread.
> "This should be used with caution: it is implementation-defined
> whether the thread runs cleanup forms or releases its locks first."
> This doesn't mean deprecated. It means: implementation-dependent. For
> example in SBCL: "Terminate the thread identified by thread, by
> causing it to run sb-ext:quit - the usual cleanup forms will be
> evaluated". And it works fine.
I'm curious - do you know what happens if threading is implemented as
a native OS thread and it's stuck in an I/O operation that is blocked?
How does the Lisp interpreter/runtime gain control again in order to
execute the specified function? I guess on many POSIX-ish
environments, internally generating a SIGALRM to interrupt a system
operation might work, but it would likely have portability problems.
Or is that combination (native OS thread and/or externally blocking
I/O) prevented by the runtime somehow (perhaps by internally polling
what appears to code as blocking I/O)? But surely if there's an
access to OS routines, the risk of blocking must be present?
That scenario is really the only rationale use case I've run into for
wanting to kill a thread, since in other cases the thread can be
monitoring for an application defined way to shut down.
-- David
Someone wrote a paper proposing some Python language extensions to
support asynchronous exceptions in Python threads. I don't have the
url handy but you can probably search for it.
> On Mon, Apr 27, 2009 at 2:14 AM, Dan Sommers <somm...@bellsouth.net>
> wrote:
>> Also from the Zen: flat is better than nested. One of the aspects of
>> flatter call trees and object hierarchies is that I hit the bottom
>> (language features or the standard library) sooner, and I "should"
>> already have the language and its standard library in my brain. That
>> said, I also tend to break my programs into layers, but I do try to
>> make each layer as thin as possible (but no thinner).
>
> I agree with your opinion about keeping the abstraction layers
> shallow, but in my view high-order and helper functions do not comprise
> a new abstraction layer. For example in Lisp, using map, reduce (fold),
> or any other high-order function is just like using for, or while in a
> normal imperative language.
If I hit a call to map or to reduce, I've hit the bottom: map and reduce
are defined by Lisp and not by the programmer.
> And also I would like to point out that "hitting the language
> sooner", means to know every function in the standard Python library
> (which is by far uncomprehensible, its huge) and most of the times you
> also need the documentation. And if we go this path, when debugging we
> could use a smart IDE, which should show as tool-tip-text for function
> names their documentation, and in this way all we have to do to
> understand a particular function is just to point it out.
Although huge, at least the standard Python library is *bounded* (even if
only within a given release). The more I use it, the better I know it,
and there's probably an 80/20 rule in there somewhere (if I concentrate
on programs in my particular area(s) of interest, there may even be a
lurking 90/10 rule). Maybe I don't know the Standard Patterns well
enough (I still speak OO as a second language), but even when I see
something as apparently clear as add_listener(f), I have to wonder about
the specific semantics of your particular add_listener function.
In every joke there's a grain of truth. And usenet is precisely for
that thing -- discussions. Even of stupid jokes. ;)
> If you want to talk about Python and problems you're running into, you
> should start a new thread.
I'm not at that level of proficiency with the language. I believe most
of my technical problems are connected with lack of knowledge or
experience, not the language's features. While conceptual problems
seem futile to discuss. There's another saying: "when in Rome do as
the Romans do"
Best regards,
Vsevolod
We're arguing to the old argument, who knows better, what the
programmer wants: language implementor or the programmer himself.
AFAIK, Python community is on former side, while Lisp one -- on the
later. As always, there's no right answer.
Best regards,
Vsevolod