A Few of My Favorite (Python) Things

Preview:

DESCRIPTION

Python's "batteries included" philosophy means that it comes with an astonishing amount of great stuff. On top of that, there's a vibrant world of third-party libraries that help make Python even more wonderful. We'll go on a breezy, example-filled tour through some of my favorites, from treasures in the standard library to great third-party packages that I don't think I could live without, and we'll touch on some of the fuzzier aspects of the Python culture that make it such a joy to be part of.

Citation preview

A Few of My Favorite Things

Mike Pirnat • AG Interactive • CodeMash 2012

A Few of My Favorite Things�������

Mike Pirnat • AG Interactive • CodeMash 2012

Disclaimers

The Language

_ = ( 255, lambda V ,B,c :c and Y(V*V+B,B, c -1)if(abs(V)<6)else ( 2+c-4*abs(V)**-0.4)/i ) ;v, x=1500,1000;C=range(v*x );import struct;P=struct.pack;M,\ j ='<QIIHHHH',open('M.bmp','wb').writefor X in j('BM'+P(M,v*x*3+26,26,12,v,x,1,24))or C: i ,Y=_;j(P('BBB',*(lambda T:(T*80+T**9 *i-950*T **99,T*70-880*T**18+701* T **9 ,T*i**(1-T**45*2)))(sum( [ Y(0,(A%3/3.+X%v+(X/v+ A/3/3.-x/2)/1j)*2.5 /x -2.7,i)**2 for \ A in C [:9]]) /9) ) )

http://preshing.com/20110926/high-resolution-mandelbrot-in-obfuscated-python

The Interactive Shell

$ pythonPython 2.7.1 (r271:86832, Jun 16 2011, 16:59:05) [GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)] on darwinType "help", "copyright", "credits" or "license" for more information.>>>

class Frobulator(object): """It frobulates things."""

def __init__(self, doohickey): """A Frobulator needs a doohickey.""" self.doohickey = doohickey

def frobulate(self): """Frobulate ALL the things!""" print "Frobulating..." doodad = self.doohickey() return FrobulatedThing(doodad)

class FrobulatedThing(object): """A thing which has been frobulated."""

def __init__(self, thing): """Make a thing into a frobulated thing.""" self.thing = thing

Docstrings...

>>> help(Frobulator)

Help on class Frobulator in module frobulator:

class Frobulator(__builtin__.object) | It frobulates things. | | Methods defined here: | | __init__(self, doohickey) | A Frobulator needs a doohickey. | | frobulate(self) | Frobulate ALL the things! | ...

...and Help

Comprehensions

• List comprehensions

• Set comprehensions

• Dictionary comprehensions

• Generator expressions

List Comprehensions

x = [item for item in series]

x = [do_something(item) for item in series if expression]

things = [Thingy.from_data(x) for x in database_results]

partiers = [x for x in codemashers if x.slides_done()]

List Comprehensions

booze = ['beer', 'wine', 'scotch', 'gin']soft_drinks = ['water', 'soda', 'juice']

a = [(x, y) for x in booze for y in soft_drinks]

[('beer', 'water'), ('beer', 'soda'), ('beer', 'juice'), ('wine', 'water'), ('wine', 'soda'), ('wine', 'juice'), ('scotch', 'water'), ('scotch', 'soda'), ('scotch', 'juice'), ('gin', 'water'), ('gin', 'soda'), ('gin', 'juice')]

List Comprehensions

b = [x for x in zip(booze, soft_drinks)]

[('beer', 'water'), ('wine', 'soda'), ('scotch', 'juice')]

Set Comprehensions

s = {v for v in 'CODEMASH ROCKS' if v not in 'ABCD'}

set([' ', 'E', 'H', 'K', 'M', 'O', 'S', 'R'])

Dictionary Comprehensions

d = {key: value for key, value in list_of_tuples}

d = { 'Mike': 'Python', 'Jim': 'Ruby', 'Brad': 'UX', ... }

d1 = {val: key for key, val in d.items()}

{'Python': 'Mike', 'Ruby': 'Jim', 'UX': 'Brad', ...}

Generators

def f(how_many): for number in range(how_many): if number**2 > 3: yield number * 2

for number in f(5): print number

468

Generator Expressions

gen = (2*x for x in range(5) if x**2 > 3)

for number in gen: print number

468

Properties

class Foo(object):

def __init__(self, bar=42): self.set_bar(bar)

def get_bar(self): return self.bar

def set_bar(self, bar): self.bar = int(bar)

Properties

class Foo(object):

def __init__(self, bar=42): self.bar = bar

@property def bar(self): return self._bar

@bar.setter def bar(self, x): self._bar = int(x)

Properties

class Foo(object):

def __init__(self, bar=42): self.bar = bar

@property def bar(self): return self._bar

@bar.setter def bar(self, x): self._bar = int(x)

Properties

>>> foo = Foo()>>> foo.bar42>>> foo.bar = 'abc'Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 9, in barValueError: invalid literal for int() with base 10: 'abc'

Decorators

• That funny-looking @ thing

• Similar to Java’s annotations

• Replaces a function with a wrapper

• Augment functionality in a reusable way

Decorators

def be_excellent(wrapped): def wrapper(*args, **kwargs): print "Be excellent to each other..." return wrapped(*args, **kwargs) return wrapper

@be_excellentdef party_on(who): print "...and party on, {0}!".format(who)

>>> party_on('dudes')Be excellent to each other......and party on, dudes!

Context Managers

• That funny “with” thing

• Simplify calling code that needs setup and teardown

• Alternative to try/finally structures

• Acquire locks, open and close files, do database transactions, etc.

frobulator = Frobulator(...)

try: frobulator.frobulate()finally: frobulator.cleanup()

Before Context Managers

with Frobulator(...) as frobulator: frobulator.frobulate()

After Context Managers

class Frobulator(object):

def __init__(self, ...): ...

def __enter__(self): print "Preparing to frobulate..." return self

def __exit__(self, exc_type, exc_value, exc_tb): print "Frobulation complete!"

def frobulate(self): print "Frobulating!" ...

with Frobulator() as frobulator: frobulator.frobulate()

Preparing to frobulate...Frobulating!Frobulation complete!

import contextlib

@contextlib.contextmanagerdef make_frobulator(): print "Preparing to frobulate..." yield Frobulator() print "Frobulation complete!"

with make_frobulator() as frobulator: frobulator.frobulate()

Preparing to frobulate...Frobulating!Frobulation complete!

The Standard Library

Itertools

• Tools for iterating on sequences

• Inspired by functional languages

• Fast and memory efficient

• Combine to express more complicated algorithms

Itertools: chain

from itertools import chain

for x in chain([1, 2, 3], [42, 1138, 2112]): print x,

1 2 3 42 1138 2112

Itertools: izip

from itertools import izip

for x in izip([1, 2, 3], [42, 1138, 2112]): print x,

(1, 42) (2, 1138) (3, 2112)

Itertools: islicefrom itertools import islice, count

for x in islice(count(), 5): print x,

for x in islice(count(), 5, 10): print x,

for x in islice(count(), 0, 100, 10): print x,

0 1 2 3 45 6 7 8 90 10 20 30 40 50 60 70 80 90

Itertools: islicefrom itertools import islice, count

for x in islice(count(), 5): print x,

for x in islice(count(), 5, 10): print x,

for x in islice(count(), 0, 100, 10): print x,

0 1 2 3 45 6 7 8 90 10 20 30 40 50 60 70 80 90

Itertools: imap

from itertools import imap

for thingy in imap(Thingy.from_data, database_results): ...

Itertools: cyclefrom itertools import cycle

for x in cycle(['wake up', 'meet Ned', 'romance Rita']): print x

wake upmeet Nedromance Ritawake upmeet Nedromance Ritawake upmeet Ned...

Itertools: repeat

from itertools import repeat

for x in repeat("stop hitting yourself", 5): print x

stop hitting yourselfstop hitting yourselfstop hitting yourselfstop hitting yourselfstop hitting yourself

Functools

• Tools for manipulating functions

• Partial

• Wraps a callable with default arguments

• Alternative to lambdas and closures

Functools: Partial

from functools import partial

def f(a, b=2): print a, b

f1 = partial(f, 'fixed_a')f2 = partial(f, b='fixed_b')

>>> f1(b=1138)fixed_a 1138

>>> f2(1138)1138 fixed_b

Functools: Partial

def category_is(category, item): categories = [x.lower() for x in item.categories] if category.lower() in categories: return True return False

is_python = partial(category_is, 'python')is_cat_pictures = partial(category_is, 'catpix')

...

python_posts = [item for item in blog_posts if is_python(item)]

cat_posts = [item for item in blog_posts if is_cat_pictures(item)]

Collections

• Beyond the basic list, dict, tuple, and set...

• Counter

• Defaultdict

• OrderedDict

• Namedtuple

• ...and more

counter = {}for char in "Hello there, CodeMash!": if char not in counter: counter[char] = 1 else: counter[char] += 1

print counter

{'a': 1, ' ': 2, 'C': 1, 'e': 4, 'd': 1, 'H': 1, 'M': 1, 'l': 2, 'o': 2, ',': 1, 's': 1, 'r': 1, '!': 1, 't': 1, 'h': 2}

Counter

counter = {}for char in "Hello there, CodeMash!": if char not in counter: counter[char] = 1 else: counter[char] += 1

print counter

{'a': 1, ' ': 2, 'C': 1, 'e': 4, 'd': 1, 'H': 1, 'M': 1, 'l': 2, 'o': 2, ',': 1, 's': 1, 'r': 1, '!': 1, 't': 1, 'h': 2}

Counter

Counter

from collections import Countercounter = Counter("Hello there, CodeMash!")print counter

Counter({'e': 4, ' ': 2, 'l': 2, 'o': 2, 'h': 2, 'a': 1, 'C': 1, 'd': 1, 'H': 1, 'M': 1, ',': 1, 's': 1, 'r': 1, '!': 1, 't': 1})

Counter

from collections import Countercounter = Counter("Hello there, CodeMash!")print counter

Counter({'e': 4, ' ': 2, 'l': 2, 'o': 2, 'h': 2, 'a': 1, 'C': 1, 'd': 1, 'H': 1, 'M': 1, ',': 1, 's': 1, 'r': 1, '!': 1, 't': 1})

Namedtupleimport math

def distance(a, b): return math.sqrt( (a[0] - b[0])**2 + (a[1] - b[1])**2 + (a[2] - b[2])**2 )

a = (1, 2, 3)b = (-1, -2, 42)print distance(a, b)

39.25557285278104

Namedtuplefrom collections import namedtuplePoint = namedtuple('Point', 'x y z')

def distance(a, b): return math.sqrt( (a.x - b.x)**2 + (a.y - b.y)**2 + (a.z - b.z)**2 )

a = Point(x=1, y=2, z=3)b = Point(-1, -2, 42)print distance(a, b)

39.25557285278104

s1 = """Lorem ipsum dolor sit amet, consectetur adipiscing elit.Phasellus dui nunc, faucibus id ullamcorper eget, tempusvitae nisl. Donec quis semper risus. Curabitur sit amettellus eget metus accumsan porta nec nec lorem. Ut vitaesem nisl. Praesent pulvinar feugiat nibh fringilla semper. Nullam cursus tempor lorem ut egestas. Nullamsuscipit gravida turpis ac porttitor. Curabitur eleifendaugue at risus commodo pretium. Aliquam eget magnarisus, ut lobortis metus. Cum sociis natoque penatibuset magnis dis parturient montes, nascetur ridiculus mus.Etiam non magna sit amet nulla porttitor molestie sitamet vel sem. Vestibulum sit amet nisl a velitadipiscing porta id non urna. Duis ullamcorper dictumipsum sit amet congue."""

Difflib

Difflibs2 = """Lorem ipsum dolor sit amet, consectetur adipiscing elit.Phasellus dui nunc, faucibus id ullamcorper eget, tempusvitae nisl. Donec quis semper risus. Curabitur sit amettellus eget metus accumsan porta nec nec lorem. Ut vitaesem nisl. Praesent pulvinar feugiat nibh fringilla semper. Nullam cursus tempor lorem ut egestas. Nullamsuscipit gravida turpis ac porttitor. Curabitur eleifendaugue at risus commodo pretium. Aliquam eget magnarisus, ut lobortis montes. Cum sociis natoque penatibuset magnis dis parturient metus, nascetur ridiculus mus.Etiam non magna sit amet nulla porttitor molestie sitamet vel sem. Vestibulum sit amet nisl a velitadipiscing porta id non urna. Duis ullamcorper dictumipsum sit amet congue."""

Difflib

import difflib

differ = difflib.Differ()diff = differ.compare(s1.splitlines(), s2.splitlines())print '\n'.join(diff)

Difflib Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus dui nunc, faucibus id ullamcorper eget, tempus vitae nisl. Donec quis semper risus. Curabitur sit amet tellus eget metus accumsan porta nec nec lorem. Ut vitae sem nisl. Praesent pulvinar feugiat nibh fringilla semper. Nullam cursus tempor lorem ut egestas. Nullam suscipit gravida turpis ac porttitor. Curabitur eleifend augue at risus commodo pretium. Aliquam eget magna- risus, ut lobortis metus. Cum sociis natoque penatibus? --

+ risus, ut lobortis montes. Cum sociis natoque penatibus? +++

- et magnis dis parturient montes, nascetur ridiculus mus.? ^^ ^

+ et magnis dis parturient metus, nascetur ridiculus mus.? ^ ^

Etiam non magna sit amet nulla porttitor molestie sit amet vel sem. Vestibulum sit amet nisl a velit adipiscing porta id non urna. Duis ullamcorper dictum ipsum sit amet congue.

Difflib

diff = difflib.unified_diff( s1.splitlines(), s2.splitlines(), lineterm='')print '\n'.join(diff)

Difflib

--- +++ @@ -7,8 +7,8 @@ semper. Nullam cursus tempor lorem ut egestas. Nullam suscipit gravida turpis ac porttitor. Curabitur eleifend augue at risus commodo pretium. Aliquam eget magna-risus, ut lobortis metus. Cum sociis natoque penatibus-et magnis dis parturient montes, nascetur ridiculus mus.+risus, ut lobortis montes. Cum sociis natoque penatibus+et magnis dis parturient metus, nascetur ridiculus mus. Etiam non magna sit amet nulla porttitor molestie sit amet vel sem. Vestibulum sit amet nisl a velit adipiscing porta id non urna. Duis ullamcorper dictum

Ast

• Use Python to process abstract syntax trees of Python grammar

• Helps introspect about what the current grammar looks like

• Helps write secure code

Ast

# Danger:foo = eval(bar)

# Safe:foo = ast.literal_eval(bar)

Multiprocessing

• Like threading, but with subprocesses

• Local and remote concurrency

• Spawn processes or entire pools

• Communicate via queues or pipes

• Shared state via shared memory or manager/server process

import osfrom multiprocessing import Process

def info(title): print title print 'parent process:', os.getppid() print 'process id:', os.getpid()

def f(name): info('function f') print 'hello', name

if __name__ == '__main__': info('main') p = Process(target=f, args=('world',)) p.start() p.join()

$ python process.py mainparent process: 18647process id: 31317----------function fparent process: 31317process id: 31318----------hello world

import osfrom multiprocessing import Pool

def g(x): info('function g('+str(x)+')') return x * x

if __name__ == '__main__': info('main') pool = Pool(2) print pool.map(g, range(100))

$ python process.py mainmodule name: __main__parent process: 18647process id: 31369----------function g(0)module name: __main__parent process: 31369process id: 31370----------function g(1)module name: __main__parent process: 31369process id: 31370----------function g(13)function g(2)module name: __main__parent process: 31369process id: 31370----------module name: __main__function g(3)module name: __main__

parent process: 31369parent process: 31369process id: 31370process id: 31371--------------------function g(4)module name: __main__parent process: 31369function g(14)process id: 31370module name: __main__----------parent process: 31369function g(5)process id: 31371module name: __main__

...

[0, 1, 4, 9, 16, 25, ..., 8836, 9025, 9216, 9409, 9604, 9801]

Third Party Packages

Third Party Packages

• Most available on PyPI--http://pypi.python.org

• pip install packagename

• easy_install packagename

Virtualenv

• Makes an isolated Python environment

• Don’t pollute your global site-packages

• Insulates Python projects from one another

• Don't have to be root

Virtualenv

$ virtualenv directory

$ virtualenv --python=/path/to/specific/python directory

$ cd directory$ . bin/activate

$ easy_install whatever$ pip install whatever

...do stuff...

$ deactivate

Datetime and Dateutil

• Python’s datetime provides date, time, datetime, and timedelta objects

• Dateutil provides powerful extensions

• Parsing

• Olson-driven timezones

• Recurrence

Parsing

>>> from dateutil.parser import parse

>>> parse('01/01/2012')datetime.datetime(2012, 1, 1, 0, 0)

>>> parse('2012-01-01')datetime.datetime(2012, 1, 1, 0, 0)

>>> from dateutil import zoneinfo

>>> zone = zoneinfo.gettz('US/Eastern')>>> zonetzfile('America/New_York')>>> zone2 = zoneinfo.gettz('US/Hawaii')

>>> dt = datetime.now(zone)>>> dtdatetime.datetime(2012, 1, 13, 13, 46, 54, 997825, tzinfo=tzfile('America/New_York'))

>>> dt.astimezone(zone2)datetime.datetime(2012, 1, 13, 8, 46, 54, 997825, tzinfo=tzfile('Pacific/Honolulu'))

Timezones

Recurrence

>>> from dateutil.rrule import rrule, YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY, BY_EASTER

>>> rr = rrule(YEARLY, dtstart=datetime(1948, 2, 24))

>>> rr.after(datetime(2012, 1, 1))datetime(2012, 2, 24, 0, 0)

>>> rr.between(datetime(2010, 1, 1), datetime(2012, 1, 1))[datetime(2010, 2, 24, 0, 0), datetime(2011, 2, 24, 0, 0)]

howoldismykid.comdef periods_between(freq, start_date, end_date):

    # dateutil.rrule falls down for monthly recurrences where the start # date's day is greater than the number of days in a subsequent month # in the range; ie, when start date is 10/31/2010, and end date is # 3/13/2011, rrule's between will only produce 2 instances, 12/31 and # 1/31, rather than 4.    if freq == MONTHLY and start_date.day > 28:        start_date = datetime.datetime(start_date.year, start_date.month,                28, start_date.hour, start_date.minute, start_date.second)

    # Same problem but for "Pirates of Penzance" leap day birthdays...    elif freq == YEARLY and is_leap_day(start_date):        start_date = datetime.datetime(start_date.year, start_date.month,                28, start_date.hour, start_date.minute, start_date.second)

    rr = rrule(freq, dtstart=start_date)    periods = len(rr.between(start_date, end_date))    return periods

howoldismykid.com>>> start_date = datetime(2007, 9, 10)>>> end_date = datetime(2012, 1, 13)

>>> periods_between(YEARLY, start_date, end_date)4

>>> periods_between(MONTHLY, start_date, end_date)52

>>> periods_between(WEEKLY, start_date, end_date)226

>>> periods_between(DAILY, start_date, end_date)1585

Nose

• Find, run, and report results of test suites

• Low-friction setup

• Extendable with plugins (more later)

• http://readthedocs.org/docs/nose/en/latest/

Nose

# test_foo.py

def test_a_thing(): assert False

def test_another_thing(): assert True

$ nosetests tests/test_foo.py F.======================================================================FAIL: test_foo.test_a_thing----------------------------------------------------------------------Traceback (most recent call last): File "/Users/mpirnat/Code/frobulator/lib/python2.7/site-packages/nose-1.1.2-py2.7.egg/nose/case.py", line 197, in runTest self.test(*self.arg) File "/Users/mpirnat/Code/frobulator/src/frobulator/tests/test_foo.py", line 2, in test_a_thing assert FalseAssertionError

----------------------------------------------------------------------Ran 2 tests in 0.001s

FAILED (failures=1)

Nose

Nose

class TestWhenFrobulating(object):

def setup(self): self.frobulator = Frobulator() self.frobulator.frobulate()

def test_that_it_frobulated(self): assert self.frobulator.has_frobulated

def teardown(self): self.frobulator.cleanup()

Nose

$ nosetests.--------------------------------------------------------Ran 1 test in 0.005s

OK

Nose: Useful Flags

• --processes=number

• -x

• --pdb

• --pdb-failures

• --failed

Nose Plugins

• Add custom behavior to your test run

• Get coverage statistics (coverage)

• Measure cleanliness with PEP-8 (tissue)

• Emit human-readable spec-style results (pinocchio)

• Or write your own...

class TestGeneratesLotsOfFailures(object):

def test_generates_failures(self):

def _make_a_test(i): # start with some wins if i < 7: assert True

# but then it hits the fan... elif i < 30: assert False

# then be a little random elif i % 3 == 0: assert False else: assert True

for i in range(50): yield _make_a_test, i

$ nosetests test_epic_fail.py

.......FFFFFFFFFFFFFFFFFFFFFFFF..F..F..F..F..F..F.[lots of test failure output; use your imagination...]--------------------------------------------------------Ran 50 tests in 0.010s

FAILED (errors=30)

import osfrom nose.plugins import Plugin

class F7U12(Plugin):

name = 'f7u12' enabled = True

def options(self, parser, env=os.environ): super(F7U12, self).options(parser, env=env)

def configure(self, options, config): super(F7U12, self).configure(options, config) self.config = config if not self.enabled: return

def setOutputStream(self, stream): self.stream = stream return self.stream

def begin(self): self.failure_count = 0 def handleFailure(self, test, err): self.failure_count += 1

if self.failure_count < 8: self.stream.write('F') else: self.stream.write('U') return True

$ nosetests --with-f7u12 test_epic_fail.py

.......FFFFFFFFUUUUUUUUUUUUUUUU..U..U..U..U..U..U.[lots of test failure output; use your imagination...]--------------------------------------------------------Ran 50 tests in 0.010s

FAILED (errors=30)

Mock

• Mock object framework

• Avoid writing custom stubs

• Uses “Action/Assert” model (not “Record/Replay”)

• Can also patch out module and class attributes in the scope of a test

• http://www.voidspace.org.uk/python/mock/

Mock

from mock import Mock

foo = Mock()foo.bar()foo.bar.baz(42)foo.bar.baz.return_value = "whatever"

assert foo.bar.calledfoo.bar.baz.assert_called_with(42)

Mock

class TestWhenFrobulating(object):

def setup(self): self.doohickey = Mock() self.frobulator = Frobulator(self.doohickey) self.frobulator.frobulate()

def test_it_uses_the_doohickey(self): assert self.doohickey.called

Mockfrom mock import patch

class TestWhenFrobulating(object):

@patch('frobulator.DingleHopper') def setup(self, dingle_hopper_class): self.dingle_hopper = Mock() dingle_hopper_class.return_value = \ self.dingle_hopper self.frobulator = Frobulator() self.frobulator.frobulate()

def test_it_uses_a_dingle_hopper(self): assert self.dingle_hopper.called

Coverage

• Measure coverage of your codebase during program execution

• Integrates with several test runners to measure test coverage

• http://nedbatchelder.com/code/coverage/

#!/usr/bin/env python

class Frobulator(object): """It frobulates things."""

def __init__(self, doohickey): """A Frobulator needs a doohickey.""" self.doohickey = doohickey

def frobulate(self): """Frobulate ALL the things!""" print "Frobulating..." doodad = self.doohickey() return FrobulatedThing(doodad)

class FrobulatedThing(object): """A thing which has been frobulated."""

def __init__(self, thing): """Make a thing into a frobulated thing.""" self.thing = thing

if __name__ == '__main__': x = FrobulatedThing(42)

#!/usr/bin/env python

class Frobulator(object): """It frobulates things."""

def __init__(self, doohickey): """A Frobulator needs a doohickey.""" self.doohickey = doohickey

def frobulate(self): """Frobulate ALL the things!""" print "Frobulating..." doodad = self.doohickey() return FrobulatedThing(doodad)

class FrobulatedThing(object): """A thing which has been frobulated."""

def __init__(self, thing): """Make a thing into a frobulated thing.""" self.thing = thing

if __name__ == '__main__': x = FrobulatedThing(42)

$ coverage run frobulator.py

$ coverage report -mName Stmts Miss Cover Missing------------------------------------------frobulator 12 4 67% 8, 12-14

from mock import Mock, patchfrom frobulator import Frobulator, FrobulatedThing

class TestWhenFrobulating(object):

def setup(self): self.doohickey = Mock() self.doohickey.return_value = 42

self.frobulator = Frobulator(self.doohickey) self.result = self.frobulator.frobulate()

def test_it_uses_a_doohickey(self): assert self.doohickey.called

def test_it_returns_a_frobulated_thing(self): assert isinstance(self.result, FrobulatedThing)

$ nosetests --with-coverage --cover-package frobulator..Name Stmts Miss Cover Missing------------------------------------------frobulator 12 1 92% 26------------------------------------------Ran 2 tests in 0.008s

OK

Useful Flags

• --cover-erase

• --cover-inclusive

• --cover-tests

Lettuce

• Like Cucumber

• BDD test framework

• Natural language augmented by code

• Big wins pairing Devs + QA + Biz

• http://lettuce.it/

Lettuce Vocabulary

• Features

• Scenarios

• Steps

• World

• Terrain

Feature: The website has a homepage In order to demo Lettuce As a presenter I want to test the homepage of a website

Scenario: Verify the site is up Given I access the url "http://localhost:8000/" Then I get a status "200"

from lettuce import step, worldfrom nose.tools import assert_equalsimport requests

@step(r'I access the url "(.*)"')def access_url(step, url): world.response = requests.get(url)

@step(r'I get a status "(.*)"')def get_status(step, expected_status): expected_status = int(expected_status) assert_equals(world.response.status_code, expected_status)

Snake-Guice

• Framework for Dependency Injection

• Reduce coupling!

• Improve testability!

• http://code.google.com/p/snake-guice/

Snake-Guice

• Bind identifiers to things you want to inject

• Decorate code with injection hints

• Use injector to get instance with all dependencies resolved and injected

• Construct with mocks in tests, minimize the need for patching

Define the Identifier

class IFrobulator(object): pass

Decorate Your Code

from snakeguice import inject

class WankelRotator(object):

@inject(frobulator=IFrobulator) def __init__(self, frobulator): self.frobulator = frobulator

Decorate Your Code

from snakeguice import inject

class WankelRotator(object):

@inject(frobulator=IFrobulator) def __init__(self, frobulator): self.frobulator = frobulator

Configure Bindings

from frobulator import Frobulator

class BindingModule(object):

def configure(self, binder): binder.bind(IFrobulator, to=Frobulator)

Get an Instance

from snakeguice import Injectorimport WankelRotator

injector = Injector(BindingModule())rotator = injector.get_instance(WankelRotator)

Requests

• A human-friendly alternative to urllib2

• Maturing rapidly

• http://python-requests.org/

Using Requests

import requests

# Authentication!r = requests.get('https://api.github.com', auth=('user', 'pass'))

print r.status_codeprint r.headers['content-type']

Using Requests

# Posting name-value pair form datapost_data = {'foo': 'bar', 'baz': 'quux', ...}r = requests.post(url, data=post_data)

# Posting a glob of JSONr = requests.post(url, data=json.dumps(post_data))

# Multipart file attachmentfiles = {'my_file.xls': open('my_file.xls', 'rb')}r = requests.post(url, files=files)

Using Requests

# Custom headers!headers = {'X-foo': 'bar'}r = requests.get(url, headers=headers)

# Cookies!cookies = {'a_cookie': 'is delicious'}r = requests.get(url, cookies=cookies)

Pylons

• http://docs.pylonsproject.org/en/latest/docs/pylons.html

• MVCish web framework

• Glues together several component projects

• Can use your preferred components

• Somewhat dead (evolved into Pyramid)

Pylons

• Request/response: webob

• URL dispatch: routes

• Input validation: formencode

• Persistence/ORM: sqlalchemy

• Session: beaker

• Templates: mako

from routes import Mapper

def make_map(config): """Create, configure and return the routes Mapper""" map = Mapper(directory=config['pylons.paths']['controllers'], always_scan=config['debug']) ...

map.connect('/', controller='main', action='index') map.connect('/contact', controller='contact', action='index', conditions={'method':'GET'}) map.connect('/contact', controller='contact', action='send', conditions={'method':'POST'})

return map

from howoldismykid.lib.base import BaseController, \ render

class MainController(BaseController):

def index(self): # Return a rendered template return render('/main.mako')

from formencode import Schema, validators

class ContactForm(Schema): email = validators.Email(not_empty=True) subject = validators.String(not_empty=True) message = validators.String(not_empty=True)

from pylons.decorators import validatefrom howoldismykid.lib.base import BaseController, renderfrom howoldismykid.model.forms import ContactForm

class ContactController(BaseController):

def __init__(self, *args, **kwargs): BaseController.__init__(self, *args, **kwargs) self.emailer = Emailer(SMTP, USER, PW, FAKE_EMAIL)

def index(self): # Return a rendered template return render('/contact.mako')

@validate(schema=ContactForm(), form="index", prefix_error=False) def send(self): validated = self.form_result self.emailer.send_mail(validated['email'], EMAIL, validated['subject'], validated['message']) return render('/contact_done.mako')

<!DOCTYPE HTML><html> <head> ${self.head_tags()} ... </head> <body> <div id="header">...</div> <div id="content"> ${self.body()} </div> <div id="footer">...</div>

<%include file="google_analytics.mako"/> ${self.foot_tags()}

</body></html>

<%inherit file="/base.mako" />

<%def name="head_tags()"><title>How Old Is My Kid?</title></%def>

<h2>Contact Us</h2>

<p>Suggest a new feature, let us know how we're doing, or just say hi.</p>

<form method="POST" action="/contact" id="contact-form"> <p><label for="email">Email:</label> <input type="text" name="email" id="email" /></p> <p><label for="subject">Subject:</label> <input type="text" name="subject" id="subject" /></p> <p><label for="message">Message:</label><br /><textarea name="message" id="message"></textarea></p> <p><input type="submit" value="Submit" /></p></form>

<%def name="foot_tags()"><script type="text/javascript">$(document).ready(function() { $("button, input:submit").button();});</script></%def>

Django

• https://www.djangoproject.com/

• Full stack/harder to replace components

• Lots of reusable apps

• Admin interface

• Lots of deployment options (Google)

• Not dead

from django.conf.urls.defaults import patterns, url

urlpatterns = patterns('', url(r'^/$', 'howoldismykid.views.main', name='main'), url(r'^/contact/$', 'howoldismykid.views.contact', name='contact'),)

from django.shortcuts import render

def main(request): return render(request, 'main.html')

from django import forms

class ContactForm(forms.Form):

email = forms.EmailField(label='Email Address') subject = forms.CharField(label='Subject') message = forms.CharField(label='Message', widget=forms.Textarea)

from contact.forms import ContactFormfrom django.shortcuts import render

def contact(request): if request.method == 'POST': form = ContactForm(request.POST) if form.is_valid(): emailer = \ Emailer(SMTP, USER, PW, FAKE_EMAIL) emailer.send_mail( form.cleaned_data['email'], EMAIL, form.cleaned_data['subject'], form.cleaned_data['message']) return render(request, 'contact_done.html') else: form = ContactForm()

return render(request, 'contact.html', {'form': form })

<!DOCTYPE HTML><html> <head> {% block head_tags %}...{% endblock %} ... </head> <body> <div id="header">...</header> <div id="content"> {% block content %}It goes here.{% endblock %} </div> <div id="footer">...</div>

{% include "google_analytics.html" %} {% block foot_tags %}{% endblock %}

</body></html>

{% extends 'base.html' %}

{% block head_tags %}<title>How Old Is My Kid?</title>{% endblock %}

{% block content %}<h2>Contact Us</h2>

<p>Suggest a new feature, let us know how we're doing, or just say hi.</p>

<form method="POST" action="/contact" id="contact-form"> {% csrf_token %} {{ form.as_p }} <p><input type="submit" value="Submit" /></p></form>{% endblock %}

{% block foot_tags %}<script type="text/javascript">$(document).ready(function() { $("button, input:submit").button();});</script>{% endblock %}

The Culture

Planet Python

• http://planet.python.org/

• Aggregate feed of Python blogs

• Great way to follow what's going on

• Minimize newsgroup/mailing list burdens

• Easy to be included

The Python Ecosystem

• http://mirnazim.org/writings/python-ecosystem-introduction/

• Great introduction to the Python ecosystem

• Everything you need to get up and running

The Hitchhiker’s Guide

• http://docs.python-guide.org/

• Opinionated advice about using Python

• Basics

• Scenario-specific details and recommendations

PyMotW

• Explored a different standard library module every week

• Examples, examples, examples

• http://www.doughellmann.com/PyMOTW/

• The Python Standard Library by Example

http://www.doughellmann.com/books/byexample/

Podcasts

• http://www.radiofreepython.com/

• http://frompythonimportpodcast.com/

• http://djangodose.com/

• http://advocacy.python.org/podcasts/

• http://www.awaretek.com/python/

• Start your own! Be the change you want...

PyOhio

• Free as in $0.00

• Columbus, Ohio

• Last weekend in July

• http://pyohio.org

PyCon

• Great people

• Great vibe

• All volunteer–personal ownership

• Video! http://pycon.blip.tv/

• 2012 & 2013: Santa Clara

• 2014 & 2015: Montreal

TiP BoF

• Testing in Python Birds of a Feather

• Lightning Talks

• Heckling...

...and goats

The Zen

• Guiding principles

• Sage advice

• >>> import this

The Zen• Beautiful is better than ugly.

• Explicit is better than implicit.

• Simple is better than complex.

• Complex is better than complicated.

• Flat is better than nested.

• Sparse is better than dense.

• Readability counts.

• Special cases aren't special enough to break the rules.

• Although practicality beats purity.

• Errors should never pass silently.

• Unless explicitly silenced.

The Zen• In the face of ambiguity,

refuse the temptation to guess.

• 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.

• Now is better than never.

• Although never is often better than right now.

• If the implementation is hard to explain, it's a bad idea.

• If the implementation is easy to explain, it may be a good idea.

• Namespaces are one honking great idea -- let's do more of those!

What About You?

Fin

• Twitter : @mpirnat

• Blog: http://mike.pirnat.com

• Win a special prize–name all the movies

• Thanks for coming!

Recommended