Clever hack or code abomination?

R

Roy Smith

I need to try a bunch of names in sequence until I find one that works
(definition of "works" is unimportant). The algorithm is:

1) Given a base name, "foo", first see if just plain "foo" works.

2) If not, try "foo-1", "foo-2", and so on

3) If you reach "foo-20", give up.

What would you say if you saw this:

for suffix in [''] + [str(i) for i in xrange(-1, -20, -1)]:

It generates the right sequence of strings. But, if you came upon that
code, would it make sense to you, or would you spend half the afternoon
trying to figure out what it did and the other half of the afternoon
ranting about the deranged lunatic who wrote it?
 
S

Steven D'Aprano

I need to try a bunch of names in sequence until I find one that works
(definition of "works" is unimportant). The algorithm is:

1) Given a base name, "foo", first see if just plain "foo" works.

2) If not, try "foo-1", "foo-2", and so on

3) If you reach "foo-20", give up.

What would you say if you saw this:

for suffix in [''] + [str(i) for i in xrange(-1, -20, -1)]:

It generates the right sequence of strings. But, if you came upon that
code, would it make sense to you, or would you spend half the afternoon
trying to figure out what it did and the other half of the afternoon
ranting about the deranged lunatic who wrote it?

Nah, it's fine. Not exactly the clearest piece of code in the world, but
hardly worth a rant.

I'd be more likely to write that as:

for suffix in [''] + ["-%d" % i for i in range(1, 21)]:

or if I needed to do it more than once, as a generator:

def suffixes(max=20):
yield ""
for i in range(1, max+1):
yield "-%d" % i
 
M

Matt Joiner

def possible_names():
yield "foo"
for i in range(20):
yield "foo-" + str(i)

ಠ_ಠ
 
C

Chris Angelico

for suffix in [''] + [str(i) for i in xrange(-1, -20, -1)]:

It generates the right sequence of strings.  But, if you came upon that
code, would it make sense to you, or would you spend half the afternoon
trying to figure out what it did and the other half of the afternoon
ranting about the deranged lunatic who wrote it?

That's a self-contained piece of code.If I came upon it, I'd probably
copy and paste it to IDLE, see what it comes up with, and proceed from
there. Deranged you may be, but so long as code can be dropped into an
interactive interpreter, it's fine.

ChrisA
 
A

Arnaud Delobelle

I need to try a bunch of names in sequence until I find one that works
(definition of "works" is unimportant).  The algorithm is:

1) Given a base name, "foo", first see if just plain "foo" works.

2) If not, try "foo-1", "foo-2", and so on

3) If you reach "foo-20", give up.

What would you say if you saw this:

for suffix in [''] + [str(i) for i in xrange(-1, -20, -1)]:

It's a little obfuscated ;) I would go for the simple:

for i in xrange(21):
suffix = "-%s" % i if i else ""
....
 
V

Vito 'ZeD' De Tullio

Arnaud said:
I need to try a bunch of names in sequence until I find one that works
(definition of "works" is unimportant). The algorithm is:

1) Given a base name, "foo", first see if just plain "foo" works.

2) If not, try "foo-1", "foo-2", and so on

3) If you reach "foo-20", give up.

What would you say if you saw this:

for suffix in [''] + [str(i) for i in xrange(-1, -20, -1)]:

It's a little obfuscated ;) I would go for the simple:

for i in xrange(21):
suffix = "-%s" % i if i else ""
....

obfuscated for obfuscated, you can merge the two ideas:

for suffix in ('-%s' % i if i else '' for i in xrange(21)):
...
 
M

Martin P. Hellwig

On 01/12/2011 03:15, Roy Smith wrote:
<cut>
Well, I have seen much worse, so the WTFs/minute(*) count won't be too bad.

However, as general rule for readability; If you think you have to ask,
don't bother asking, spend that time rethinking and write a more
readable solution.


*) http://www.osnews.com/story/19266/WTFs_m
 
S

Steven D'Aprano

On 01/12/2011 03:15, Roy Smith wrote: <cut>
Well, I have seen much worse, so the WTFs/minute(*) count won't be too
bad.

However, as general rule for readability; If you think you have to ask,
don't bother asking, spend that time rethinking and write a more
readable solution.

That's generally good advice, but in this case, I don't know why Roy
Smith thought he had to ask. His initial snippet was pretty much standard
vanilla Python: a for loop over a list. The list was made by adding a
list containing a single element '', and a second list made from a list
comprehension that converted a bunch of numbers into strings. It uses no
advanced features like generators. A newbie to Python could work it out.

In my opinion, if anyone thinks that's "obfuscated" (as opposed to merely
"not quite idiomatic"), either their standards for readability are
impossibly high, or they just can't read Python code.

Try this on for size.


def obfuscated_prefixes(q=20):
c = str()
while isinstance(type(c), type(type)):
yield None or c
while isinstance(c, object):
try:
f = type(q)(c[c.index(chr(45))+1:])+type(q)(1)
except ValueError:
c = str.join('\n', list(map(chr, (45, 48))) + [c])[::2]
else:
break
if f <= q: pass
else: break
try:
c = (lambda a,b: a+b)(c[:c.index(chr(45))+1], type(c)(f))
except IndexError:
c = c[1::]
 
C

Chris Angelico

Try this on for size.


               f = type(q)(c[c.index(chr(45))+1:])+type(q)(1)
               c = str.join('\n', list(map(chr, (45, 48))) + [c])[::2]
           c = (lambda a,b: a+b)(c[:c.index(chr(45))+1], type(c)(f))

I would consider integer representations of ASCII to be code smell.
It's not instantly obvious that 45 means '-', even if you happen to
know the ASCII table by heart (which most people won't). This is one
thing that I like about C's quote handling; double quotes for a
string, or single quotes for an integer with that character's value.
It's clearer than the Python (and other) requirement to have an actual
function call:

for (int i=0;i<10;++i) {
digit='0'+i;
letter='A'+i;
}

versus

for i in range(10):
digit=chr(ord('0')+i)
letter=chr(ord('A')+i)

Ignoring the fact that you'd probably use a list comp in Python, this
is imho a win for C.

ChrisA
 
T

Terry Reedy

def possible_names():
yield "foo"
for i in range(20):
yield "foo-" + str(i)

This is my favorite -- crystal clear with only the variable part
variable. And it works in both 2.x and 3.x.
 
S

Steven D'Aprano

Try this on for size.


               f = type(q)(c[c.index(chr(45))+1:])+type(q)(1) c
               = str.join('\n', list(map(chr, (45, 48))) +
               [c])[::2]
           c = (lambda a,b: a+b)(c[:c.index(chr(45))+1],
           type(c)(f))

I would consider integer representations of ASCII to be code smell. It's
not instantly obvious that 45 means '-', even if you happen to know the
ASCII table by heart (which most people won't). This is one thing that I
like about C's quote handling; double quotes for a string, or single
quotes for an integer with that character's value. It's clearer than the
Python (and other) requirement to have an actual function call:

for (int i=0;i<10;++i) {
digit='0'+i;
letter='A'+i;
}


I would disagree that this is clear at all. You're adding what looks like
a character, but is actually an integer, with an integer. And then just
to add insult to injury, you're storing integers into arrays that are
named as if they were characters. In what mad universe would you describe
65 as a letter?

To say nothing of the fact that C's trick only works (for some definition
of works) for ASCII. Take for example one of the many EBCDIC encodings,
cp500. If you expect 'I' + 1 to equal 'J', you will be sorely
disappointed:

py> u'I'.encode('cp500')
'\xc9'
py> u'J'.encode('cp500')
'\xd1'

Characters are not integers, and C conflates them, to the disservice of
all. If fewer people learned C, fewer people would have such trouble
understanding Unicode.

Anyone unfamiliar with C's model would have trouble guessing what 'A' + 1
should mean. Should it be?

- an error
- 'B'
- 'A1'
- the numeric value of variable A plus 1
- 66 (assuming ascii encoding)
- 194 (assuming cp500 encoding)
- some other number
- something else?

How about 1000 + 'A'?
versus

for i in range(10):
digit=chr(ord('0')+i)
letter=chr(ord('A')+i)


It's a tad more verbose, but it's explicit about what is being done. Take
the character '0', find out what ordinal value it encodes to, add 1 to
that value, re-encode back to a character. That's exactly what C does,
only it does it explicitly.

Note that this still doesn't work the way we might like in EBCDIC, but
the very fact that you are forced to think about explicit conversion
steps means you are less likely to make unwarranted assumptions about
what characters convert to.

Better than both, I would say, would be for string objects to have
successor and predecessor methods, that skip ahead (or back) the
specified number of code points (defaulting to 1):

'A'.succ() => 'B'
'A'.succ(5) => 'F'

with appropriate exceptions if you try to go below 0 or above the largest
code point.
 
S

Steven D'Aprano

Note, I'm not saying that C's way is perfect; merely that using the
integer 45 to represent a hyphen is worse.

Dude, it was deliberately obfuscated code. I even named the function
"obfuscated_prefixes". I thought that would have been a hint <wink>

It's kinda scary that of all the sins against readability committed in my
function, including isinstance(type(c), type(type)) which I was
particularly proud of, the only criticism you came up with was that
chr(45) is hard to read. I'm impressed <grins like a mad thing>

[...]
I don't know about that. Anyone brought up on ASCII and moving to EBCDIC
will likely have trouble with this, no matter how many function calls it
takes.

Of course you will, because EBCDIC is a pile of festering garbage :)

But IMAO you're less likely to have trouble with with Unicode if you
haven't been trained to treat characters as synonymous with integers.

And besides, given how rare such byte-manipulations on ASCII characters
are in Python, it would be a shame to lose the ability to use '' and ""
for strings just to avoid calling ord and chr functions.

... and this still has that same issue. Arithmetic on codepoints depends
on that.

We shouldn't be doing arithmetic on code points. Or at least we shouldn't
unless we are writing a Unicode library that *needs* to care about the
implementation. We should only care about the interface, that the
character after 'A' is 'B'. Implementation-wise, we shouldn't care
whether A and B are represented in memory by 0x0041 and 0x0042, or by
0x14AF and 0x9B30. All we really need to know is that B comes immediately
after A. Everything else is implementation.

But I fear that the idea of working with chr and ord is far to ingrained
now to get rid of it.
 
C

Chris Angelico

Dude, it was deliberately obfuscated code. I even named the function
"obfuscated_prefixes". I thought that would have been a hint <wink>

It's kinda scary that of all the sins against readability committed in my
function, including isinstance(type(c), type(type)) which I was
particularly proud of, the only criticism you came up with was that
chr(45) is hard to read. I'm impressed <grins like a mad thing>

Heh! I know it was obfuscated, and that's why I didn't bother
mentioning the obvious.
And besides, given how rare such byte-manipulations on ASCII characters
are in Python, it would be a shame to lose the ability to use '' and ""
for strings just to avoid calling ord and chr functions.

Agreed. That flexibility is a _huge_ advantage. Definitely not worth
changing anything.

ChrisA
 
J

John Ladasky

That's a self-contained piece of code.If I came upon it, I'd probably
copy and paste it to IDLE, see what it comes up with, and proceed from
there.

+1. That was going to be my comment exactly.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,013
Latest member
KatriceSwa

Latest Threads

Top