Exercises Review

Css Generator

def css_generator(selector, **kwargs):
  """
  Returns a formatted CSS block for the given selector. 
  Accepts any number of attribute-value pairs as keyword args.

  """
  return ("%s {\n%s\n}" % 
          (selector,
           "\n".join(["\t%s: %s;" % (k,v)
                      for k,v in kwargs.items()])))

if __name__ == '__main__':
    print css_generator('.wildtype', color="#fff")

Save this as, say, css.py

Automatic tests

We can run the Python file to execute our test:

$ python css.py
.wildtype {
	color: #fff;
}

Using it from the shell

And we can also import the function into the Python shell for manual tests:

>>> from css import css_generator
>>> wildtype_block = css_generator('.wildtype', color="#fff")
>>> wildtype_block
'.wildtype {\n\tcolor: #fff\n}'

>>> print wildtype_block
.wildtype {
	color: #fff;
}

Looks great! But there's a pretty serious flaw that a couple of you discovered.

Hyphen Nation

Many CSS attributes (font-family, text-size, etc.) have hyphens in them. But the hyphen is not a legal character in Python identifiers (variable names, function names, argument names); it would be ambiguous with its use for subtraction/negation:

>>> print css_generator('.wildtype', font-weight="bold")
  File "", line 1
SyntaxError: keyword can't be an expression

Python thinks we're trying to subtract font - weight, which would be an expression, not a keyword argument name.

How could we solve this?

Learning how to fish

We know that Python identifiers can use underscores; but CSS attributes don't. We'll use underscores in our keyword arguments, and replace them with hyphens in our function.

So we need a way to replace one character with another in a string. Let's see if a string object has any methods that might help:

The dir() function

>>> s = 'some string'

>>> dir(s)
['__add__', '__class__', '__contains__', '__delattr__', 
'__doc__', '__eq__', '__ge__', '__getattribute__', '__getitem__', 
'__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', 
'__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', 
'__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', 
'__rmul__', '__setattr__', '__str__', 'capitalize', 'center', 
'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 
'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 
'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 
'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 
'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 
'swapcase', 'title', 'translate', 'upper', 'zfill']

The help() function

It has a .replace() method! But how does it work?

>>> help(s.replace)

Help on built-in function replace:

replace(...)
    S.replace (old, new[, count]) -> string

    Return a copy of string S with all occurrences of substring
    old replaced by new.  If the optional argument count is
    given, only the first count occurrences are replaced.

Try this with one of your own functions: import it and call help() on it.

Using s.replace() in our function

def css_generator(selector, **kwargs):
  """
  Returns a formatted CSS block for the given selector. 
  Accepts any number of attribute-value pairs as keyword args.
  Use underscores in keyword arg names to represent hyphens 
  in CSS attributes.

  """
  return ("%s {\n%s\n}" % 
          (selector,
           "\n".join(["\t%s: %s;" % (k.replace('_', '-'),v)
                      for k,v in kwargs.items()])))

if __name__ == '__main__':
    print css_generator('.wildtype', color="#fff")

Trying it out

>>> print css_generator('.wildtype', font_weight="bold")
.wildtype {
	font-weight: bold;
}

The map function

def map(func, alist):
    """
    Apply the given function to each element of alist, 
    and return the resulting list.

    """
    return [func(elem) for elem in alist]

if __name__ == '__main__':
    def double(x):
        return x*2
    print map(double, [2, 4, 3])

Playing with first-class functions

Brief excursus on functions, scope, references and imports...