Object Orientation vs. Functional Programming in Python

Preview:

DESCRIPTION

Python is a multi-paradigm language meaning it supports different programming styles, Object Orientation and Functional Programming being the major ones. However choice is not always a good thing, if you are interested in writing modular programs that are easy to maintain and promote code reuse what should you do? This talk discusses modularity in this context looking at Python's support for both paradigms, comparing and contrasting them. We then look at Python techniques and tools that bridge the perceived impedance mismatch between Object Orientation and Functional Programming.

Citation preview

Object Orientationvs.

Functional Programming

Writing Modular Python Programs

Twitter: @insmallportionswww.insmallportions.com

About Me

Modularity

RoadmapThesisObject Orientation is a proven way of creating models in software that represent the problem domain in a useful manner. There are many patterns that show how to achieve the modularity goal in different contexts.

AntithesisFunctional Programming is a long standing approach to defining processes in terms of others at different levels of abstraction. Higher order functions make the idiomatic ways to perform certain tasks fall out naturally.

SynthesisPython has good support for both styles of programming and for good reason. Depending on the situation one or the other maybe more appropriate. Moreover in Python these tools do not only exist but they complement each other.

Object Orientation

• Class Oriented 1.The Three Pillars of

OO are:2.Delegation– Polymorphism– Instantiation

Template Methodclass Game(object):    PLAYERS = 2    def initialize_game(self):        raise NotImplementedError()    def make_play(self, player):        raise NotImplementedError()    def end_of_game(self):        raise NotImplementedError()    def print_winner(self):        print self.current

    def play_game(self, players=PLAYERS):        self.initialize_game()        self.current = 0        while not self.end_of_game():            self.make_play(self.current)            self.current = (self.current + 1)\                % players        self.print_winner()

class Monopoly(Game):    PLAYERS = 4    def initialize_game(self):        pass # Monopoly code here    def make_play(self, player):        pass # Monopoly code here    def end_of_game(self):        pass # Monopoly code here    def print_winner(self):        pass # Monopoly code here

class Chess(Game):    def initialize_game(self):        pass # Chess code here    def make_play(self, player):        pass # Chess code here    def end_of_game(self):        pass # Chess code here

Abstract Base Classes

>>> class MyDict(dict):...   def __getitem__(self, key):...       return 101... >>> d = MyDict()>>> d['x']101>>> d.get('x', 202)202>>> 

>>> from collections import Mapping>>> class >>> from collections import Mapping>>> class MyMapping(Mapping):...     def __getitem__(self, key):...         return 101... >>> m = MyMapping()Traceback (most recent call last):  File "<stdin>", line 1, in <module>TypeError: Can't instantiate abstract class MyMapping with abstract methods __iter__, __len__

Mixins

class XMPPClient(object):    def connect(self):        pass # XMPP code    def disconnect(self):        pass # XMPP code    def send(self, player):        pass # XMPP code    def terminate(self, player):        raise NotImplementedError()        def mainain_presence(self):        self.connect()        while not self.terminate():            yield        self.disconnect()

class OnlineChess(Game,                 XMPPClient):    def initialize_game(self):        pass # Chess code here

    ...    def end_of_game(self):        pass # Chess code here

    def terminate(self, player):        return self.end_of_game()

Mixins (Multiple Inheritance)

class A(object):    pass

class B(A):    def method1(self):        pass

class C(A):    def method1(self):        pass class D(B, C):    pass

Wrapping/Composition• Prefer Composition over Inheritance• Use a class's functionality but not its API• Expose only limited part of an object

 • Typical uses:

o Adapto Proxyo Decorate

Wrapping/Compositionclass Eprom(object):    def read(self):        pass # Eprom code    def write(self, data):        pass # Eprom code    def complete(self):        pass # Eprom code

class FileLikeEprom(object):    def __init__(self, eprom):        self._eprom = eprom    def read(self):        return self._eprom.read()    def write(self, data):        self._eprom.write(data)    def close(self):        self._eprom.complete()

class SafeEprom(object):    def __init__(self, eprom):        self._eprom = eprom    def read(self):        return self._eprom.read()    def write(self, data):        if data.safe():            self._eprom.write(data)    def close(self):        self._eprom.complete()

Wrapping/Composition (Tricks)

class FileLikeEprom(object):    def __init__(self, eprom):        self._eprom = eprom    def __getattr__(self, a):        if a == 'close':            return self.close        else:            return getattr(self._eprom, a)    def close(self):        self._eprom.complete()

• Don't Repeat Yourself• Avoid boilerplate• Use __getattr__ to

return computed attributes

Mixins Again

class SafeAndFileLike(FileLikeEprom, SafeEprom):    def __init__(self, *args, **kwargs):        return super(SafeAndFileLike, self).__init__(*args, **kwargs)

RoadmapThesisObject Orientation is a proven way of creating models in software that represent the problem domain in a useful manner. There are many patterns that show how to achieve the modularity goal in different contexts.

AntithesisFunctional Programming is a long standing approach to defining processes in terms of others at different levels of abstraction. Higher order functions make the idiomatic ways to perform certain tasks fall out naturally.

SynthesisPython good support for both styles of programming and for good reason. Depending on the situation one or the other maybe more appropriate. Moreover in Python these tools do not only exist but they complement each other.

Functional Programming

• Functions take input and produce output, without any side effects.

• Pure functional languages are strict about side effect freeness.

• Python is not a pure functional language.

• Functions may be internally imperative, but appear purely functional in their behaviour.

Callbacks

• The Hollywood principle• Role reversal, library code calls your code• Library code accepts a callable and invokes it

when appropriate • The main uses:

o Customisationo Event Handling

sorted() sans Callbacks>>> people = [Person('John', 'Smith'),...     Person('Mary', 'Doe'),...     Person('Lucy', 'Pearl'),]>>> for p in sorted(people):...     print p... Mary DoeLucy PearlJohn Smith>>>

class Person(object):    def __init__(self, f, s):        self.f = f        self.s = s    def __str__(self):        return '%s %s' % (self.f, self.s)    def __eq__(self, other):        return self.s == other.s    def __lt__(self, other):        return self.s < other.s

sorted() with Callbacks>>> for p in sorted(people, key=first_name):...     print p... John SmithLucy PearlMary Doe>>> for p in sorted(people, key=surname_name):...     print p... Mary DoeLucy PearlJohn Smith>>>

class Person(object):    def __init__(self, f, s):        self.f = f        self.s = s    def __str__(self):        return '%s %s' % (self.f, self.s) first_name = lambda p: p.fsurname = lambda p: p.s

operator module

from operator import attrgetter  class Person(object):    def __init__(self, f, s):        self.f = f        self.s = s    def __str__(self):        return '%s %s' % (self.f, self.s) first_name = attrgetter('f')surname = attrgetter('s')

• attrgetter• itemgetter• add• mul• pow• ...

Operations on aggregates

• sum• filter• map• reduce

>>> def square(x):...         return x ** 2... >>> l = [1, 2, 3, 4, 5]>>> sum(map(square, l))55>>> def square(x):...        return x ** 2...>>> def odd(x):...        return x % 2...>>> l = [1, 2, 3, 4, 5]>>> sum(map(square, filter(odd, l)))35

itertools module

• cycle()• repeat()• chain()• tee()• product()• ...

Decoratorsdef cache(fn, c=None):    if c is None: c = {}    def cached(*args):        if args in c:            return c[args]        result = fn(*args)        c[args] = result        return result    return cached

def adder(x, y):    return x + y adder = cache(adder)

def cache(fn, c=None):    if c is None: c = {}    def cached(*args):        if args in c:            return c[args]        result = fn(*args)        c[args] = result        return result    return cached

@cachedef adder(x, y):    return x + y

Do not write code like this, use: functools.lru_cache

Partial function evaluation

>>> from functools import partial>>> >>> def power(base, exp=1):...         return base ** exp... >>> square = partial(power, exp=2)>>> cube = partial(power, exp=3)>>> >>> l = [1, 2, 3, 4, 5]>>> sum(map(square, l))55>>> print sum(map(cube, l))225

RoadmapThesisObject Orientation is a proven way of creating models in software that represent the problem domain in a useful manner. There are many patterns that show how to achieve the modularity goal in different contexts.

AntithesisFunctional Programming is a long standing approach to defining processes in terms of others at different levels of abstraction. Higher order functions make the idiomatic ways to perform certain tasks fall out naturally.

SynthesisPython good support for both styles of programming and for good reason. Depending on the situation one or the other maybe more appropriate. Moreover in Python these tools do not only exist but they complement each other.

Best of Both Worlds

Unbound methods

• Functions are descriptors• Override binding behaviour• Override differently for A.x

and a.x• Unbound methods know

their class but not their instance

• Ideal for use in a functional style

>>> food = ['Spam', 'ham', 'Cheese', 'eggs']>>> sorted(food)['Cheese', 'Spam', 'eggs', 'ham']

>>> sorted(food, key=str.lower)['Cheese', 'eggs', 'ham', 'Spam']>>> >>> sorted(food, key='ham'.lower)Traceback (most recent call last):  File "<stdin>", line 1, in <module>TypeError: lower() takes no arguments (1 given)

Computed fields (property)class Person(object):    def __init__(self, f, s):        self.f = f        self.s = s    @property    def fullname(self):        return '%s %s' % (self.f,            self.s) >>> p = Person('John', 'Smith')>>> p.fullname'John Smith'

class Person(object):    def __init__(self, f, s):        self.f = f        self._s = s    @property    def s(self):        return self._s.upper()     @s.setter    def s(self, value):        self._s = value >>> p = Person('Jane', 'Doe')>>> p.s'DOE'

property([fget[, fset[, fdel[, doc]]]])

property and inheritanceclass Person(object):    def __init__(self, t, f, s):        ...        def full(self):        return '%s %s' % (self.f,self.s)    fullname = property(full)

class Customer(Person):    def full(self):        return '%s. %s %s' %                     (self.t, self.f, self.s)  >>> c = Customer('Mr', 'John', 'Smith')>>> c.fullname'John Smith'

class Person(object):    def __init__(self, t, f, s):        ...    def full(self):       return '%s %s' % (self.f, self.s)    def _full(self):        return self.full()    fullname = property(_full) class Customer(Person):    def full(self):        return '%s. %s %s' %                        (self.t, self.f, self.s) >>> c.fullname'Mr John Smith'

Dependency Inversion class Employee(object):    def __init__(self, f, s):        self.f = f        self.s = s    def register(self):        pass # Register me

def register(emps):    for f, s in emps:        emp = Employee(f, s)        emp.register()

>>> emps = [('John', 'Smith'), ('Mary', 'Doe')]>>>register(emps)

def employee_fact(f, s):    return Employee(f, s)

def register(emps, fact):    for f, s in emps:        emp = fact(f, s)        emp.register()

>>> emps = [('John', 'Smith'), ('Mary', 'Doe')]>>>register(emps, employee_fact)

Python classes are factories

class Employee(object):    def __init__(self, f, s):        self.f = f        self.s = s    def register(self):        pass # Register me

def register(emps, fact):    for f, s in emps:        emp = fact(f, s)        emp.register()

>>> emps = [('John', 'Smith'), ('Mary', 'Doe')]>>>register(emps, Employee)

• Python classes are callables• Indistinguishable from other

callables to the caller• Allow us to postpone the

creation of a factory until it actually needed

Many types of callables• Functions• Unbound methods• Bound methods• Classes

• Any object that has a __call__ method is a callable

• Testable using the callable built-in function

 >>> callable(str)True>>> callable('Spam')False>>> 

class Callable(object):    def __init__(self, m):        self.message = m    def __call__(self):        print self.messageclass NotCallable(object):    def call(self):        print "You Rang?"

>>> c = Callable('You Rang')>>> c()You Rang>>> n = NotCallable()>>> n()Traceback (most recent call last):  File "<stdin>", line 1, in <module>TypeError: 'NotCallable' object is not callable

RoadmapThesisObject Orientation is a proven way of creating models in software that represent the problem domain in a useful manner. There are many patterns that show how to achieve the modularity goal in different contexts.

AntithesisFunctional Programming is a long standing approach to defining processes in terms of others at different levels of abstraction. Higher order functions make the idiomatic ways to perform certain tasks fall out naturally.

SynthesisPython good support for both styles of programming and for good reason. Depending on the situation one or the other maybe more appropriate. Moreover in Python these tools do not only exist but they complement each other nicely.

We hire superheroes!

• www.demonware.net/jobs/ • Development & Operations

Positions • Come talk to us

Recommended