简体   繁体   中英

What is the most pythonic way to reuse data in multiple calls to same function?

Normally I would not ask such a question, but python seems to have 1. an unusual level of community consensus on idioms and 2. tends to encourage them by making them more performant (eg list comprehension vs map, filter).

This is a pattern I find myself using quite a bit when coding, consider the following JavaScript:

var f = (function() {
  var closedOver = "whatever"
  return function(param) {
    // re-uses closure variable again and again with different param
  }
})();

Or C:

int foo(int x)
{
  /* 
    compile-time constant, will not be recalced for every call,
    name 'someConst' not visible in other scopes 
  */
  const int someConst = 134;
  /* do stuff */
  return whatever;
}

Some possible ways to translate into python:

globalConstant = someConstant
def foo(param):
    # does stuff with param and constant
    return whatever

or possibly:

from functools import partial
def foo(invariant, variant):
    """Actually bar"""
    # does stuff
    return whatever

bar = partial(foo, someInvariant)

or:

class Foo(object):
    """I'm just here to hold a non-visible binding. Actually bar"""
    def __init__(self, invariant):
        super(Foo, self).__init__()
        self.value = invariant

    def __call__(self, param):
        return actualFnResultWithSelfValue

bar = Foo(invariant)

or:

def foo(variant, invariant=someConstantValue):
  return whatever

This is unfortunate, now depending on which way I go I may have to use a throw-away name for the initial function definition since I'm only ever using the partially applied version, write a lot of boilerplate classes (which also have throw-away names), or pollute the module namespace with a global constant when its only used in one function, or restrict my function parameters and ensure that someone can break it by calling it with the wrong number of arguments.

I could also 'solve' this by re-instantiating on every call and hoping that it will get optimized away, but since I'm not using pypy I'm not too hopeful on that score.

So my question is two-fold: first, is there a way to do this without the trade-offs? And second, if not, which of the above is the most 'pythonic' (idiomatic, performant, reasonable, etc.)?

Jared, I totally understand your hesitation to ask this, because it could be answered by many different opinions and spawn a flame war. But, I do agree with your observation: the Python community does tend towards consistency over time with many implementation questions. That's one of the strengths of Python.

Here's my rule of thumb: When in doubt, try to use the Python standard library as much as possible. Your instinct here about functools.partial is correct, for these reasons:

  1. The Python standard library is highly optimized C code that will out-perform any Python class or function closure structure you come up with.
  2. The Python standard library is widely used by other Python programmers, so when you use it, your coding intent will be more widely understood by other Python programmers. ("Code is read more often than it is written.")
  3. The Python standard library is programmed by the core Python developers; no code could possibly claim to be more "Pythonic" than the standard library.

I hope that helps!

I'd suggest something that's usually a code smell - default mutable argument.

Trivial example:

def f(x, cache={'x': 0}):
    cache['x'] += x;
    return cache['x']


assert f(1) == 1
assert f(1) == 2  
assert f(1) == 3
assert f(3) == 6

Your dict (or list, or anything which is mutable) is bound to function object. Subsequent calls, when cache keyword argument is omitted, will refer to same object. This object state will persist across calls.

I don't like idea with var in outer scope and may suggest use closure, I think it's better way, as you see it's more like JavaScript, so you can work with functions as objects :

def foo():
    const = 1
    def bar(param):
      return const + param
    return bar

a = foo()
print(a(5))

There is an alternative that you didn't mention, which is IMO the most pythonic way which wouldn't create a throw away name :)

def foo(variant):
    try:
        return foo.invariant + variant
    except AttributeError:
        foo.invariant = 1
        return foo(variant)

Although, sticking with the std library would be the best.

I don't think your two first examples are equivalent. The first seems to be a closure, while the second uses a global constant. Anyway, for the first example, a direct equivalent in python would be

def f(x):
   def g(y):
      return x + y
   return g

and use it like

add2 = f(2)
print(add2(3)) # == 5

Actually, your implementation using partial , does something similar under the hood

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