web-dev-qa-db-fra.com

mot-clé non local dans Python 2.x

J'essaie d'implémenter une fermeture dans Python 2.6 et j'ai besoin d'accéder à une variable non locale mais il semble que ce mot-clé ne soit pas disponible dans python 2. x. Comment doit-on accéder aux variables non locales dans les fermetures de ces versions de python?

110
adinsa

Les fonctions internes peuvent lire les variables non locales dans 2.x, tout simplement pas relier les. C'est ennuyeux, mais vous pouvez contourner ce problème. Créez simplement un dictionnaire et stockez vos données en tant qu'éléments. Les fonctions internes ne sont pas interdites de muter les objets auxquels les variables non locales se réfèrent.

Pour utiliser l'exemple de Wikipedia:

def outer():
    d = {'y' : 0}
    def inner():
        d['y'] += 1
        return d['y']
    return inner

f = outer()
print(f(), f(), f()) #prints 1 2 3
116
Chris B.

La solution suivante est inspirée par la réponse d'Elias Zamaria , mais contrairement à cette réponse, elle gère correctement les appels multiples de la fonction externe. La "variable" inner.y est local à l'appel en cours de outer. Seulement ce n'est pas une variable, car c'est interdit, mais un attribut d'objet (l'objet étant la fonction inner elle-même). C'est très moche (notez que l'attribut ne peut être créé qu'après la définition de la fonction inner) mais semble efficace.

def outer():
    def inner():
        inner.y += 1
        return inner.y
    inner.y = 0
    return inner

f = outer()
g = outer()
print(f(), f(), g(), f(), g()) #prints (1, 2, 1, 3, 2)
37
Marc van Leeuwen

Plutôt qu'un dictionnaire, il y a moins d'encombrement dans une classe non locale . Modifier @ ChrisB's exemple :

def outer():
    class context:
        y = 0
    def inner():
        context.y += 1
        return context.y
    return inner

Ensuite

f = outer()
assert f() == 1
assert f() == 2
assert f() == 3
assert f() == 4

Chaque appel externe () crée une nouvelle classe distincte appelée contexte (et pas simplement une nouvelle instance). Cela évite donc @ Nathaniel's méfiez-vous sur le contexte partagé.

g = outer()
assert g() == 1
assert g() == 2

assert f() == 5
29
Bob Stein

Je pense que la clé ici est ce que vous entendez par "accès". Il ne devrait pas y avoir de problème avec la lecture d'une variable en dehors de la portée de fermeture, par exemple,

x = 3
def outer():
    def inner():
        print x
    inner()
outer()

devrait fonctionner comme prévu (impression 3). Cependant, la substitution de la valeur de x ne fonctionne pas, par exemple,

x = 3
def outer():
    def inner():
        x = 5
    inner()
outer()
print x

imprimera toujours 3. D'après ma compréhension de PEP-3104 c'est ce que le mot-clé non local est censé couvrir. Comme mentionné dans le PEP, vous pouvez utiliser une classe pour accomplir la même chose (un peu désordonné):

class Namespace(object): pass
ns = Namespace()
ns.x = 3
def outer():
    def inner():
        ns.x = 5
    inner()
outer()
print ns.x
14
ig0774

Il existe une autre façon d'implémenter des variables non locales dans Python 2, au cas où l'une des réponses ici ne serait pas souhaitable pour une raison quelconque:

def outer():
    outer.y = 0
    def inner():
        outer.y += 1
        return outer.y
    return inner

f = outer()
print(f(), f(), f()) #prints 1 2 3

Il est redondant d'utiliser le nom de la fonction dans l'instruction d'affectation de la variable, mais cela me semble plus simple et plus propre que de placer la variable dans un dictionnaire. La valeur est mémorisée d'un appel à l'autre, tout comme dans la réponse de Chris B.

12
Elias Zamaria

Voici quelque chose inspiré d'une suggestion faite par Alois Mahdal dans un commentaire concernant un autre réponse :

class Nonlocal(object):
    """ Helper to implement nonlocal names in Python 2.x """
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)


def outer():
    nl = Nonlocal(y=0)
    def inner():
        nl.y += 1
        return nl.y
    return inner

f = outer()
print(f(), f(), f()) # -> (1 2 3)

Mise à jour

Après y avoir récemment repensé, j'ai été frappé de voir à quel point cela ressemblait à un décorateur - quand j'ai compris que le mettre en œuvre comme tel le rendrait plus générique et utile (bien que cela dégrade sans doute sa lisibilité dans une certaine mesure).

# Implemented as a decorator.

class Nonlocal(object):
    """ Decorator class to help implement nonlocal names in Python 2.x """
    def __init__(self, **kwargs):
        self._vars = kwargs

    def __call__(self, func):
        for k, v in self._vars.items():
            setattr(func, k, v)
        return func


@Nonlocal(y=0)
def outer():
    def inner():
        outer.y += 1
        return outer.y
    return inner


f = outer()
print(f(), f(), f()) # -> (1 2 3)

Notez que les deux versions fonctionnent dans les deux Python 2 et 3.

11
martineau

Une autre façon de le faire (bien que ce soit trop verbeux):

import ctypes

def outer():
    y = 0
    def inner():
        ctypes.pythonapi.PyCell_Set(id(inner.func_closure[0]), id(y + 1))
        return y
    return inner

x = outer()
x()
>> 1
x()
>> 2
y = outer()
y()
>> 1
x()
>> 3
3
Ezer Fernandes

Il y a une verrue dans les règles de portée de python - l'affectation rend une variable locale à sa portée de fonction immédiatement englobante. Pour une variable globale, vous pouvez résoudre ce problème avec le mot clé global.

La solution est d'introduire un objet qui est partagé entre les deux étendues, qui contient des variables mutables, mais est lui-même référencé via une variable qui n'est pas affectée.

def outer(v):
    def inner(container = [v]):
        container[0] += 1
        return container[0]
    return inner

Une alternative est le piratage de certaines étendues:

def outer(v):
    def inner(varname = 'v', scope = locals()):
        scope[varname] += 1
        return scope[varname]
    return inner

Vous pourrez peut-être trouver une astuce pour obtenir le nom du paramètre à outer, puis le passer en tant que varname, mais sans compter sur le nom outer, vous aimeriez avoir besoin d'utiliser un Y Combinator.

3
Marcin

En étendant la solution élégante de Martineau ci-dessus à un cas d'utilisation pratique et un peu moins élégant, j'obtiens:

class nonlocals(object):
""" Helper to implement nonlocal names in Python 2.x.
Usage example:
def outer():
     nl = nonlocals( n=0, m=1 )
     def inner():
         nl.n += 1
     inner() # will increment nl.n

or...
    sums = nonlocals( { k:v for k,v in locals().iteritems() if k.startswith('tot_') } )
"""
def __init__(self, **kwargs):
    self.__dict__.update(kwargs)

def __init__(self, a_dict):
    self.__dict__.update(a_dict)
0
Amnon Harel