Upload
michael-pirnat
View
16.796
Download
2
Embed Size (px)
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
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!