1

I'm working on a simple Pygame project, and in the options menu I have a couple of toggle boxes for switching the sounds and animations off. The boolean variables for these are stored in a separate Settings class.

Although it's not really necessary, I thought I'd try to make a ToggleButton class that takes the following arguments:

ToggleButton(game_instance, boolean, text, xpos, ypos, width, height)

I've given it a draw() method and a toggle() method. I'd want to pass in my self.settings.sounds_on or self.settings.animations_on booleans from my main game program, so that switching the toggle box on and off would switch the sounds and animations on and off.

In testing this, I've made two instances of my ToggleButton class:

ToggleButton(self, self.game_instance, self.settings.sounds_on, "Sounds", 50, 100, 25, 25)
ToggleButton(self, self.game_instance, self.settings.animations_on, "Animations", 50, 150, 25, 25)

The toggle boxes draw the screen, and they can be interacted with.

Image of toggle boxes on screen

However, while the toggle() method changes the value of the boolean inside the instance of the class, it doesn't seem to be connecting to the original boolean that was passed into the instance as an argument (I hope I'm using the correct terminology here, I'm still learning). So while the appearance of toggle box changes depending on whether the boolean is True or False, it doesn't actually have any effect at all on the settings for sound and animations.

Here is a link to the code for the button, and also a short example program that just draws a single toggle box to the screen and each time it's clicked, it prints the status of the original boolean, and the boolean inside the instance. You can clearly see that the original boolean is always False, whereas the self.boolean parameter of the toggle box changes.

https://github.com/ElJuanito82/ToggleBox/blob/main/example

So it seems that when the button is instantiated, it takes a copy of the boolean value in its current state, rather than creating a direct link so that when one changes so does the other. Do I have the right idea here?

Is there something I'm missing here that will allow this to work?

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
ElJuanito
  • 13
  • 2
  • 1
    What do you expect? The argument is just an input argument. Python has no concept of in-out arguments. Related: [Is there a way in Python to return a value via an output parameter?](https://stackoverflow.com/questions/4702249/is-there-a-way-in-python-to-return-a-value-via-an-output-parameter) – Rabbid76 Nov 19 '20 at 19:33
  • I'm still trying to get my head around Object Oriented Programming. I thought the argument is an object, so that by modifying it in one place, it would amend it in another place, like how appending to a copy of a list also modifies the original list. I was expecting that passing in self.settings.sounds_on would refer to a memory location where the boolean is stored, so that by amending it in the instance, it would amend the original. – ElJuanito Nov 19 '20 at 19:42
  • Yes it is, but the argument (formal parameter) `boolean` is just a reference to the object. When you do `ToggleButton(..., self.settings.sounds_on, ...)`, the object which is reference by `self.settings.sounds_on` (actual parameter) is assigned to `boolean`. Now `boolean` refers to the same object as `self.settings.sounds_on`. When you do `boolean = True` (inside the class), `boolean` refers to `True`. But this doesn't effect `self.settings.sounds_on`. – Rabbid76 Nov 19 '20 at 19:46
  • OK, it's coming together now. Thanks for your insight. I've had a rethink and removed the boolean argument and added an active parameter to the ToggleBox class whose effect is purely on the appearance of the box, and I've added a separate toggle() method to my main game file. Clicking the box now runs both self.toggle_button.toggle() and self.toggle_sounds() to change the appearance of the box and switch the sounds boolean. It's not as elegant as I'd have liked, but it does the job. https://github.com/ElJuanito82/ToggleBox/blob/main/example2 – ElJuanito Nov 19 '20 at 20:04

1 Answers1

0

The boolean argument is passed by value, it's not possible to modify it this way. Probably the best explanation is this Q&A set - How do I pass a variable by reference? . Quite a few "tutorials" are incorrect on the details of this, one even saying "everything is passed by reference" - this is wrong. Generally basic types are passed by value - booleans, numbers, tuples, strings. Most other things like objects and lists are passed by reference - which means these can be changed in a function.

Consider this python session:

Python 3.8.5 (default, Jul 28 2020, 12:59:40) 
>>> x = True
>>> def f( boolval ):
...     boolval = False
... 
>>> f(x)
>>> x
True  # <-- Still the original value

x does not change because it is passed as a value. Now consider the same thing, but using a list parameter:

>>> y = [ 'apple', 'banana', 'cherry' ]
>>> def f2( fruit_list ):
...     fruit_list.append( 'durian' )
... 
>>> f2( y )
>>> y
['apple', 'banana', 'cherry', 'durian']   # <-- Changed!

Here a list is passed as a reference, so it can be modified in the function. If you're planning on coding like this, it's important to know which types are mutable, and which aren't.

So for your ToggleButton, obviously now we know the boolean parameter is immutable, and thus not going to be changed. But you can, and do change the value inside the object. So add helper-functions to set & query it.

This is common way of implementing this sort of functionality in an Object Orientated GUI control, rather than interrogating the member variable directly.

For example:

class ToggleButton:

    def __init__(self, game_instance, boolean, text, xpos, ypos, width, height):
        """Initialise the toggle box attributes."""
        self.game_instance = game_instance
        self.boolean = boolean
        # [...]

    def isToggled( self ):
        """ Return True if the toggle is selected """
        return self.boolean

    def setToggled( self, value ):
        """ Set the state of the Toggle """
        self.boolean = value
        ### TODO: cause a re-draw, iff necessary

Aside: boolean is not really a good name for a variable, since it is not descriptive. Something like .is_set, .checked, or .selected might be better since it describes what the content represents.

Kingsley
  • 14,398
  • 5
  • 31
  • 53
  • 1
    Thank you, this is very clear. So in this example, my **self.settings.sounds_on** variable is rendered completely obsolete, and can be replaced by **self.sounds_toggle_button.isToggled()** in the rest of my program? That is exactly the kind of elegant solution I was looking for! – ElJuanito Nov 19 '20 at 20:40
  • @ElJuanito - Yes, that sounds ideal. – Kingsley Nov 19 '20 at 20:42