简体   繁体   中英

Is there a more 'pythonic' way to write this closure?

In JavaScript I might write a function like this:

function counter() {
    foo = 0;
    function increment() {
    foo += 1
    console.log(foo);
  }
  function printVal() {
    console.log(foo);
  }

  return {
                increment: increment,
          printVal: printVal,
                  }    
}

func = counter();

func.increment()
func.increment()
func.printVal()

I want to try to achieve a similar function using python. Particularly how I can access both inner functions via the outer function's return.

Here is a working, but funny looking, version in python:

def counter():
    foo = {'bar': 0}
    def increment():
        foo['bar'] += 1
        return foo['bar']
    def printVal():
        return foo['bar']
    return {'increment': increment, 'printVal': printVal}

func = counter()
func['increment']()
func['printVal']()

Is there some sort of more elegant or 'pythonic' way of writing a closure like this?

Python is not as strong as other languages in closures. First, it supports only reading the "closed" variables, and second, the return statement makes it a bit more awkward.

On the other hand, it is very concise with classes, so if you want a data member with two functions, I would do it with a class:

class Counter:
    def __init__(self, c=0):
        self.count = c
    def increment(self):
        self.count += 1
    def printVal(self):
        return self.count


c = Counter()
c.increment()
print(c.printVal())

I dare say that in this case, this would be the pythonic way.

EDIT

After seeing your comment about tracking function calls, I add this. You can make it by a closure, like this:

# this is the tracked function
def add2(a, b):
    return a + b

# this is the count tracker
def counterize(func):
    c = [0]
    def counter_func(*args, **kw):
        c[0] += 1
        counter_func.count = c[0]
        return func(*args, **kw)
    return counter_func


cadd2 = counterize(add2)

print(cadd2(1, 2))
print(cadd2(3, 4))
print('Called %s times' % cadd2.count)

>>>
3
7
Called 2 times

But this is not idiomatic in Python. Also keeping count inside the function object is a nice trick, but that's what it is. A trick. In LISP or Scala on the other hand a closure would be more natural, but then, I don't think it would be possible to keep count as a field, but, rather, return it together with the result.

I would say that in this case, again, the idiomatic Python code would be via classes, it is more comprehensible IMO and is of the same length of code:

class Counterize:
    def __init__(self, func):
        self.func = func
        self.count = 0
    def __call__(self, *args, **kwargs):
        self.count += 1
        return self.func(*args, **kwargs)


cadd2 = Counterize(add2)
print(cadd2(1, 2))
print(cadd2(3, 4))
print('Called %s times' % cadd2.count)

And the output would be the same. The purpose of __call__ is to allow treating the object as a function by means of calling with parenthesis.

Simply implement __getitem__

class Foo:
    class_attributes = {'a': 3,
                        'b': 5}

    def __getitem__(self, name):
        return Foo.class_attributes[name]


f = Foo()
print f['a']

output:

3

I think that what you want to achieve is something like the following example :

def makeInc(x):
  def inc(y):
     # x is "attached" in the definition of inc
     return y + x

  return inc

incOne = makeInc(1)
incFive = makeInc(5)

incOne (5) # returns 6
incFive(5) # returns 10

You want the pythonic way of creating closures. So the above example demonstrate how to do that.

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