web-dev-qa-db-fra.com

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

En utilisant la syntaxe de la fonction Python def f(**kwargs)]) _, un dictionnaire d'arguments mot-clé kwargs est créé et les dictionnaires sont mutables. La question est donc, si modifier le dictionnaire kwargs, est-il possible que j'aie un effet en dehors de la portée de ma fonction?

D'après ma compréhension du fonctionnement de la décompression du dictionnaire et de la compression des mots-clés, je ne vois aucune raison de penser que cela pourrait être dangereux, et il me semble qu'il n'y a aucun danger de ce type dans Python 3.6:

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

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

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

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

Cependant, cela est-il spécifique à l'implémentation, ou fait-il partie de la Python spec?)? Suis-je en train de négliger toute situation ou implémentation où (sauf modifications apportées aux arguments eux-mêmes mutable , comme kwargs['somelist'].append(3)), ce type de modification pourrait poser problème?

47
Paul

C'est toujours en sécurité. Comme le spec dit

Si le formulaire "** identificateur" est présent, il est initialisé à un nouveau mappage ordonné recevant tous les arguments de mot clé en excès, par défaut à un new mappage vide du même type.

Soulignement ajouté.

Vous êtes toujours assuré d'obtenir un nouvel objet de mappage dans l'appelable. Voir cet exemple

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

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

Ainsi, bien que f puisse modifier un objet transmis via **, Il ne peut pas modifier l'objet ** De l'appelant lui-même.


Update: Puisque vous avez posé des questions sur les coins, voici un enfer spécial qui modifie en fait le kwargs de l'appelant:

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.

55
user2722968

Pour le code de niveau Python, le dictkwargs d'une fonction sera toujours un nouveau dict.

Pour les extensions C , cependant, faites attention. La version API C de kwargs passe parfois un dict directement. Dans les versions précédentes, il transmettait même directement les sous-classes dict, conduisant au bogue ( maintenant corrigé ) où

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

produirait '0' au lieu de lever un KeyError.

Si vous devez éventuellement écrire des extensions C, y compris éventuellement Cython, n'essayez pas de modifier l'équivalent kwargs, et surveillez les sous-classes dict sur les anciennes Python versions.

13
user2357112

Les deux réponses ci-dessus ont raison de dire que techniquement, la mutation de kwargs n'aura jamais d'effet sur les portées des parents.

Mais ... ce n'est pas la fin de l'histoire. Il est possible qu'une référence à kwargs soit partagée en dehors de la portée de la fonction, puis que vous rencontriez toutes les données mutées habituelles partagées. problèmes d'état que vous 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 à mutable kwargs conduit à des effets extérieurs à ceux de la fonction.

J'ai été piqué à plusieurs reprises par ce code de production, et c'est quelque chose que je surveille explicitement pour l'instant, à la fois dans mon propre code et lors de l'examen des autres. L’erreur est évidente dans l’exemple ci-dessus, mais c’est beaucoup plus sournois dans le code réel lors de la création de fonctions d’usine qui partagent certaines options communes.

2
Nick Sweeting