J'ai le code suivant dans une fonction:
stored_blocks = {}
def replace_blocks(m):
block = m.group(0)
block_hash = sha1(block)
stored_blocks[block_hash] = block
return '{{{%s}}}' % block_hash
num_converted = 0
def convert_variables(m):
name = m.group(1)
num_converted += 1
return '<%%= %s %%>' % name
fixed = MATCH_DECLARE_NEW.sub('', template)
fixed = MATCH_PYTHON_BLOCK.sub(replace_blocks, fixed)
fixed = MATCH_FORMAT.sub(convert_variables, fixed)
Ajouter des éléments à stored_blocks
fonctionne bien, mais je ne peux pas augmenter num_converted
dans la deuxième sous-fonction:
UnboundLocalError: variable locale 'num_converted' référencée avant l'affectation
Je pourrais utiliser global
mais les variables globales sont laides et je n’ai vraiment pas besoin que cette variable soit globale.
Je suis donc curieux de savoir comment écrire dans une variable de la portée de la fonction parent .nonlocal num_converted
ferait probablement l'affaire, mais j'ai besoin d'une solution compatible avec Python 2.x.
Problème: C'est parce que les règles de portée de Python sont démentes. La présence de l'opérateur d'affectation +=
marque la cible, num_converted
, comme étant locale à la portée de la fonction englobante, et Python 2.x ne permet pas d'accéder de manière rationnelle à un seul niveau de portée. Seul le mot clé global
peut extraire les références de variable de la portée actuelle et vous emmène directement au sommet.
Correction: Transformez num_converted
en un tableau à un seul élément.
num_converted = [0]
def convert_variables(m):
name = m.group(1)
num_converted[0] += 1
return '<%%= %s %%>' % name
(voir ci-dessous pour la réponse modifiée)
Vous pouvez utiliser quelque chose comme:
def convert_variables(m):
name = m.group(1)
convert_variables.num_converted += 1
return '<%%= %s %%>' % name
convert_variables.num_converted = 0
De cette façon, num_converted
fonctionne comme une variable "statique" de type C de la méthode convert_variable
(édité)
def convert_variables(m):
name = m.group(1)
convert_variables.num_converted = convert_variables.__dict__.get("num_converted", 0) + 1
return '<%%= %s %%>' % name
De cette façon, vous n'avez pas besoin d'initialiser le compteur dans la procédure principale.
L'utilisation du mot clé global
convient. Si vous écrivez:
num_converted = 0
def convert_variables(m):
global num_converted
name = m.group(1)
num_converted += 1
return '<%%= %s %%>' % name
... num_converted
ne devient pas une "variable globale" (c’est-à-dire qu’elle ne devient pas visible dans d’autres endroits inattendus), cela signifie simplement qu’elle peut être modifiée dans convert_variables
. Cela semble être exactement ce que vous voulez.
En d'autres termes, num_converted
est déjà une variable globale. Toute la syntaxe de global num_converted
consiste à dire à Python "dans cette fonction, ne créez pas de variable num_converted
locale, utilisez plutôt la variable globale existante.
Qu'en est-il de l'utilisation d'une instance de classe pour contenir l'état? Vous instanciez une classe et passez des méthodes d'instance à des sous-marins et ces fonctions auraient une référence à self.
J'ai quelques remarques.
Tout d'abord, une application pour de telles fonctions imbriquées apparaît lorsqu'il s'agit de rappels bruts, tels qu'ils sont utilisés dans des bibliothèques comme xml.parsers.expat. (Le fait que les auteurs de la bibliothèque aient choisi cette approche est peut-être choquant, mais ... il y a néanmoins des raisons de l'utiliser.)
Deuxièmement: au sein d'une classe, il existe des alternatives beaucoup plus agréables au tableau (num_converted [0]). Je suppose que c'est ce dont Sebastjan parlait.
class MainClass:
_num_converted = 0
def outer_method( self ):
def convert_variables(m):
name = m.group(1)
self._num_converted += 1
return '<%%= %s %%>' % name
C'est quand même assez étrange pour mériter un commentaire dans le code ... mais la variable est au moins locale à la classe.
Modifié de: https://stackoverflow.com/a/40690954/819544
Vous pouvez utiliser le module inspect
pour accéder au dict de règles globales de la portée de l'appel et y écrire. Cela signifie que cette astuce peut même être utilisée pour accéder à la portée de l'appel à partir d'une fonction imbriquée définie dans un sous-module importé.
import inspect
def get_globals(scope_level=0):
return dict(inspect.getmembers(inspect.stack()[scope_level][0]))["f_globals"]
num_converted = 0
def foobar():
get_globals(0)['num_converted'] += 1
foobar()
print(num_converted)
# 1
Jouez avec l'argument scope_level
si nécessaire. Définir scope_level=1
fonctionne pour une fonction définie dans un sous-module, scope_level=2
pour la fonction interne définie dans un décorateur dans un sous-module, etc.
NB: Le fait que vous puissiez faire cela ne signifie pas que vous devriez le faire.