178 votes

Fonctions imbriquées en Python : délimitation des variables

J'ai lu presque toutes les autres questions sur le sujet, mais mon code ne fonctionne toujours pas.

Je pense qu'il me manque quelque chose à propos de la portée des variables python.

Voici mon code :

PRICE_RANGES = {
                64:(25, 0.35),
                32:(13, 0.40),
                16:(7, 0.45),
                8:(4, 0.5)
                }

def get_order_total(quantity):
    global PRICE_RANGES
    _total = 0
    _i = PRICE_RANGES.iterkeys()
    def recurse(_i):
        try:
            key = _i.next()
            if quantity % key != quantity:
                _total += PRICE_RANGES[key][0]
            return recurse(_i) 
        except StopIteration:
            return (key, quantity % key)

    res = recurse(_i)

Et je reçois

Le nom global "_total" n'est pas défini".

Je sais que le problème se situe au niveau de la _total mais je ne comprends pas pourquoi. Ne devrait-on pas recurse() ont accès aux variables de la fonction mère ?

Quelqu'un peut-il m'expliquer ce que j'ai manqué à propos de la portée des variables en python ?

8voto

redman Points 107

Vous avez probablement obtenu la réponse à votre question. Mais je voulais vous indiquer un moyen que j'utilise habituellement pour contourner ce problème, à savoir l'utilisation de listes. Par exemple, si je veux faire ceci :

X=0
While X<20:
    Do something. ..
    X+=1

Je ferais plutôt ceci :

X=[0]
While X<20:
   Do something....
   X[0]+=1

Ainsi, X n'est jamais une variable locale

4voto

tantrix Points 33

D'un point de vue plus philosophique, une réponse pourrait être "si vous avez des problèmes d'espace de noms, donnez-lui son propre espace de noms !

Le fait de le fournir dans sa propre classe permet non seulement d'encapsuler le problème, mais aussi de faciliter les tests, d'éliminer ces globales gênantes et de réduire la nécessité de faire circuler des variables entre les différentes fonctions de premier niveau (il y aura sans doute plus de get_order_total ).

Préserver le code de l'OP pour se concentrer sur le changement essentiel,

class Order(object):
  PRICE_RANGES = {
                  64:(25, 0.35),
                  32:(13, 0.40),
                  16:(7, 0.45),
                  8:(4, 0.5)
                  }

  def __init__(self):
    self._total = None

  def get_order_total(self, quantity):
      self._total = 0
      _i = self.PRICE_RANGES.iterkeys()
      def recurse(_i):
          try:
              key = _i.next()
              if quantity % key != quantity:
                  self._total += self.PRICE_RANGES[key][0]
              return recurse(_i) 
          except StopIteration:
              return (key, quantity % key)

      res = recurse(_i)

#order = Order()
#order.get_order_total(100)

En tant que PS, un hack qui est une variante de l'idée de liste dans une autre réponse, mais peut-être plus claire,

def outer():
  order = {'total': 0}

  def inner():
    order['total'] += 42

  inner()

  return order['total']

print outer()

4voto

Evgeni Sergeev Points 1517

J'avais l'habitude d'utiliser l'approche basée sur les listes de @redman, mais elle n'est pas optimale en termes de lisibilité.

Voici une approche modifiée de @Hans, sauf que j'utilise un attribut de la fonction interne, plutôt que de la fonction externe. Cela devrait être plus compatible avec la récursivité, et peut-être même avec le multithreading :

def outer(recurse=2):
    if 0 == recurse:
        return

    def inner():
        inner.attribute += 1

    inner.attribute = 0
    inner()
    inner()
    outer(recurse-1)
    inner()
    print "inner.attribute =", inner.attribute

outer()
outer()

Cette empreinte :

inner.attribute = 3
inner.attribute = 3
inner.attribute = 3
inner.attribute = 3

Si je s/inner.attribute/outer.attribute/g nous obtenons :

outer.attribute = 3
outer.attribute = 4
outer.attribute = 3
outer.attribute = 4

Il semble donc préférable d'en faire les attributs de la fonction interne.

En outre, cela semble judicieux en termes de lisibilité : en effet, la variable se rapporte conceptuellement à la fonction interne, et cette notation rappelle au lecteur que la variable est partagée entre les champs d'application des fonctions interne et externe. Un léger inconvénient en termes de lisibilité est que l'élément inner.attribute ne peut être définie syntaxiquement qu'après la ligne de commande def inner(): ... .

0voto

Ma façon de faire...

def outer():

class Cont(object):
    var1 = None
    @classmethod
    def inner(cls, arg):
        cls.var1 = arg

Cont.var1 = "Before"
print Cont.var1
Cont.inner("After")
print Cont.var1

outer()

-1voto

Ant Points 1953
>>> def get_order_total(quantity):
    global PRICE_RANGES

    total = 0
    _i = PRICE_RANGES.iterkeys()
    def recurse(_i):
    print locals()
    print globals()
        try:
            key = _i.next()
            if quantity % key != quantity:
                total += PRICE_RANGES[key][0]
            return recurse(_i)
        except StopIteration:
            return (key, quantity % key)
    print 'main function', locals(), globals()

    res = recurse(_i)

>>> get_order_total(20)
main function {'total': 0, 'recurse': <function recurse at 0xb76baed4>, '_i': <dictionary-keyiterator object at 0xb6473e64>, 'quantity': 20} {'__builtins__': <module '__builtin__' (built-in)>, 'PRICE_RANGES': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, '__package__': None, 's': <function s at 0xb646adf4>, 'get_order_total': <function get_order_total at 0xb646ae64>, '__name__': '__main__', '__doc__': None}
{'recurse': <function recurse at 0xb76baed4>, '_i': <dictionary-keyiterator object at 0xb6473e64>, 'quantity': 20}
{'__builtins__': <module '__builtin__' (built-in)>, 'PRICE_RANGES': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, '__package__': None, 's': <function s at 0xb646adf4>, 'get_order_total': <function get_order_total at 0xb646ae64>, '__name__': '__main__', '__doc__': None}
{'recurse': <function recurse at 0xb76baed4>, '_i': <dictionary-keyiterator object at 0xb6473e64>, 'quantity': 20}
{'__builtins__': <module '__builtin__' (built-in)>, 'PRICE_RANGES': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, '__package__': None, 's': <function s at 0xb646adf4>, 'get_order_total': <function get_order_total at 0xb646ae64>, '__name__': '__main__', '__doc__': None}
{'recurse': <function recurse at 0xb76baed4>, '_i': <dictionary-keyiterator object at 0xb6473e64>, 'quantity': 20}
{'__builtins__': <module '__builtin__' (built-in)>, 'PRICE_RANGES': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, '__package__': None, 's': <function s at 0xb646adf4>, 'get_order_total': <function get_order_total at 0xb646ae64>, '__name__': '__main__', '__doc__': None}

Traceback (most recent call last):
  File "<pyshell#32>", line 1, in <module>
    get_order_total(20)
  File "<pyshell#31>", line 18, in get_order_total
    res = recurse(_i)
  File "<pyshell#31>", line 13, in recurse
    return recurse(_i)
  File "<pyshell#31>", line 13, in recurse
    return recurse(_i)
  File "<pyshell#31>", line 12, in recurse
    total += PRICE_RANGES[key][0]
UnboundLocalError: local variable 'total' referenced before assignment
>>> 

comme vous le voyez, total est dans la portée locale de la fonction principale, mais il n'est pas dans la portée locale de recurse (évidemment), mais il n'est pas non plus dans la portée globale, car il est défini uniquement dans la portée locale de get_order_total

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X