Comment créer un nombre variable de variables?

364

Comment réaliser des variables variables en Python?

Voici une entrée manuelle détaillée, par exemple: Variables variables

J'ai entendu dire que c'est une mauvaise idée en général cependant, et c'est un trou de sécurité en Python. Est-ce vrai?

Taryn
la source
39
ce sont les aspects de maintenance et de débogage qui provoquent l'horreur. Imaginez-vous essayer de savoir où la variable «foo» a changé alors qu'il n'y a aucun endroit dans votre code où vous changez réellement «foo». Imaginez plus loin que c'est le code de quelqu'un d'autre que vous devez maintenir ... OK, vous pouvez maintenant vous rendre à votre bonheur.
glenn jackman
4
Un autre écueil qui n'a pas été mentionné jusqu'à présent est si une telle variable créée dynamiquement a le même nom qu'une variable utilisée dans votre logique. Vous ouvrez essentiellement votre logiciel en otage à l'entrée qui lui est donnée.
holdenweb
Toutes les réponses ici supposent que vous avez accès aux variables de base auxquelles vous souhaitez accéder dynamiquement par nom, ce qui n'est pas toujours le cas. Je pense que l'approche la plus générale pour reproduire l'exemple de comportement en PHP est d'utiliser eval () comme ceci: var_name = 'foo'; bar = 5; output = eval (var_name)
Luis Vazquez
1
Vous pouvez modifier vos variables globales et locales en accédant aux dictionnaires sous-jacents pour elles; c'est une idée horrible du point de vue de la maintenance ... mais cela peut être fait via globals (). update () et locals (). update () (ou en enregistrant la référence dict de l'un ou l'autre et en l'utilisant comme n'importe quel autre dictionnaire ). NON RECOMMANDÉ ... mais vous devez savoir que c'est possible.
Jim Dennis
(J'ajoute ce commentaire car une question similaire, demandant plus spécifiquement ce type de manipulation a été fermée avec un pointeur sur cette question "assez proche". Je veux que ceux qui suivent la question fermée obtiennent leur réponse).
Jim Dennis

Réponses:

297

Vous pouvez utiliser des dictionnaires pour accomplir cela. Les dictionnaires sont des magasins de clés et de valeurs.

>>> dct = {'x': 1, 'y': 2, 'z': 3}
>>> dct
{'y': 2, 'x': 1, 'z': 3}
>>> dct["y"]
2

Vous pouvez utiliser des noms de clé de variable pour obtenir l'effet des variables de variable sans risque de sécurité.

>>> x = "spam"
>>> z = {x: "eggs"}
>>> z["spam"]
'eggs'

Pour les cas où vous songez à faire quelque chose comme

var1 = 'foo'
var2 = 'bar'
var3 = 'baz'
...

une liste peut être plus appropriée qu'un dict. Une liste représente une séquence ordonnée d'objets, avec des indices entiers:

lst = ['foo', 'bar', 'baz']
print(lst[1])           # prints bar, because indices start at 0
lst.append('potatoes')  # lst is now ['foo', 'bar', 'baz', 'potatoes']

Pour les séquences ordonnées, les listes sont plus pratiques que dicts avec les touches entières, car les listes itération prennent en charge pour l'index, le découpage , appendet d' autres opérations qui requièrent une gestion clé maladroite avec un dict.

ggorlen
la source
88

Utilisez la getattrfonction intégrée pour obtenir un attribut sur un objet par son nom. Modifiez le nom selon vos besoins.

obj.spam = 'eggs'
name = 'spam'
getattr(obj, name)  # returns 'eggs'
SilentGhost
la source
70

Ce n'est pas une bonne idée. Si vous accédez à une variable globale, vous pouvez l'utiliser globals().

>>> a = 10
>>> globals()['a']
10

Si vous souhaitez accéder à une variable dans la portée locale, vous pouvez l'utiliser locals(), mais vous ne pouvez pas affecter de valeurs au dict renvoyé.

Une meilleure solution consiste à utiliser getattrou à stocker vos variables dans un dictionnaire, puis à y accéder par nom.

Nadia Alramli
la source
locals (). update ({'new_local_var': 'some local value'}) fonctionne très bien pour moi dans Python 3.7.6; donc je ne suis pas sûr de ce que vous voulez dire lorsque vous dites que vous ne pouvez pas lui attribuer de valeurs.
Jim Dennis
Étant donné x = "foo"et en locals()["x"] = "bar"utilisant print xdonne la sortie barpour Jython 2.5.2. Cela a été testé avec un script d'automatisation à la demande in maximo .
Preacher
37

Chaque fois que vous souhaitez utiliser des variables variables, il est probablement préférable d'utiliser un dictionnaire. Donc au lieu d'écrire

$foo = "bar"
$$foo = "baz"

vous écrivez

mydict = {}
foo = "bar"
mydict[foo] = "baz"

De cette façon, vous n'écraserez pas accidentellement des variables existantes (ce qui est l'aspect sécurité) et vous pouvez avoir différents "espaces de noms".

sepp2k
la source
34

Les nouveaux codeurs écrivent parfois du code comme ceci:

my_calculator.button_0 = tkinter.Button(root, text=0)
my_calculator.button_1 = tkinter.Button(root, text=1)
my_calculator.button_2 = tkinter.Button(root, text=2)
...

Le codeur se retrouve alors avec une pile de variables nommées, avec un effort de codage de O ( m * n ), où m est le nombre de variables nommées et n est le nombre de fois que ce groupe de variables doit être accédé (y compris la création ). Le débutant le plus astucieux observe que la seule différence dans chacune de ces lignes est un nombre qui change en fonction d'une règle, et décide d'utiliser une boucle. Cependant, ils se retrouvent bloqués sur la façon de créer dynamiquement ces noms de variables et peuvent essayer quelque chose comme ceci:

for i in range(10):
    my_calculator.('button_%d' % i) = tkinter.Button(root, text=i)

Ils constatent rapidement que cela ne fonctionne pas.

Si le programme nécessite des «noms» de variables arbitraires, un dictionnaire est le meilleur choix, comme expliqué dans d'autres réponses. Cependant, si vous essayez simplement de créer de nombreuses variables et que cela ne vous dérange pas de vous y référer avec une séquence d'entiers, vous recherchez probablement un list. Cela est particulièrement vrai si vos données sont homogènes, telles que les relevés de température quotidiens, les scores de quiz hebdomadaires ou une grille de widgets graphiques.

Cela peut être assemblé comme suit:

my_calculator.buttons = []
for i in range(10):
    my_calculator.buttons.append(tkinter.Button(root, text=i))

Cela listpeut également être créé en une seule ligne avec une compréhension:

my_calculator.buttons = [tkinter.Button(root, text=i) for i in range(10)]

Dans les deux cas, le résultat est un remplissage list, avec le premier élément accédé avec my_calculator.buttons[0], le suivant avec my_calculator.buttons[1], et ainsi de suite. Le nom de la variable "base" devient le nom du listet l'identifiant variable est utilisé pour y accéder.

Enfin, n'oubliez pas d'autres structures de données, telles que le set- c'est similaire à un dictionnaire, sauf que chaque "nom" n'a pas de valeur attachée. Si vous avez simplement besoin d'un "sac" d'objets, cela peut être un excellent choix. Au lieu de quelque chose comme ça:

keyword_1 = 'apple'
keyword_2 = 'banana'

if query == keyword_1 or query == keyword_2:
    print('Match.')

Vous aurez ceci:

keywords = {'apple', 'banana'}
if query in keywords:
    print('Match.')

Utilisez a listpour une séquence d'objets similaires, a setpour un sac d'objets commandés arbitrairement ou a dictpour un sac de noms avec des valeurs associées.

TigerhawkT3
la source
12

Au lieu d'un dictionnaire, vous pouvez également utiliser à namedtuplepartir du module collections, ce qui facilite l'accès.

Par exemple:

# using dictionary
variables = {}
variables["first"] = 34
variables["second"] = 45
print(variables["first"], variables["second"])

# using namedtuple
Variables = namedtuple('Variables', ['first', 'second'])
vars = Variables(34, 45)
print(vars.first, vars.second)
ojas mohril
la source
10

Si vous ne voulez utiliser aucun objet, vous pouvez toujours utiliser à l' setattr()intérieur de votre module actuel:

import sys
current_module = module = sys.modules[__name__]  # i.e the "file" where your code is written
setattr(current_module, 'variable_name', 15)  # 15 is the value you assign to the var
print(variable_name)  # >>> 15, created from a string
Guillaume Lebreton
la source
Celui-ci me semble mieux que d'utiliser "exec".
fralau
__dict__Cependant, cela ne fonctionne pas avec la variable. Je me demande s'il existe un mécanisme général pour créer une dynamique variable globale.
Alexey
globals()peut le faire
Guillaume Lebreton
9

La SimpleNamespaceclasse peut être utilisée pour créer de nouveaux attributs avec setattr, ou sous SimpleNamespace- classe et créer votre propre fonction pour ajouter de nouveaux noms d'attribut (variables).

from types import SimpleNamespace

variables = {"b":"B","c":"C"}
a = SimpleNamespace(**variables)
setattr(a,"g","G")
a.g = "G+"
something = a.a
Bill Oldroyd
la source
6

Je réponds à la question: comment obtenir la valeur d'une variable étant donné son nom dans une chaîne? qui est fermé en double avec un lien vers cette question.

Si les variables en question font partie d'un objet ( une partie d'une classe par exemple) , alors certaines fonctions utiles pour atteindre ce sont exactement hasattr, getattret setattr.

Ainsi, par exemple, vous pouvez avoir:

class Variables(object):
    def __init__(self):
        self.foo = "initial_variable"
    def create_new_var(self,name,value):
        setattr(self,name,value)
    def get_var(self,name):
        if hasattr(self,name):
            return getattr(self,name)
        else:
            raise("Class does not have a variable named: "+name)

Ensuite, vous pouvez faire:

v = Variables()
v.get_var("foo")

"variable_initiale"

v.create_new_var(v.foo,"is actually not initial")
v.initial_variable

"n'est en fait pas initial"

patapouf_ai
la source
6

Utilisation globals()

Vous pouvez réellement affecter des variables à la portée globale de façon dynamique, par exemple, si vous voulez 10 variables accessibles sur une portée globale i_1, i_2... i_10:

for i in range(10):
    globals()['i_{}'.format(i)] = 'a'

Cela affectera «a» à toutes ces 10 variables, bien sûr, vous pouvez également modifier la valeur dynamiquement. Toutes ces variables sont accessibles maintenant comme les autres variables déclarées globalement:

>>> i_5
'a'
Rocky Li
la source
4

Vous devez utiliser la globals()méthode intégrée pour obtenir ce comportement:

def var_of_var(k, v):
    globals()[k] = v

print variable_name # NameError: name 'variable_name' is not defined
some_name = 'variable_name'
globals()[some_name] = 123
print variable_name # 123

some_name = 'variable_name2'
var_of_var(some_name, 456)
print variable_name2 # 456
Andriy Ivaneyko
la source
3

Le consensus est d'utiliser un dictionnaire pour cela - voir les autres réponses. C'est une bonne idée pour la plupart des cas, cependant, il y a de nombreux aspects qui en découlent:

  • vous serez vous-même responsable de ce dictionnaire, y compris la collecte des ordures (des variables in-dict), etc.
  • il n'y a ni localité ni globalité pour les variables variables, cela dépend de la globalité du dictionnaire
  • si vous voulez renommer un nom de variable, vous devrez le faire manuellement
  • cependant, vous êtes beaucoup plus flexible, par exemple
    • vous pouvez décider d'écraser les variables existantes ou ...
    • ... choisissez d'implémenter des variables const
    • pour lever une exception sur l'écrasement pour différents types
    • etc.

Cela dit, j'ai implémenté un gestionnaire de variables variables -class qui fournit certaines des idées ci-dessus. Cela fonctionne pour python 2 et 3.

Vous utiliseriez la classe comme ceci:

from variableVariablesManager import VariableVariablesManager

myVars = VariableVariablesManager()
myVars['test'] = 25
print(myVars['test'])

# define a const variable
myVars.defineConstVariable('myconst', 13)
try:
    myVars['myconst'] = 14 # <- this raises an error, since 'myconst' must not be changed
    print("not allowed")
except AttributeError as e:
    pass

# rename a variable
myVars.renameVariable('myconst', 'myconstOther')

# preserve locality
def testLocalVar():
    myVars = VariableVariablesManager()
    myVars['test'] = 13
    print("inside function myVars['test']:", myVars['test'])
testLocalVar()
print("outside function myVars['test']:", myVars['test'])

# define a global variable
myVars.defineGlobalVariable('globalVar', 12)
def testGlobalVar():
    myVars = VariableVariablesManager()
    print("inside function myVars['globalVar']:", myVars['globalVar'])
    myVars['globalVar'] = 13
    print("inside function myVars['globalVar'] (having been changed):", myVars['globalVar'])
testGlobalVar()
print("outside function myVars['globalVar']:", myVars['globalVar'])

Si vous souhaitez autoriser l'écrasement de variables de même type uniquement:

myVars = VariableVariablesManager(enforceSameTypeOnOverride = True)
myVars['test'] = 25
myVars['test'] = "Cat" # <- raises Exception (different type on overwriting)
DomTomCat
la source
À première vue, les longues importations camélisées m'ont fait penser que c'était Java.
markroxor
3

J'ai essayé les deux en python 3.7.3, vous pouvez utiliser soit globals () ou vars ()

>>> food #Error
>>> milkshake #Error
>>> food="bread"
>>> drink="milkshake"
>>> globals()[food] = "strawberry flavor"
>>> vars()[drink] = "chocolate flavor"
>>> bread
'strawberry flavor'
>>> milkshake
'chocolate flavor'
>>> globals()[drink]
'chocolate flavor'
>>> vars()[food]
'strawberry flavor'


Référence:
https://www.daniweb.com/programming/software-development/threads/111526/setting-a-string-as-a-variable-name#post548936

Hzzkygcs
la source
0

Tout ensemble de variables peut également être enveloppé dans une classe. Des variables "variables" peuvent être ajoutées à l'instance de classe pendant l'exécution en accédant directement au dictionnaire intégré via l'attribut __dict__.

Le code suivant définit la classe Variables, qui ajoute des variables (dans ce cas des attributs) à son instance pendant la construction. Les noms de variables sont tirés d'une liste spécifiée (qui, par exemple, aurait pu être générée par le code du programme):

# some list of variable names
L = ['a', 'b', 'c']

class Variables:
    def __init__(self, L):
        for item in L:
            self.__dict__[item] = 100

v = Variables(L)
print(v.a, v.b, v.c)
#will produce 100 100 100
ru13r
la source