Slides from my PyCon 2011 talk, "Exhibition of Atrocity," a confessional of my sins against the Python programming language.
Abstract: http://us.pycon.org/2011/schedule/presentations/138/
Video: http://www.pycon.tv/#/video/49
23. class MyGiganticUglyClass(object):
def iUsedToWriteJava(self,x,y = 42):
blnTwoSpacesAreMoreEfficient = 1
while author.tragicallyConfused():
print "Three spaces FTW roflbbq!!1!"
if (new_addition):
four_spaces_are_best = True
if (multipleAuthors
or peopleDisagree):
print "tabs! spaces are so mainstream"
return ((pain) and (suffering))
24. class MyGiganticUglyClass(object):
def iUsedToWriteJava(self,x,y = 42):
blnTwoSpacesAreMoreEfficient = 1
while author.tragicallyConfused():
print "Three spaces FTW roflbbq!!1!"
if (new_addition):
four_spaces_are_best = True
if (multipleAuthors
or peopleDisagree):
print "tabs! spaces are so mainstream"
return ((pain) and (suffering))
25. class MyGiganticUglyClass(object):
def iUsedToWriteJava(self,x,y = 42):
blnTwoSpacesAreMoreEfficient = 1
while author.tragicallyConfused():
print "Three spaces FTW roflbbq!!1!"
if (new_addition):
four_spaces_are_best = True
if (multipleAuthors
or peopleDisagree):
print "tabs! spaces are so mainstream"
return ((pain) and (suffering))
26. class MyGiganticUglyClass(object):
def iUsedToWriteJava(self,x,y = 42):
blnTwoSpacesAreMoreEfficient = 1
while author.tragicallyConfused():
print "Three spaces FTW roflbbq!!1!"
if (new_addition):
four_spaces_are_best = True
if (multipleAuthors
or peopleDisagree):
print "tabs! spaces are so mainstream"
return ((pain) and (suffering))
27. class MyGiganticUglyClass(object):
def iUsedToWriteJava(self,x,y = 42):
blnTwoSpacesAreMoreEfficient = 1
while author.tragicallyConfused():
print "Three spaces FTW roflbbq!!1!"
if (new_addition):
four_spaces_are_best = True
if (multipleAuthors
or peopleDisagree):
print "tabs! spaces are so mainstream"
return ((pain) and (suffering))
28. class MyGiganticUglyClass(object):
def iUsedToWriteJava(self,x,y = 42):
blnTwoSpacesAreMoreEfficient = 1
while author.tragicallyConfused():
print "Three spaces FTW roflbbq!!1!"
if (new_addition):
four_spaces_are_best = True
if (multipleAuthors
or peopleDisagree):
print "tabs! spaces are so mainstream"
return ((pain) and (suffering))
29. class MyGiganticUglyClass(object):
def iUsedToWriteJava(self,x,y = 42):
blnTwoSpacesAreMoreEfficient = 1
while author.tragicallyConfused():
print "Three spaces FTW roflbbq!!1!"
if (new_addition):
four_spaces_are_best = True
if (multipleAuthors
or peopleDisagree):
print "tabs! spaces are so mainstream"
return ((pain) and (suffering))
30. class MyGiganticUglyClass(object):
def iUsedToWriteJava(self,x,y = 42):
blnTwoSpacesAreMoreEfficient = 1
while author.tragicallyConfused():
print "Three spaces FTW roflbbq!!1!"
if (new_addition):
four_spaces_are_best = True
if (multipleAuthors
or peopleDisagree):
print "tabs! spaces are so mainstream"
return ((pain) and (suffering))
31. from regrets import unfortunate_choices
class AnotherBadHabit(object):
short_name = 'foo'
much_longer_name = 'bar'
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z_is_a_silly_name = z
self.came_later = 42
self.leftover = ‘timewaster’
self.dictionary = {
'foo' : 'bar',
'bar' : 'baz',
'baz' : 'quux',
}
32. from regrets import unfortunate_choices
class AnotherBadHabit(object):
short_name = 'foo'
much_longer_name = 'bar'
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z_is_a_silly_name = z
self.came_later = 42
self.leftover = ‘timewaster’
self.dictionary = {
'foo' : 'bar',
'bar' : 'baz',
'baz' : 'quux',
}
33. from regrets import unfortunate_choices
class AnotherBadHabit(object):
short_name = 'foo'
much_longer_name = 'bar'
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z_is_a_silly_name = z
self.came_later = 42
self.leftover = ‘timewaster’
self.dictionary = {
'foo' : 'bar',
'bar' : 'baz',
'baz' : 'quux',
}
34. A Twisty Maze of
Single-Character
Variables, All Alike
41. lstRollOut = lstRollOut + filter(lambda x:
x[-1] == '0', filter(lambda x: x != '0|0',
lstMbrSrcCombo))
if not filter(lambda lst, sm=sm: sm in
lst, map(lambda x, dicA=dicA: dicA.get(x,
[]), lstAttribute)):
42.
43. _make_keys = lambda cc, pm:
tuple(map(lambda m, c=cc: ("%s.%s" % (c,
m), m), pm))
44. _make_keys = lambda cc, pm:
tuple(map(lambda m, c=cc: ("%s.%s" % (c,
m), m), pm))
return [map(lambda l: l[0],
lstResults),map(lambda l: l[1],
lstResults)]
45. _make_keys = lambda cc, pm:
tuple(map(lambda m, c=cc: ("%s.%s" % (c,
m), m), pm))
return [map(lambda l: l[0],
lstResults),map(lambda l: l[1],
lstResults)]
sum = lambda lst: lst and reduce(lambda x,
y: x + y, lst) or 0
46. _make_keys = lambda cc, pm:
tuple(map(lambda m, c=cc: ("%s.%s" % (c,
m), m), pm))
return [map(lambda l: l[0],
lstResults),map(lambda l: l[1],
lstResults)]
sum = lambda lst: lst and reduce(lambda x,
y: x + y, lst) or 0
assert reduce(lambda x,y: x or y, [z.id ==
event.id for z in events])
49. lstCrumb = [y for y in [x.replace('"','') for x in
lstCrumb] if y]
50. lstCrumb = [y for y in [x.replace('"','') for x in
lstCrumb] if y]
return [dict(x) for x in [zip(lstKeys,x) for x in
lstValues]]
51. lstCrumb = [y for y in [x.replace('"','') for x in
lstCrumb] if y]
return [dict(x) for x in [zip(lstKeys,x) for x in
lstValues]]
prop_list = [FilterProp(prop='P_EXCLUDED',
data='_'.join([i,j,k])) for i in prop_data[0] for
j in prop_data[1] for k in prop_data[2]]
52. def feedparser_entries_to_RSSItems(entries):
rss_items = [PyRSS2Gen.RSSItem(
title = x.get('title','Untitled'),
link = x.link,
description = x.summary,
guid = x.link,
pubDate = datetime.datetime(
x.modified_parsed[0],
x.modified_parsed[1],
x.modified_parsed[2],
x.modified_parsed[3],
x.modified_parsed[4],
x.modified_parsed[5]))
for x in entries]
return rss_items
93. class DataObject(object):
def __new__(cls, *args, **kwargs):
obj = object.__new__(cls)
for key, value in cls.__dict__.items():
if not callable(value)
and not hasattr(value, '__get__')
and not key.startswith('_'):
setattr(obj, key, value)
return obj
94. class DataObject(object):
def __new__(cls, *args, **kwargs):
obj = object.__new__(cls)
for key, value in cls.__dict__.items():
if not callable(value)
and not hasattr(value, '__get__')
and not key.startswith('_'):
setattr(obj, key, value)
return obj
95. class DataObject(object):
def __new__(cls, *args, **kwargs):
obj = object.__new__(cls)
for key, value in cls.__dict__.items():
if not callable(value)
and not hasattr(value, '__get__')
and not key.startswith('_'):
setattr(obj, key, value)
return obj
96. class StrictDataObject(DataObject):
def __init__(self, **kwargs):
for key, value in kwargs.items():
if hasattr(self, key):
setattr(self, key, value)
else:
raise AttributeError(
'key %r must be predefined '
'at the class level' % key)
97. class StrictDataObject(DataObject):
def __init__(self, **kwargs):
for key, value in kwargs.items():
if hasattr(self, key):
setattr(self, key, value)
else:
raise AttributeError(
'key %r must be predefined '
'at the class level' % key)
98. class StrictDataObject(DataObject):
def __init__(self, **kwargs):
for key, value in kwargs.items():
if hasattr(self, key):
setattr(self, key, value)
else:
raise AttributeError(
'key %r must be predefined '
'at the class level' % key)
99. class StrictDataObject(DataObject):
def __init__(self, **kwargs):
for key, value in kwargs.items():
if hasattr(self, key):
setattr(self, key, value)
else:
raise AttributeError(
'key %r must be predefined '
'at the class level' % key)
100. class MutantDataObject(StrictDataObject):
def __new__(cls, *args, **kwargs):
new_object = object.__new__(cls)
for c in cls.__mro__[::-1]:
update_friendly_dict =
_get_update_friendly_dict_from_class_dictproxy(c.__dict__)
new_object.update(update_friendly_dict)
return new_object
def __init__(self, **kwargs):
deferred = {}
clean = {}
for key, value in kwargs.items():
if hasattr(self, key):
clean[key] = value
else:
deferred[key] = value
super(MutantDataObject, self).__init__(**clean)
self.update(deferred)
...
103. Time to instantiate 100000
15
11.25
7.5
3.75
0
DataObject StrictDataObject MutantDataObject
104. class DataObject(object):
_mutables = None
__metaclass__ = DataObjectMeta
def __new__(cls, **kwargs):
obj = object.__new__(cls)
for name, default_func in cls._mutables:
if name not in kwargs:
setattr(obj, name, default_func())
return obj
def __init__(self, **kwargs):
for name, value in kwargs.iteritems():
setattr(self, name, value)
105. class DataObject(object):
_mutables = None
__metaclass__ = DataObjectMeta
def __new__(cls, **kwargs):
obj = object.__new__(cls)
for name, default_func in cls._mutables:
if name not in kwargs:
setattr(obj, name, default_func())
return obj
def __init__(self, **kwargs):
for name, value in kwargs.iteritems():
setattr(self, name, value)
106. class DataObject(object):
_mutables = None
__metaclass__ = DataObjectMeta
def __new__(cls, **kwargs):
obj = object.__new__(cls)
for name, default_func in cls._mutables:
if name not in kwargs:
setattr(obj, name, default_func())
return obj
def __init__(self, **kwargs):
for name, value in kwargs.iteritems():
setattr(self, name, value)
107. class DataObject(object):
_mutables = None
__metaclass__ = DataObjectMeta
def __new__(cls, **kwargs):
obj = object.__new__(cls)
for name, default_func in cls._mutables:
if name not in kwargs:
setattr(obj, name, default_func())
return obj
def __init__(self, **kwargs):
for name, value in kwargs.iteritems():
setattr(self, name, value)
108. class DataObjectMeta(type):
def __new__(meta, classname, bases, classdict):
mutables = find_mutables(classdict, bases)
classdict['_mutables'] = mutables
return type.__new__(meta, classname, bases, classdict)
def find_mutables(classdict, bases):
def create_default(name, value):
return name, lambda: copy.copy(value)
found_mutables = set()
mutables = set()
for name, value in classdict.items():
if (not name.startswith('__') and not callable(value)
and not hasattr(value, '__get__') and
isinstance(value, (list, dict))):
if not name in found_mutables:
found_mutables.add(name)
mutables.add(create_default(name, value))
del classdict[name]
for base in bases:
if hasattr(base, '_mutables'):
for name, value in base._mutables:
if not name in found_mutables:
found_mutables.add(name)
mutables.add((name, value))
return frozenset(mutables)
109. Time to instantiate 100000
15
11.25
7.5
3.75
0
DataObject StrictDataObject MutantDataObject Metaclass DataObject
125. • Highly convenient when writing front-end
website code
• Highly painful when doing anything else
(scripts, cron jobs, back-end systems)
• Any code that uses it is not reusable
outside the website context without
extensive faking
126. • Highly convenient when writing front-end
website code
• Highly painful when doing anything else
(scripts, cron jobs, back-end systems)
• Any code that uses it is not reusable
outside the website context without
extensive faking
• Safety issues/data leakage
162. Things to Explore
• Clean Code: http://ASIN.cc/~11GQo
• http://code.google.com/p/snake-guice/
163. Things to Explore
• Clean Code: http://ASIN.cc/~11GQo
• http://code.google.com/p/snake-guice/
• http://www.voidspace.org.uk/python/mock/
164.
165.
166. Links
• Twitter: @mpirnat
• Blog: http://mike.pirnat.com
• Inspiration:
• Gary Numan: http://youtu.be/Oj2PR9CI9xY
• Joy Division: http://youtu.be/5AqeqAQ1ILI
• Hyperbole and a Half: http://hyperboleandahalf.blogspot.com/2010/06/
this-is-why-ill-never-be-adult.html
Notes de l'éditeur
Hi, I’m Mike. I’ve been working with Python for 11 years and one week, and during that time [SWITCH]\n
...I’ve had THIS experience pretty regularly. I’ve come to think that one of the best ways to know what good code looks like is to know what it DOESN’T look like. So today we’re going to take a tour of some of my code sins and try to save you some of that toe-stubbing, face-palming pain.\n
I write most of my code for work; for this talk, most of the examples I’m going to show have been abstracted down from actual code or distilled from memory of past habits so that I can have permission to use them; hopefully this helps us focus on actual insights rather than wasting your time with reams of gnarly source code. I’ve got a lot of stuff to talk about and I’m going to move fast to get through it.\n
When I started with Python, our shop was religious about Hungarian notation\n
That’s where you prefix your variable names to indicate what sort of things they are. Meant to be a guidepost for the future, hint at intentions, set expectations. At first, this seems quite reasonable.\n
That’s where you prefix your variable names to indicate what sort of things they are. Meant to be a guidepost for the future, hint at intentions, set expectations. At first, this seems quite reasonable.\n
That’s where you prefix your variable names to indicate what sort of things they are. Meant to be a guidepost for the future, hint at intentions, set expectations. At first, this seems quite reasonable.\n
That’s where you prefix your variable names to indicate what sort of things they are. Meant to be a guidepost for the future, hint at intentions, set expectations. At first, this seems quite reasonable.\n
That’s where you prefix your variable names to indicate what sort of things they are. Meant to be a guidepost for the future, hint at intentions, set expectations. At first, this seems quite reasonable.\n
That’s where you prefix your variable names to indicate what sort of things they are. Meant to be a guidepost for the future, hint at intentions, set expectations. At first, this seems quite reasonable.\n
But this quickly leads to silliness - a list of dictionaries, useless “hey look an object” notation, abbreviations, things that don’t know what they want, and frightening ambiguity\n
But this quickly leads to silliness - a list of dictionaries, useless “hey look an object” notation, abbreviations, things that don’t know what they want, and frightening ambiguity\n
But this quickly leads to silliness - a list of dictionaries, useless “hey look an object” notation, abbreviations, things that don’t know what they want, and frightening ambiguity\n
But this quickly leads to silliness - a list of dictionaries, useless “hey look an object” notation, abbreviations, things that don’t know what they want, and frightening ambiguity\n
But this quickly leads to silliness - a list of dictionaries, useless “hey look an object” notation, abbreviations, things that don’t know what they want, and frightening ambiguity\n
From there it’s a short trip to outright lies -- a number that isn’t a number, fake false values in booleans, and, if something goes wrong, AttributeErrors when you get a None where you don’t expect to.\n
From there it’s a short trip to outright lies -- a number that isn’t a number, fake false values in booleans, and, if something goes wrong, AttributeErrors when you get a None where you don’t expect to.\n
From there it’s a short trip to outright lies -- a number that isn’t a number, fake false values in booleans, and, if something goes wrong, AttributeErrors when you get a None where you don’t expect to.\n
From there it’s a short trip to outright lies -- a number that isn’t a number, fake false values in booleans, and, if something goes wrong, AttributeErrors when you get a None where you don’t expect to.\n
It was also a time before I really knew or cared about community standards and good style\n
I picked up bad habits from the existing codebase and perpetuated them for much longer than I should have. [quick remarks about each]\n
I picked up bad habits from the existing codebase and perpetuated them for much longer than I should have. [quick remarks about each]\n
I picked up bad habits from the existing codebase and perpetuated them for much longer than I should have. [quick remarks about each]\n
I picked up bad habits from the existing codebase and perpetuated them for much longer than I should have. [quick remarks about each]\n
I picked up bad habits from the existing codebase and perpetuated them for much longer than I should have. [quick remarks about each]\n
I picked up bad habits from the existing codebase and perpetuated them for much longer than I should have. [quick remarks about each]\n
I picked up bad habits from the existing codebase and perpetuated them for much longer than I should have. [quick remarks about each]\n
I picked up bad habits from the existing codebase and perpetuated them for much longer than I should have. [quick remarks about each]\n
I picked up bad habits from the existing codebase and perpetuated them for much longer than I should have. [quick remarks about each]\n
I picked up bad habits from the existing codebase and perpetuated them for much longer than I should have. [quick remarks about each]\n
I picked up bad habits from the existing codebase and perpetuated them for much longer than I should have. [quick remarks about each]\n
There’s an appealing foolishness to lining things up; it may look nice at first but you’re quickly locked into a hell-spiral of adjusting the interior whitespace as code changes, and annoying artifacts are inevitably left behind.\n
There’s an appealing foolishness to lining things up; it may look nice at first but you’re quickly locked into a hell-spiral of adjusting the interior whitespace as code changes, and annoying artifacts are inevitably left behind.\n
Short variable names mean you can fit more code into an 80-character line, so they must be good, right?\n
Wrong. Inscrutable variable names lead to code that’s hard to read and that people are afraid to clean up.\n
I can only take credit for finding this one--check out how defining everything as single characters lets us write lines like this:\n
I’ve become increasingly allergic to lambdas. Even when they’re simple, they’re hard to read...\n
And they’ll get complicated in a hurry. (Sadly, I couldn’t find the triple-lambda that I remember writing many moons ago...)\n
And they’ll get complicated in a hurry. (Sadly, I couldn’t find the triple-lambda that I remember writing many moons ago...)\n
Worse, the desire for brevity drives us to use inscrutable single-character variable names!\nIn the interest of optimizing for our fellow humans, give your functions descriptive names instead of packing as much cleverness as possible into a single line. Your functions deserve it, and your colleagues deserve it.\n
Worse, the desire for brevity drives us to use inscrutable single-character variable names!\nIn the interest of optimizing for our fellow humans, give your functions descriptive names instead of packing as much cleverness as possible into a single line. Your functions deserve it, and your colleagues deserve it.\n
Worse, the desire for brevity drives us to use inscrutable single-character variable names!\nIn the interest of optimizing for our fellow humans, give your functions descriptive names instead of packing as much cleverness as possible into a single line. Your functions deserve it, and your colleagues deserve it.\n
Worse, the desire for brevity drives us to use inscrutable single-character variable names!\nIn the interest of optimizing for our fellow humans, give your functions descriptive names instead of packing as much cleverness as possible into a single line. Your functions deserve it, and your colleagues deserve it.\n
List comprehensions are really powerful, and beautiful until you start filling them full of crap.\n
Nesting list comprehensions adds significant cognitive load for most mortals. First one’s not too bad; second one starts to scare the junior devs who aren’t friends with zip yet; but this last one is just freaky.\n
Nesting list comprehensions adds significant cognitive load for most mortals. First one’s not too bad; second one starts to scare the junior devs who aren’t friends with zip yet; but this last one is just freaky.\n
Nesting list comprehensions adds significant cognitive load for most mortals. First one’s not too bad; second one starts to scare the junior devs who aren’t friends with zip yet; but this last one is just freaky.\n
This is taken from a personal project that scrapes my LiveJournal friends page and converts it into an RSS feed. Minus some formatting issues, it’s not terribly unclean, but so much space gets taken by the setup of RSSItem that it’s easy to lose track of the list comprehension itself.\n
Doesn’t this look cleaner?\n
This deadly pattern emerges when you aim to provide a “one stop shop” that has to deal with a lot of special cases.\n
For example, swapping out functionality based on what site we’re serving. Once you start tacking on a few elifs, it’s hard to stop!\n
Pull the insides of the if/elif block out into separate functions; build a map based on some key, then just choose the key, grab the function, and call it. But this can get ugly too as determining the key can be a lot of work.\n
Pull the insides of the if/elif block out into separate functions; build a map based on some key, then just choose the key, grab the function, and call it. But this can get ugly too as determining the key can be a lot of work.\n
Pull the insides of the if/elif block out into separate functions; build a map based on some key, then just choose the key, grab the function, and call it. But this can get ugly too as determining the key can be a lot of work.\n
Pull the insides of the if/elif block out into separate functions; build a map based on some key, then just choose the key, grab the function, and call it. But this can get ugly too as determining the key can be a lot of work.\n
Pull the insides of the if/elif block out into separate functions; build a map based on some key, then just choose the key, grab the function, and call it. But this can get ugly too as determining the key can be a lot of work.\n
So why not externalize it entirely -- let it get chosen by the caller, who may know better than we do about those deciding factors.\n
And then it’s just a quick step into Dependency Injection, where our code gets provided with what it needs.\n
You can even use a DI framework like snake-guice to help wire up your objects.\n
You can even use a DI framework like snake-guice to help wire up your objects.\n
You can even use a DI framework like snake-guice to help wire up your objects.\n
\n
I didn’t actually do this one, as I had learned the lesson the hard way; instead I let this happen to a coworker who was new to Python by not making it part of his early onboarding to the language. Our app was only supposed to let users set a max of 4 reminders for an event, but...\n
we’d keep accumulating more and more data in that list as the process handled more and more requests. Periodically we’d restart the app server and the problem would vanish, only to build again.\n
we’d keep accumulating more and more data in that list as the process handled more and more requests. Periodically we’d restart the app server and the problem would vanish, only to build again.\n
\n
\n
There are various ways of doing this; here’s one.\n
There are various ways of doing this; here’s one.\n
There are various ways of doing this; here’s one.\n
\n
\n
\n
\n
\n
\n
In between Perl and Python, I was briefly a Java guy, and while my exposure was brief, the effects were lasting.\n
I wrote an invitations app years ago, all of the model classes were very protective of their internals, but without much benefit. Same basic get/set pain repeated ~40 times in that class. And the calling syntax gets cumbersome and ugly.\n
I wrote an invitations app years ago, all of the model classes were very protective of their internals, but without much benefit. Same basic get/set pain repeated ~40 times in that class. And the calling syntax gets cumbersome and ugly.\n
I wrote an invitations app years ago, all of the model classes were very protective of their internals, but without much benefit. Same basic get/set pain repeated ~40 times in that class. And the calling syntax gets cumbersome and ugly.\n
I wrote an invitations app years ago, all of the model classes were very protective of their internals, but without much benefit. Same basic get/set pain repeated ~40 times in that class. And the calling syntax gets cumbersome and ugly.\n
I wrote an invitations app years ago, all of the model classes were very protective of their internals, but without much benefit. Same basic get/set pain repeated ~40 times in that class. And the calling syntax gets cumbersome and ugly.\n
Much nicer to just access attributes directly and use modern property syntax if we really need special logic\n
Much nicer to just access attributes directly and use modern property syntax if we really need special logic\n
Much nicer to just access attributes directly and use modern property syntax if we really need special logic\n
That invite stuff that had all those getters and setters also had wicked __init__s that would take 40 or more parameters and then call all those getters and setters. So in my next big project I thought I’d learn from that and do some magic.\n
Was building a backend for a reminder system that was going to use Data Transfer Objects to speak XMLRPC with our web app front end, and needed to make sure that all the data attributes made it across the wire...\n
So with a little __new__ magic, we transform all of the class attributes into instance attributes so that xmlrpclib will include our Nones\n
So with a little __new__ magic, we transform all of the class attributes into instance attributes so that xmlrpclib will include our Nones\n
So with a little __new__ magic, we transform all of the class attributes into instance attributes so that xmlrpclib will include our Nones\n
Then, remembering those brutal inits, it seems reasonable to automatically set everything that got passed in our keyword args--we just saved a ton of typing, right? And maybe only allow it to be set if we declared up front that it’s something our DTO *should* be getting.\n
Then, remembering those brutal inits, it seems reasonable to automatically set everything that got passed in our keyword args--we just saved a ton of typing, right? And maybe only allow it to be set if we declared up front that it’s something our DTO *should* be getting.\n
Then, remembering those brutal inits, it seems reasonable to automatically set everything that got passed in our keyword args--we just saved a ton of typing, right? And maybe only allow it to be set if we declared up front that it’s something our DTO *should* be getting.\n
Then, remembering those brutal inits, it seems reasonable to automatically set everything that got passed in our keyword args--we just saved a ton of typing, right? And maybe only allow it to be set if we declared up front that it’s something our DTO *should* be getting.\n
Then, remembering those brutal inits, it seems reasonable to automatically set everything that got passed in our keyword args--we just saved a ton of typing, right? And maybe only allow it to be set if we declared up front that it’s something our DTO *should* be getting.\n
Eventually I wanted to be able to declare mutables -- like lists and dictionaries -- in my class and have fresh, unshared copies of them for each instance that’s created, and thus was born MutantDataObject.\n
This init-less convenience becomes quite popular and used in a number of systems. Then one day we get a nasty surprise from a system that uses them heavily--it’s dog slow.\n
\n
The lookup and copying of mutables blows MutantDataObject’s startup time through the roof. So how do we fix it?\n
Our replacement DataObject is now based on a metaclass that knows how to find and copy those mutables more efficiently, and it also takes advantage of iterators\n
Our replacement DataObject is now based on a metaclass that knows how to find and copy those mutables more efficiently, and it also takes advantage of iterators\n
Our replacement DataObject is now based on a metaclass that knows how to find and copy those mutables more efficiently, and it also takes advantage of iterators\n
Our replacement DataObject is now based on a metaclass that knows how to find and copy those mutables more efficiently, and it also takes advantage of iterators\n
Our replacement DataObject is now based on a metaclass that knows how to find and copy those mutables more efficiently, and it also takes advantage of iterators\n
And here’s what the metaclass looks like--don’t look at it too hard now, I’ll have the slides up soon.\n
The important thing is that it saves a lot of time!\n
A “god object” or “god method” is one that has accumulated too many responsibilities. It may start out subtle at first, but they’re easy to recognize before long...\n
\n
\n
\n
\n
\n
\n
Favor small functions and small classes with as few responsibilities as possible. Strive to do no work in the init. Break up the wanna-be gods before they get out of hand.\n
This is one of my deepest regrets.\n
It started so simply, as a favor to another developer, who needed easy access to some objects and didn’t want to pass them around everywhere... Basically it’s a module with module-level globals that get repopulated by the app server on every request\n
If you import it, you can talk to those globals. You can do this at any level, from the lofty heights that first see the request, to the deepest, darkest depths.\n
Try not to do this.\n
Try not to do this.\n
Try not to do this.\n
Try not to do this.\n
Try not to do this.\n
Having easy access to all that awesome global state also encourages you to break the Law of Demeter, which basically says that you should only interact with the things you know about and not reach through to “friends of friends” or “strangers”. But it’s damn convenient!\n
Here are just a few of countless examples of my crimes -- reaching across multiple objects to call a method, through dictionaries, into single-underscore “internals”, into the contents of a list...\n
Here are just a few of countless examples of my crimes -- reaching across multiple objects to call a method, through dictionaries, into single-underscore “internals”, into the contents of a list...\n
Here are just a few of countless examples of my crimes -- reaching across multiple objects to call a method, through dictionaries, into single-underscore “internals”, into the contents of a list...\n
Here are just a few of countless examples of my crimes -- reaching across multiple objects to call a method, through dictionaries, into single-underscore “internals”, into the contents of a list...\n
\n
The Diaper Pattern is a popular term in my office for the antipattern of catching and silencing exceptions\n
A lot of us have young kids, so we naturally call this a “diaper” because it tries to catch all the crap. Unfortunately, it doesn’t always work so well, and we can have “blowouts” -- if anything had gone wrong getting foo, our call to do_something_important will die!\n\nThe farther away the diaper is from the code that it breaks, the harder it is to find--in a large codebase, it can be buried quite a ways down.\n
A lot of us have young kids, so we naturally call this a “diaper” because it tries to catch all the crap. Unfortunately, it doesn’t always work so well, and we can have “blowouts” -- if anything had gone wrong getting foo, our call to do_something_important will die!\n\nThe farther away the diaper is from the code that it breaks, the harder it is to find--in a large codebase, it can be buried quite a ways down.\n
A lot of us have young kids, so we naturally call this a “diaper” because it tries to catch all the crap. Unfortunately, it doesn’t always work so well, and we can have “blowouts” -- if anything had gone wrong getting foo, our call to do_something_important will die!\n\nThe farther away the diaper is from the code that it breaks, the harder it is to find--in a large codebase, it can be buried quite a ways down.\n
We’re better off when exceptions bubble up, then at least you know where they came from. Errors, as they say, should not pass silently.\nOK to diaper at system boundaries - eg, around a web service or xmlrpc backend where you always want reasonable output\n
Coming into the home stretch now, let’s talk about some fun ways to make people angry. We’re going to meet a thing called DUCK PUNCHER.\n
If the test breaks, the teardown never happens, and whatever was “punched” stays punched. Maybe this hurts you immediately -- if “open” got punched, nose will die because it can’t get at the stack trace -- or maybe it hurts you thirty directories away when something that wasn’t changed starts failing.\n
The immediate fix is to make sure we have a try/finally block.\n\n
Really folks should just learn to use Mock's @patch decorator or context manager and not reinvent the wheel\n
\n
Once upon a time in the bad old days of Python 1.4, we needed to log.\n
Once upon a time in the bad old days of Python 1.4, we needed to log.\n
Once upon a time in the bad old days of Python 1.4, we needed to log.\n
Once upon a time in the bad old days of Python 1.4, we needed to log.\n
Once upon a time in the bad old days of Python 1.4, we needed to log.\n
Once upon a time in the bad old days of Python 1.4, we needed to log.\n
Once upon a time in the bad old days of Python 1.4, we needed to log.\n
Once upon a time in the bad old days of Python 1.4, we needed to log.\n
Once upon a time in the bad old days of Python 1.4, we needed to log.\n
Once upon a time in the bad old days of Python 1.4, we needed to log.\n
Once upon a time in the bad old days of Python 1.4, we needed to log.\n