47 votes

Est-il toujours prudent de modifier le dictionnaire `** kwargs`?

À l'aide de la fonction Python syntaxe def f(**kwargs), dans la fonction d'un argument mot-clé dictionnaire kwargs est créé, et les dictionnaires sont mutables, donc la question est, si je modifie l' kwargs dictionnaire, est-il possible que je puisse avoir des effets en dehors de la portée de ma fonction?

De ma compréhension de la façon dont le dictionnaire du déballage et de l'argument mot-clé emballage fonctionne, je ne vois pas de raison de croire qu'il peut être dangereux, et il me semble qu'il n'y a pas de danger de ce en Python 3.6:

def f(**kwargs):
    kwargs['demo'] = 9

if __name__ == '__main__':
    demo = 4
    f(demo=demo)
    print(demo)     # 4

    kwargs = {}
    f(**kwargs)
    print(kwargs)   # {}

    kwargs['demo'] = 4
    f(**kwargs)
    print(kwargs)    # {'demo': 4}

Cependant, est-ce spécifique à l'implémentation, ou fait-il partie de l'Python spec? Suis-je le surplombant de toute situation ou de mise en oeuvre (sous réserve des modifications à des arguments qui sont eux-mêmes mutable, comme kwargs['somelist'].append(3)) ce genre de modification peut être un problème?

55voto

user2722968 Points 2421

Il est toujours plus sûr. Comme la spécification dit

Si la forme "**identifiant" est présent, il est initialisé à une nouvelle commandé cartographie de la réception de tout excès de mot-clé arguments, par défaut un nouveau vide cartographie du même type.

L'emphase est ajoutée.

Vous êtes toujours assuré d'obtenir un nouveau mappage objet-à l'intérieur de la appelable. Voir cet exemple

def f(**kwargs):
    print((id(kwargs), kwargs))

kwargs = {'foo': 'bar'}
print(id(kwargs))
# 140185018984344
f(**kwargs)
# (140185036822856, {'foo': 'bar'})

Ainsi, si l' f peut modifier un objet qui est passé par **, il ne peut pas modifier l'appelant **-objet lui-même.


Mise à jour: Depuis que vous avez demandé à propos de cas de coin, voici une spéciale en enfer pour vous qui n'en fait modifier l'appelant kwargs:

def f(**kwargs):
    kwargs['recursive!']['recursive!'] = 'Look ma, recursive!'

kwargs = {}
kwargs['recursive!'] = kwargs
f(**kwargs)
assert kwargs['recursive!'] == 'Look ma, recursive!'

Ce que vous ne verrez probablement pas dans la nature, cependant.

13voto

user2357112 Points 37737

Pour Python au niveau du code, l' kwargs dict l'intérieur d'une fonction sera toujours un nouveau dict.

Pour C les extensions, même si, méfiez-vous. L'API C de la version de kwargs parfois passer un dict par directement. Dans les versions précédentes, il serait même passer dict sous-classes à travers directement, menant à la bogue (maintenant résolu) où

'{a}'.format(**collections.defaultdict(int))

produirait '0' , au lieu de lever une KeyError.

Si jamais vous avez à écrire C extensions, y compris éventuellement Cython, n'essayez pas de modifier l' kwargs équivalent, et regarder dehors pour les dict sous-classes sur les anciennes versions de Python.

2voto

Nick Sweeting Points 152

Les deux réponses ci-dessus sont correctes en indiquant que, techniquement, la mutation d' kwargs ne sera jamais à avoir un effet sur les étendues parents.

Mais... ce n'est pas la fin de l'histoire. Il est possible qu'une référence à l' kwargs à l'extérieur de la portée de la fonction, et puis vous avez tous l'habitude partagée muté état des problèmes que vous attendez.

def create_classes(**kwargs):

    class Class1:
        def __init__(self):
            self.options = kwargs

    class Class2:
        def __init__(self):
            self.options = kwargs

    return (Class1, Class2)

Class1, Class2 = create_classes(a=1, b=2)

a = Class1()
b = Class2()

a.options['c'] = 3

print(b.options)
# {'a': 1, 'b': 2, 'c': 3}
# other class's options are mutated because we forgot to copy kwargs

Techniquement, cela répond à votre question, car le partage d'une référence à l' mutable kwargs ne conduisent à des effets en dehors de la portée de la fonction de l'.

J'ai été mordu à plusieurs reprises par cette dans le code de production, et c'est quelque chose que j'ai choisi de regarder pour l'instant, à la fois dans mon propre code et lors de l'examen des autres. L'erreur est évidente dans mon artificiel exemple ci-dessus, mais il est beaucoup plus sournoises dans le code réel, lors de la création de l'usine de funcs qui partagent des options communes.

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