2016-02-16 4 views
2

Est-il possible de changer le comportement des variables global et local en Python lors de l'exécution?Est-il possible de faire en sorte que les locals() et globals() ressemblent à des valeurs par défaut

En Python, locals() donne des références aux variables de la portée d'exécution actuelle, qui est un objet dict.

>>> locals() 
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', '__doc__': None, '__package__': None} 

Est-il possible de remplacer cette référence retourné par locals() à un defaultdict, mais conserver les valeurs précédentes (une copie de locals()) avant de le remplacer?

Je m'attendrais à ce que cela évite les exceptions UnboundLocalException lors de l'utilisation de variables non initialisées et d'accéder à n'importe quel nom de variable dans la portée d'exécution (les variables non initialisées prendraient une valeur par défaut spécifiée).

J'ai essayé de modifier la valeur retournée par locals() en la réaffectant aux locaux sans succès.

La même question vaut pour globals().

Répondre

4

Non, vous ne pouvez pas. locals() est juste une réflexion de l'espace de noms réel utilisé pour les fonctions. Pour des raisons de performances, l'espace de noms réel est un tableau et les locals ne sont pas recherchés par nom mais par index. Vous ne pouvez pas ajouter de nouveaux noms après coup, car le compilateur n'a tout simplement pas pris en compte plus de références dans le tableau.

Notez que les exceptions NameError sont renvoyées pour les globaux manquants, pas locaux. Les noms locaux, s'ils ne sont pas encore liés, lancent un UnboundLocalException à la place. Cependant, vous ne pouvez pas remplacer le dictionnaire globals() par un defaultdict; l'attribut __dict__ des objets de module est en lecture seule. Même s'il n'était pas en lecture seule, seul le type dict intégré est pris en charge en raison de la manière dont les noms sont recherchés dans l'espace de noms; c'est by design.

2

Oui, en quelque sorte, en utilisant metaclasses, l'outil de Python pour modifier le comportement de l'instruction class.

Ici, DefaultDictMeta renvoie une instance de defaultdict à partir de sa méthode __prepare__. Toute classe avec une métaclasse de DefaultDictMeta finira donc par utiliser une instance de defaultdict pour son espace de noms. Cela signifie que les recherches de noms ne peuvent pas échouer dans le corps de NoNameErrorsInBody.

from collections import defaultdict 

class DefaultDictMeta(type): 
    def __prepare__(name, bases, default=None, **kwargs): 
     return defaultdict(lambda: default) 

    def __new__(meta, name, bases, dct, **kwargs): 
     # swallow keyword arguments 
     return super().__new__(meta, name, bases, dct) 

    def __init__(cls, name, bases, dct, **kwargs): 
     # swallow keyword arguments 
     pass 

class NoNameErrorsInBody(metaclass=DefaultDictMeta, default=2): 
    x = y + 3 # y is not defined 


>>> NoNameErrorsInBody.x 
5 

Il va sans dire que vous ne devriez jamais faire dans le code du monde réel. Je ne fais que le démontrer pour m'amuser un peu. (Imaginez que vous essayez de déboguer du code qui ne vous dit pas quand vous utilisez un nom invalide, ce serait horrible, genre, je ne sais pas, Javascript ou quelque chose comme ça.)