简体   繁体   中英

Python: Equivalent of Inheritance with Closures

I'm trying to transition from OOP to functional programming. I have the following situation: (the variables make no sense - they're just examples).

Funcs = namedtuple('Funcs', ('func1', 'func2'))

def thing_1(alpha, beta):
    gamma = alpha+beta
    def func_1(x):
        return x+gamma
    def func_2(x):
        return x*gamma
    return Funcs(func_1, func_2)

def thing_2(alpha, beta):
    gamma = alpha+beta
    delta = alpha*beta
    def func_1(x):
        return x+gamma
    def func_2(x):
        return x*gamma+delta
    return Funcs(func_1, func_2)

Now, we have some code duplication: func_1 is the same in both things. Both things also initialize gamma the same way.

If I use OOP, it's obvious what to do - make a BaseThing , make func_2 abstract, and have Thing1 override method func_2 , and Thing2 override both the func_2 method and __init__ (which will call BaseThing.__init__ then initialize delta).

Using closures, it's not obvious to me - what's the best way to do the same thing?

I didn't understand your specific example, but in a more abstract way, the difference between OOP and FP can be summarized as follows:

  • in OOP
    • object is a unit
    • parametrization is achieved by virtual methods
    • specialization is achieved by inheritance

In other words, the behavior of an object depends on (or "is parametrized by") virtual methods it calls. To fix (or "specialize") a certain set of "parameters" (=methods), you extend the object.

  • in FP
    • function is a unit
    • parametrization is achieved by functional parameters
    • specialization is achieved by partial application

To parametrize a function you pass other functions to it. To fix a set of parameters you create a new function which is the base func with partially applied params.

Illustration:

# OOP style

class Animal:
    def voice(self):
        pass
    def greet(self, person):
        return '%s, %s!' % (self.voice(), person)

class Dog(Animal):
    def voice(self):
        return 'woof'

class Cat(Animal):
    def voice(self):
        return 'meow'

print Dog().greet('master')
print Cat().greet('master')

# FP style

def greet(voice, person):
    return '%s, %s!' % (voice(), person)

from functools import partial

dogGreet = partial(greet, lambda: 'woof')
catGreet = partial(greet, lambda: 'meow')

print dogGreet('master')
print catGreet('master')

This works, but it's not particularly neat.

#!/usr/bin/env python

from collections import namedtuple

Funcs = namedtuple('Funcs', ('func1', 'func2'))

def thing_1(alpha, beta):
    gamma = alpha+beta
    def func_1(x):
        return x+gamma
    def func_2(x):
        return x*gamma
    return Funcs(func_1, func_2)

t1 = thing_1(3, 7)
print t1.func1(10), t1.func2(10)

def thing_2(alpha, beta):
    delta = alpha*beta
    t = thing_1(alpha, beta)
    def func_2(x):
        return t.func2(x) + delta
    return Funcs(t.func1, func_2)

t2 = thing_2(4, 6)
print t2.func1(10), t2.func2(10)

output

20 100
20 124

The most basic way is to create a separate closure for func_1 :

def gammafied_func_1(gamma):
    def func_1(x):
        return x + gamma
    return func_1

def thing_1(alpha, beta):
    gamma = alpha+beta
    def func_2(x):
        return x*gamma
    return Funcs(gammafied_func_1(gamma), func_2)

This sort of thing comes up often enough that there is a higher-order function for it, called partial , referring to the general concept of partial application. That lets you use one function to create a smaller function with some of its parameters "frozen":

from functools import partial

def func_1(gamma, x):
    return x + gamma

def thing_1(alpha, beta):
    gamma = alpha+beta
    def func_2(x):
        return x*gamma
    return Funcs(partial(func_1, gamma), func_2)

Here, partial(func_1, gamma) returns a new function that has the same body as func_1 , except it only takes an x parameter and gamma has been "frozen" to the local gamma inside thing_1 .

Scope works:

In [22]: def f1(x):
   ....:     return x + gamma
   ....: 

In [23]: def t1():
   ....:     return f1("foo")
   ....: 

In [24]: gamma = "bar"

In [25]: t1()
Out[25]: 'foobar'

f1 closes over gamma .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM