Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
195 views
in Technique[技术] by (71.8m points)

Python - bound variable scope to closure

I have some function which uses outside variables. A (substantially) simplified example:

a = 2
b = 3

def f(x):
    return x * a + b

While I need a and b in f, I don't need them anywhere else. In particular, one can write a = 5, and that will change the behavior of f. How should I make a and b invisible to the outside?

Other languages allow me to write roughly the following code:

let f = 
   a = 2
   b = 3
   lambda x: x * a + b

What I want:

  • f must work as intended and have the same signature
  • a and b must be computed only once
  • a and b must not exist in the scope outside of f
  • Assignments a = ... and b = ... don't affect f
  • The cleanest way to do this. E.g. the following solution formally works, but it introduces g and then deletes it, which I don't like (e.g. there is a risk of overriding an existing g and I believe that it's simply ugly):
def g():
    a = 2
    b = 3
    return lambda x: x * a + b

f = g()
del g

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

One method is to simply use a class. This allows you to place a and b in the scope of the class while f can still access them.

custom class

class F:
    def __init__(self):
        self.a = 2
        self.b = 3
    
    def __call__(self, x):
        return x * self.a + self.b

f = F()
f(1)
# returns:
5

If you don't like having to call the class constructor, you can override __new__ to essentially create a callable with internal stored variables. This is an antipattern though and not very pythonic.

custom callable

class f:
    a = 2
    b = 3

    def __new__(cls, x):
        return x * cls.a + cls.b

f(1)
# returns:
5

This approach is based on the answers provided in this thread, though scoped to the specific problem above. You can use a decorator to update the global variables available to the function while also storin a and b within a closure.

decorator with closure

from functools import wraps

def dec_ab(fn):
    a = 2
    b = 3
    @wraps(fn)
    def wrapper(*args, **kwargs):
        # get global scope
        global_scope = f.__globals__

        # copy current values of variables
        var_list = ['a', 'b']
        current_vars = {}
        for var in var_list:
            if var in global_scope:
                current_vars[var] = global_scope.get(var)

        # update global scope
        global_scope.update({'a': a, 'b': b})
        try:
            out = fn(*args, **kwargs)
        finally:
            # undo the changes to the global scope
            for var in var_list:
                global_scope.pop(var)
            global_scope.update(current_vars)

        return out
    return wrapper

@dec_ab
def f(x):
    """hello world"""
    return x * a + b

This preserves the functions signature and keeps a and b from being altered

f(1)
# returns: 
5

a
# raises:
NameError: name 'a' is not defined

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...