76 votes

Comment un singe corrige-t-il une fonction en python?

Je ne parviens pas à remplacer une fonction d'un module différent par une autre et cela me rend fou.

Disons que j'ai un module bar.py qui ressemble à ceci:

 from a_package.baz import do_something_expensive

def a_function():
    print do_something_expensive()
 

Et j'ai un autre module qui ressemble à ceci:

 from bar import a_function
a_function()

from a_package.baz import do_something_expensive
do_something_expensive = lambda: 'Something really cheap.'
a_function()

import a_package.baz
a_package.baz.do_something_expensive = lambda: 'Something really cheap.'
a_function()
 

Je m'attendrais à obtenir les résultats:

 Something expensive!
Something really cheap.
Something really cheap.
 

Mais au lieu de cela je reçois ceci:

 Something expensive!
Something expensive!
Something expensive!
 

Qu'est-ce que je fais mal?

85voto

Nicholas Riley Points 26161

Il peut aider à penser de façon Python noms d'espaces de travail: ils sont essentiellement des dictionnaires. Donc, quand vous faites cela:

from a_package.baz import do_something_expensive
do_something_expensive = lambda: 'Something really cheap.'

pensez-y comme ça:

do_something_expensive = a_package.baz['do_something_expensive']
do_something_expensive = lambda: 'Something really cheap.'

J'espère que vous pouvez comprendre pourquoi cela ne fonctionne pas, alors :-) une Fois que vous importez un nom dans un espace de noms, la valeur du nom de l'espace de noms que vous avez importé à partir de n'est pas pertinent. Vous êtes seulement la modification de la valeur de do_something_expensive dans le module local de l'espace de noms, ou dans a_package.baz de l'espace de noms, ci-dessus. Mais parce que la barre des importations do_something_expensive directement, plutôt que de référencement du module de l'espace de noms, vous devez écrire à son espace de noms:

import bar
bar.do_something_expensive = lambda: 'Something really cheap.'

21voto

RyanWilcox Points 7838

Il y a un décorateur vraiment élégant pour ça: Guido van Rossum: Liste Python-Dev: Monkeypatching Idioms .

Il y a aussi le paquet dectools , pour lequel j'ai vu un PyCon 2010, qui pourrait également être utilisé dans ce contexte, mais qui pourrait en réalité aller dans l'autre sens (monkeypatching au niveau déclaratif de la méthode ... là où vous ne l'êtes pas)

4voto

Risadinha Points 1254

Si vous souhaitez appliquer un correctif uniquement à votre appel et laisser le code d'origine, vous pouvez utiliser https://docs.python.org/3/library/unittest.mock.html#patch (depuis Python 3.3):

 with patch('a_package.baz.do_something_expensive', new=lambda: 'Something really cheap.'):
    print do_something_expensive()
    # prints 'Something really cheap.'

print do_something_expensive()
# prints 'Something expensive!'
 

3voto

Mike Graham Points 22480

Dans le premier extrait, vous faites de l' bar.do_something_expensive se réfèrent à la fonction de l'objet qu' a_package.baz.do_something_expensive se réfère à ce moment-là. Pour vraiment "monkeypatch" que vous auriez besoin de changer la fonction elle-même (vous ne modifiez ce nom); c'est possible, mais vous n'avez pas réellement envie de le faire.

Dans vos tentatives pour modifier le comportement de l' a_function, vous avez fait deux choses:

  1. Dans la première tentative, vous faites do_something_expensive un nom global dans votre module. Cependant, vous appelez a_function, ce qui n'a pas l'air dans votre module pour résoudre les noms, il fait toujours référence à la même fonction.

  2. Dans le deuxième exemple, vous modifiez ce qu' a_package.baz.do_something_expensive se réfère, mais bar.do_something_expensive n'est pas comme par magie attachée à lui. Ce nom fait toujours référence à la fonction de l'objet, il leva les yeux quand il a été initilized.

Le plus simple, mais de loin-de-la solution idéale serait de changer de bar.py - à-dire

import a_package.baz

def a_function():
    print a_package.baz.do_something_expensive()

La solution est probablement l'une de deux choses:

  • Redéfinir a_function de prendre une fonction comme argument et d'appel qui, plutôt que d'essayer de se faufiler et de modification de quelle fonction il est codé en dur pour les consulter, ou
  • Magasin de la fonction pour être utilisé dans une instance d'une classe; c'est de cette façon que nous ne mutable état en Python.

À l'aide de variables globales (c'est ce que l'évolution au niveau du module des trucs à partir d'autres modules) est une mauvaise chose qui mène à désuète, confusion, untestestable, infranchissables code le flux de ce qui est difficile à suivre.

0voto

DavidG Points 82

do_something_expensive dans la fonction a_function () est simplement une variable dans l’espace de noms du module pointant vers un objet fonction. Lorsque vous redéfinissez le module, vous le faites dans un espace de noms différent.

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