Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Idioms in python: closure vs functor vs object

Tags:

python

So I am curious about the views of more experienced python programmers on the following style question. Suppose that I am building a function that is going to iterate row by row through a pandas dataframe or any similar use-case where a function requires access to its previous state. There seem to be at least four ways to implement this in python:

  1. Closures:
def outer():
    previous_state = None
    def inner(current_state) :
        nonlocal previous_state
        #do something
        previous_state=current_state
        return something

So if you come from a javascript background this will doubtless seem natural to you. It feels pretty natural in python too, right up until you need to access the enclosing scope when you will end up doing something like inner.__code__.co_freevars, which will give you the names of your enclosing variables as a tuple, and finding the index of the one you want, and then going to inner.__closure__[index].cell_contents to get its value. Not exactly elegant, but I suppose the point is often to hide scope, so it makes sense that it should be hard to reach. On the other hand, it also feels a bit weird that python makes the enclosing function private when it has done away with almost every other way to have a private variable compared to OOP languages.

  1. Functor
def outer():
    def inner(current_state):
        #do something
        inner.previous_state=current_state
        return something
    ret = inner
    ret.previous_state=None
    return ret

This "opens the closure" in that now the enclosing state is fully visible as an attribute of the function. This works because functions are really just objects in disguise. I am leaning towards this as the most pythonic. Its clear, concise, and readable.

  1. Objects This is probably the most familiar to OOP programmers
class Calculator(Object) :
    def __init__(self):
        self.previous_state=None

    def do_something(self, current_state) :
        #do_something
        self.previous_state = current_state
        return something

The biggest con here is that you tend to end up with a lot of class definitions. That is fine in a fully OOP language like Java where you have interfaces and the like to manage this, but it seems a bit odd in a duck typed language to have many simple classes just to carry around a function that needs a bit of state.

  1. globals - I won't demonstrate this as I specifically want to avoid polluting the global namespace

  2. Decorators - this is a little bit of a curveball, but you can use decorators to store partial state information.

@outer
def inner(previous_state, current_state):
    #do something
    return something

def outer(inner) :
    def wrapper(current_state) :
        result =  inner(wrapper.previous_state, current_state)
        wrapper.previous_state = current_state
        return result
    ret = wrapper
    ret.previous_state=None
    return result

This kind of syntax is the least familiar to me, but if I now call

func = inner

I actually get

func = outer(inner)

and then repeatedly calling func() acts just like the functor example. I actually really hate this way the most. It seems to me to have a really non transparent syntax in that it isn't clear if calling inner(current_state) lots of times will give you the same result or if it will give you a newly decorated function every time, so it seems like bad practice to make decorators which add state to a function in this way.

So which is the correct way? What pros and cons have I missed here?

like image 331
phil_20686 Avatar asked Dec 16 '15 16:12

phil_20686


People also ask

Are closures like objects?

If you come from a FP background, you might find thinking in terms of closures more natural, and may therefore think of them as a more fundamental concept than objects. Moral of the story is that closures and objects are ideas that are expressible in terms of each other, and none is more fundamental than the other.

What is closure in Python?

A Closure is a function object that remembers values in enclosing scopes even if they are not present in memory.

What is closure and decorators in Python?

A decorator is a function that takes in a function and returns an augmented copy of that function. When writing closures and decorators, you must keep the scope of each function in mind. In Python, functions define scope. Closures have access to the scope of the function that returns them; the decorator's scope.

What are Python closures?

Python Closures are these inner functions enclosed within the outer function. Let us see a simple example to work around inner and outer functions − To understand Python Closures, lets first understand what’s nested function and python class. A function defined inside another function is called a nested function.

Why do we use functors in Python?

Functors are used when you want to hide/abstract the real implementation. Let’s say you want to call the different functions depending on the input but you don’t want the user code to make explicit calls to those different functions. This is the ideal situation where functors can help.

What is a function closure in C++?

A Closure is a function object that remembers values in enclosing scopes even if they are not present in memory.

What is the difference between innerfunction () and closure () in JavaScript?

Hence, here, innerFunction () is treated as nested Function which uses text as non-local variable. A Closure is a function object that remembers values in enclosing scopes even if they are not present in memory.


2 Answers

So the correct answer to this is the callable object, which essentially replaces the idiom of the closure in python.

so working off option 3 above change:

class Calculator(Object) :
    def __init__(self):
        self.previous_state=None

    def do_something(self, current_state) :
        #do_something
        self.previous_state = current_state
        return something

to

class Calculator(Object) :
    def __init__(self):
        self.previous_state=None

    def __call__(self, current_state) :
        #do_something
        self.previous_state = current_state
        return something

and now you can call it like a function. So

func = Calculator():
for x in list:
    func(x)
like image 58
phil_20686 Avatar answered Sep 24 '22 13:09

phil_20686


You can define a generator, which is a restricted form of a coprocess.

def make_gen():
    previous_state = None
    for row in rows:
        # do something
        previous_state = current_state
        yield something

thing = make_gen()
for item in thing:
    # Each iteration, item is a different value
    # "returned" by the yield statement in the generator

Instead of calling thing (which replaces your inner function) repeatedly, you iterate over it (which is basically the same as calling next(thing) repeatedly).

The state is entirely contained within the body of the generator.

If you don't want to actually iterate over it, you can still selectively "re-enter" the coprocess by calling next explicitly.

thing = make_gen()
first_item = next(thing)
# do some stuff
second_item = next(thing)
# do more stuff
third_item = next(thing)
fourth_item = next(thing)
# etc
like image 40
chepner Avatar answered Sep 22 '22 13:09

chepner