USING (AND ABUSING) PYTHON'S MAGIC METHODS TO REDUCE GOO CODE
USING (AND ABUSING) PYTHON'S MAGIC METHODS
TO REDUCE GOO CODE
WHO AM I?
• Work at indico (small local company)
• Previously at Olin College, Pearson, edX, fetchnotes, and freelance work
• Semi-retired SO & quora user
• Love playing with the dirty little secrets of python
PART 1: USING MAGIC METHODS
YOU CAN’T CONTROL ACCESS TO ATTRIBUTES IN
PYTHON
class MutableObject(object): def __init__(self, a): self.a = a
>>> test = MutableObject("one") >>> test.a = "two"
NORMAL PYTHON ATTRIBUTES
class MutableObject(object): def __init__(self, a): self._a = a
>>> test = MutableObject("one") >>> test._a = "two" # Feels Wrong
TELLING OTHER USERS NOT TO TOUCH YOUR ATTRIBUTES
class StubbornObject(object):
def __setattr__(self, key, value): if hasattr(self, key): raise ValueError(“Already set in my ways") else: object.__setattr__(self, key, value)
>>> test = StubbornObject() >>> test.a = "one" >>> test.a = “two” # Now actually errors
ACTUALLY STOPPING PEOPLE FROM TOUCHING YOUR ATTRIBUTES
PART 2: (AB)USING MAGIC METHODS
results = my_object.attribute if not isinstance(results, list): results = list(results) # Do Something
EVER WRITTEN CODE LIKE THIS?
class ChangelingObject(object): list_fields = {"a", "b", "c"}
def __setattr__(self, key, value): if key in self.list_fields: value = [value] object.__setattr__(self, key, value)
>>> test = ChangelingObject() >>> test.a = 1 >>> print test.a [1]
NEVER AGAIN WITH SPECIAL PYTHON MAGIC
PART 3: ABUSING MAGIC METHODS
def add_money(user_id, amount): session = Session() user_object = session.query(User).filter_by(id=user_id).first()
current_value = user_object.balance try: user_object.balance = current_value + amount session.commit() except ORMException: session.rollback
class User(object): # Standard ORM stuff
def __iadd__(self, other): current = user_object.balance try: user_object.balance = current + other session.commit() except ORMException: session.rollback()
def add_money(user_id, amount): session = Session() user_object = session.query(User).filter_by(id=user_id).first() user_object += amount
SHORT CODE, BUT AT WHAT COST?
PART 4: HERE THERE BE DRAGONS
class Food(object): recipes = { frozenset({"flour", "water"}): "dough", frozenset({"dough", "yeast"}): "bread", frozenset({"avocado", "onion"}): "guac", frozenset({"guac", "bread"}): "tasty snack" }
def __init__(self, ingredient): self.name = ingredient
def _mix(self, second): current_pantry = frozenset({self.name, second.name}) try: return self.recipes[current_pantry] except KeyError: raise ValueError("%s and %s don't mix, trust me" % (self.name, second.name))
def __add__(self, other): return Food(self._mix(other))
def __iadd__(self, other): self.name = self._mix(other)
def __repr__(self): return self.name
>>> step_one = Food("flour") + Food("water") + Food("yeast") >>> step_two = Food("avocado") + Food("onion") >>> print step_one + step_two tasty snack
PLEASE NEVER ACTUALLY DO THIS
MAGIC METHODS ARE EXTREMELY POWERFUL, BUT
REMEMBER:
ALWAYS CODE AS IF THE PERSON WHO ENDS UP
MAINTAINING YOUR CODE IS A VIOLENT PSYCHOPATH WHO
KNOWS WHERE YOU LIVE.— Jeff Atwood