web-dev-qa-db-fra.com

Emulation du comportement par valeur en python

Je voudrais émuler le comportement de passage par valeur en python. En d'autres termes, je voudrais absolument m'assurer que la fonction que j'écris ne modifie pas les données fournies par l'utilisateur. 

Une solution possible consiste à utiliser la copie en profondeur:

from copy import deepcopy
def f(data):
    data = deepcopy(data)
    #do stuff

existe-t-il un moyen plus efficace ou plus Pythonic pour atteindre cet objectif, en faisant le moins de suppositions possibles à propos de l'objet transmis (comme la méthode .clone ())?

Modifier  

Je suis conscient que techniquement tout en python est passé par valeur. J'étais intéressé par émuler le comportement, c'est-à-dire en veillant à ne pas déranger les données transmises à la fonction. Je suppose que la méthode la plus générale consiste à cloner l'objet en question avec son propre mécanisme de clonage ou avec une copie en profondeur.

52
Boris Gorelik

Il n'y a pas de moyen pythonique de le faire. 

Python fournit très peu de fonctionnalités pour faire respecter des éléments tels que les données privées ou en lecture seule. La philosophie Pythonic est que "nous sommes tous des adultes consentants": dans ce cas, cela signifie que "la fonction ne doit pas changer les données" fait partie de la spécification mais n'est pas appliquée dans le code. 


Si vous souhaitez effectuer une copie des données, la solution la plus proche possible est votre solution. Mais copy.deepcopy, en plus d’être inefficace, présente également des mises en garde telles que :

Parce que la copie en profondeur copie tout ce qu’elle peut copier trop, par exemple des structures de données administratives qui devraient être partagées même entre les copies.

[...]

Ce module ne copie pas les types tels que module, méthode, trace de pile, cadre de pile, fichier, socket, fenêtre, tableau ou autres types similaires. 

Je ne le recommanderais donc que si vous savez que vous utilisez des types Python intégrés ou vos propres objets (où vous pouvez personnaliser le comportement de copie en définissant les méthodes spéciales __copy__/__deepcopy__, il n'est pas nécessaire de définir votre propre méthode clone(). ).

29
dF.

Vous pouvez faire un décorateur et mettre le comportement de clonage dans cela. 

>>> def passbyval(func):
def new(*args):
    cargs = [deepcopy(arg) for arg in args]
    return func(*cargs)
return new

>>> @passbyval
def myfunc(a):
    print a

>>> myfunc(20)
20

Ce n'est pas le moyen le plus robuste, et ne gère pas les arguments clé-valeur ou les méthodes de classe (absence d'argument de soi), mais vous obtenez l'image.

Notez que les déclarations suivantes sont égales:

@somedecorator
def func1(): pass
# ... same as ...
def func2(): pass
func2 = somedecorator(func2)

Vous pouvez même demander au décorateur de prendre une sorte de fonction qui effectue le clonage, permettant ainsi à son utilisateur de décider de la stratégie de clonage. Dans ce cas, le décorateur est probablement mieux implémenté en tant que classe avec __call__ remplacé.

29
Skurmedel

Il n'y a que quelques types intégrés qui fonctionnent en tant que références, comme list, par exemple.

Ainsi, pour moi, la méthode Pythonic pour effectuer une analyse passe-par-valeur, pour list, dans cet exemple, serait:

list1 = [0,1,2,3,4]
list2 = list1[:]

list1[:] crée une nouvelle instance de la liste1 et vous pouvez l'affecter à une nouvelle variable.

Vous pourriez peut-être écrire une fonction pouvant recevoir un argument, puis vérifier son type et, en conséquence, effectuer une opération intégrée pouvant renvoyer une nouvelle instance de l'argument transmis.

Comme je l'ai dit plus tôt, il n'y a que quelques types intégrés, dont le comportement est semblable à celui des références, listes dans cet exemple.

De toute façon ... espérons que cela aide.

8
user695800

habituellement, lorsque vous transmettez des données à une API externe, vous pouvez assurer l'intégrité de vos données en les transmettant en tant qu'objet immuable, par exemple pour envelopper vos données dans un Tuple. Cela ne peut pas être modifié, si c'est ce que vous avez essayé d'empêcher par votre code.

3
Tom

Je ne peux pas trouver d'autre Pythonic option. Mais personnellement, je préférerais le plusOOmoyen.

class TheData(object):
  def clone(self):  """return the cloned"""

def f(data):
  #do stuff

def caller():
  d = TheData()
  f(d.clone())
2
Sake

Bien que je sois sûr qu’il n’y ait pas vraiment de moyen Pythonic de le faire, je pense que le module pickle vous donnera des copies de tout ce que vous considérez comme une valeur. 

import pickle

def f(data):
    data = pickle.loads(pickle.dumps((data)))
    #do stuff

Suite à user695800's answer, transmettez valeur par liste pour les listes possibles avec l'opérateur [:]

def listCopy(l):
    l[1] = 5
    for i in l:
        print i

appelé avec 

In [12]: list1 = [1,2,3,4]

In [13]: listCopy(list1[:])
1
5
3
4

list1
Out[14]: [1, 2, 3, 4]
0
ejectamenta