2

I have a question regarding variable linking between objects. Because I'm learning Python programming by myself, I may have bad habits…

I am programming a module which will save me a lot of labor by managing by itself the standard configparser module. I just have to pass to it as an argument, the list of the application's variables, and just tell it "load()" and "save()" to let it do all the job automatically (create new config file, load values into variables, check updates, etc…). It works perfectly with "true objects" like tkinter variables, but with variables containing simple numeric or string value, the value only is copied, not the object's "address".

Here's an example, it is an extremely simplified script focusing the problematic part but keeps the architecture of my module:

class Sauce:
    """Consider this class a Python standard module, so don't modify it."""
    def __init__(self, ingredient):
        self.ingredient = ingredient

    def set(self, result):
        self.ingredient = result

    def get(self):
        return self.ingredient

class Heater:
    def __init__(self, ingredients_list):
        self.ingredients_list = ingredients_list

    def heat(self):
        for item in self.ingredients_list:
            if item['type'] == 'sauce':
                item['ingredient'].set('caramel')
            elif item['type'] == 'cereal':
                item['ingredient'] = 'popcorn'

class Cooking:
    def __init__(self):
        self.cereal = 'corn'
        self.sauce = Sauce('sugarAndWater')
        print('We will cook these ingredients: {0} and {1}'.format(self.cereal, self.sauce.get()))
        print('After cooking we should theorically get "popcorn" and "caramel"')

        self.heater = Heater([{'type':'cereal', 'ingredient':self.cereal}, {'type':'sauce', 'ingredient':self.sauce}])
        self.heater.heat()

        print('But in reality we\'ve got:', self.cereal, self.sauce.get())

if __name__ == '__main__':
    start = Cooking()

There are now the questions:

  1. Is it ok to directly transfer objects as an argument (self.sauce) to be modified by the instanced object (self.heater)?
  2. If 1 is yes, is there any trick to make a common variable (self.cereal) to work as expected?
agf
  • 171,228
  • 44
  • 289
  • 238
Bogey Jammer
  • 638
  • 2
  • 8
  • 23
  • 1
    In answer to 1), you're not "transferring" the object so much as passing the object as an argument and changing its reference count. self.heater is free to modify it, and changes made will be reflected in its original scope if the object is mutable. When you say you want to make common variables work as expected, do you mean you want to pass in an int instance, modify it in a function, and see that change in original scope? If so, you're running into a problem with the (im)mutability of some objects and might consider writing a wrapper class to get around it if you really want the behavior. – BenTrofatter Aug 12 '11 at 21:54
  • 1
    This related question: http://stackoverflow.com/questions/986006/python-how-do-i-pass-a-variable-by-reference has a pretty lengthy explanation. – Rob Marrowstone Aug 12 '11 at 22:01
  • @OlduvaiHand Sorry, I badly translated my thoughts, I just meant something like «how to get the expected result for the a variable like self.cereal» Hoons, Thanks for the link – Bogey Jammer Aug 12 '11 at 22:07

3 Answers3

0

I really didn't looked your code, but the common way to achieve this is using a list(or another mutable container) as a wrapper.

>>> def a(n):
...     n = 5
... 
>>> def b(n):
...     n[0] = 5
... 
>>> k = 3
>>> a(k)
>>> k
3
>>> k = [3]
>>> b(k)
>>> k[0]
5
utdemir
  • 26,532
  • 10
  • 62
  • 81
0

As you suspected, numbers and strings are passed by value, so they cannot be modified from other functions / methods. The easiest way to pass a value to another function for modification is to put it in a container -- a list or a dictionary -- and pass that container instead.

michel-slm
  • 9,438
  • 3
  • 32
  • 31
  • 5
    All objects in Python are passed the same way, the type doesn't enter into it. But numbers and strings are *immutable*, which means the called function can't change the object, so the effect is similar to other languages' call-by-value. – Ned Batchelder Aug 12 '11 at 21:57
0

The issue you have is not with arguments being passed as values. In Python, arguments are passed by reference, so start.heater.ingredients_list[1]["ingredient"] is start.sauce.

The actual issue is with the assumption that item['ingredient'].set('caramel') is altering the value of start.sauce. It is not, it is setting start.heater.ingredients_list[1]["ingredient"] at the address of the string "caramel".

Long story short, you are doing this

a = "sugarAndWater"  # Put "sugarAndWater" in memory and assign its address to a
b = a                # Assign a to b, so b is the address of "sugarAndWater"
b = "caramel"        # Put "caramel" in memory and assign its address to b, don't alter a

A classical construct to avoid that is to do

a = ["sugarAndWater"]
b = a
b[0] = "sauce"

which is a bit verbose, but remember that functions should have as few side-effects as possible.

This explanation is already on the internet, on a great article that talked about "boxes" and was a lot more inspired than this. If someone remembers where it was...

Edit : Here it is, in Code Like a Pythonista

Evpok
  • 4,273
  • 3
  • 34
  • 46