Python Module: nift

Paul McGuire ptmcg at austin.rr.com
Sun Feb 8 15:57:39 EST 2009


On Feb 8, 12:42 pm, J <seaworthyjer... at gmail.com> wrote:
> What are your thoughts on this module I created?
>
Here are a few steps to illustrate some good basic Python idioms worth
learning:

Step 1: Replace many-branched if-elif with dict

While translating characters in a string is a special case that
usually warrants using str.translate, a more general-purpose technique
to learn is to define a dict that captures the various branching
options, and then use the selecting value as a key to access the
desired branch.
Here is a dict containing your branches:

LEET_LETTERS = {
    "e" : "3",
    "E" : "3",
    "a" : "4",
    "A" : "4",
    "i" : "1",
    "I" : "1",
    "t" : "7",
    "T" : "7",
    "s" : "5",
    "S" : "5",
    "o" : "0",
    "O" : "0",
    "b" : "8",
    "B" : "8",
    }

Now you can implement leet() using access to the LEET_LETTERS
mapping.  Here are 3 different versions, that illustrate the usual
options when accessing entries from a map:
1. Look before you leet, uh, I mean "leap" (check for key existence
before using)
2. Better to ask forgiveness than permission (just use the key and
handle the exception that gets raised if the key is not found - an
homage to Grace Hopper)
3. Slightly different strategy, using dict.get() method instead of key/
indexing into the dict

def leet1(string):
    outlist = []
    for letter in string:
        if letter in LEET_LETTERS:
            letter = LEET_LETTERS[letter]
        outlist.append(letter)
    return "".join(outlist)

def leet2(string):
    outlist = []
    for letter in string:
        try:
            letter = LEET_LETTERS[letter]
        except KeyError:
            pass
        outlist.append(letter)
    return "".join(outlist)

def leet3(string):
    outlist = []
    for letter in string:
        outlist.append( LEET_LETTERS.get(letter,letter) )
    return "".join(outlist)

You can test each in turn without changing your test code, just assign
leet to each of these variants in turn:

leet = leet1
print leet("Hello You Beautiful Sweet World!")

I'm partial to option number 3, so let me show you a few other helpful
tips.

By the way, you can do this if you are doing any kind if if-elif, such
as:

if a=="0":
    fn_1()
elif a=="1":
    fn_100()
elif a=="7":
    fn_greater_than_five()
...

Instead you would write:

# note: values are the methods only, no ()'s
fns = { "0" : fn_1, "1" : fn_100, "7" : fn_greater_than_five }
if a in fns:
    # get desired function from fns map, and call it
    fns[a]()


Step 2: dict construction instead of {} syntax

LEET_LETTERS is so space-consuming, sometimes it is easier to use the
dict constructor with a list expression of list comprehension.  That
is, instead of the built-in syntax P{}'s to define a dict, you can use
the dict constructor and pass a list of (key,value) tuples.

LEET_LETTERS = dict( [("E","3"), ("e","3"), ("A","4"),
("a","4"), ...] )

Well, my fingers are tired and I'm bored already, how about if we
could just list the two strings of key and value characters, and some
how build the tuples by pulling an item from each list one at a time.
Fortunately, that is what Python's zip() function does:

LEET_LETTERS = dict( zip("eEaAiItTsSoObB",
                         "33441177550088") )

(I've listed the key-value pairs one above the other to illustrate how
the mapping will work, but of course, that isn't required.)

Now if I want to add another leet-speak letter (like "6" for "G"), I
just add the two characters on the end of the two strings.


Step 3: Use list comprehension instead of explicit list.appends using
for loop

As was already mentioned in another post, ''.join
(list_of_characters_to_join_together) is preferred to final +=
next_letter_to_add_to_final.  But there is also a nice short and clean
way to build up the list besides explicitly calling append in a loop.
Instead, use a list comprehension.  To do this, you convert:

    outlist = []
    for letter in string:
        outlist.append( LEET_LETTERS.get(letter,letter) )

to:
    outlist = [ LEET_LETTERS.get(letter,letter) for letter in string ]


In fact, Python is so smart, you can skip the list entirely, and just
pass that expression inside the brackets (called a generator
expression) directly to ''.join:

    final = ''.join( LEET_LETTERS.get(letter,letter) for letter in
string )

Just a "list" is not desired variable name since it masks the built-in
list type, I avoid "string" as a name since there is a commonly-used
string module.  How about just plain "s"?

Here is the final version of leet - we even got rid of the variable
final, and just return the value that would have been assigned to it:

LEET_LETTERS = dict( zip("eEaAiItTsSoObB", "33441177550088") )
def leet(s):
    return ''.join( LEET_LETTERS.get(c,c) for c in s )

Soon you'll be writing Python just like all the cool kids!

-- Paul



More information about the Python-list mailing list