6

I'm new in python and I'm having many troubles in using global instruction.
Here is a code example:

mouse = "a"
background = "b"

list_ab = [mouse, background]

def func ():
    for item in list_ab:
        global item  # I want to modify the GLOBAL item, which is mouse
                     # and background.
        item = "modified"
    print mouse  # must be "modified" not "a"
    print background  # must be "modified" not "b"

This is the problem. How can I solve it?

martineau
  • 119,623
  • 25
  • 170
  • 301
User
  • 63
  • 1
  • 1
  • 4
  • `list_ab` does not contain references to `mouse` and `background`, it has copies of their values when it was created -- so modifications to them in the list only change the list. – martineau Nov 14 '10 at 23:18
  • @martineau, more than that, `for item in list_ab` creates a whole new layer of bindings that insulates even the list elements from change. – aaronasterling Nov 14 '10 at 23:21
  • @aaronasterling: You're right of course, but I tried to put it in terms that someone who thought Python had references in it like C++ would understand. – martineau Nov 14 '10 at 23:39
  • @martineau, just my two cents, and I do tend to be a militant about these things, but that's a good way to make sure that they keep not understanding. Sink or swim baby, sink or swim. There is no try. – aaronasterling Nov 14 '10 at 23:41
  • 1
    @aaronasterling: FWIW, I didn't just make a comment, (like you) I also submitted an answer. Giving them too much information all at once has the potential of losing them completely -- except for the more diligent -- and (IMHO) with smaller doses that's less likely since it's provided in more digestible chunks. So I limited what I said to first pointing-out what I thought was the root issue. – martineau Nov 15 '10 at 01:21

6 Answers6

7

Your problem is that Python works not the way you think.
So I will try to explain what's going on in your code, line by line.

mouse = "a"

Assigns the string "a" to the name mouse.

background = "b"

Assigns the string "b" to the name background.

list_ab = [mouse, background]

Assigns two objects, referenced by names mouse and background, to the list list_ab.
As we already know, they are constants "a" and "b". So you can just write:

list_ab = ["a", "b"]

Now the loop

for item in list_ab:

assigns each object in the list to the name item.
For the first loop iteration, it means item = "a".

The line

    global item # I want to modify the GLOBAL item, which is mouse ad background

doesn't make sense, because it tries to tell Python that the name item is global, while there is no such global variable declared.

And for the most confusing behavior on the line

    item = "modified"

you should just understand that while it assigns the string "modified" to the name item, strings "a" and "b" are still the same, and still assigned to the list list_ab (and names mouse and background, which you didn't touch either).

The name item itself lives only in the scope where it was declared, when the "for" loop ends, it's destructed. It is also reassigned every iteration with the next item from the list_ab.

To sum it up:

If you want to assign "modified" string to the items in the list, do it directly:

list_ab[0] = "modified"
list_ab[1] = "modified"

If you need to change variable declared in the global scope, do this:

mouse = "a"

def func():
    global mouse   # tell Python that you want to work with global variable
    mouse = "modified"

func()
print mouse # now mouse is "modified"

Example based on the first revision of the question:

Instead of

background = g_base("bg.jpg") # g_base class instance
mouse = g_base("mouse.png",alpha=1) # g_base class instance

imgs_to_load = [mouse, background]

def Image_loader (img):
    # ..... code to load the image and convert it into pygame surface...
    return img_load

def main ():
    for image in img_to_load:
        global image
        image = Image_loader (img)
        print image # if I print image, it is a pygame surface

    print background # But here, outside the loop, image is still a g_base instance ?!?!
    print mouse # " "   

main()

You can do:

imgs_to_load = [g_base("bg.jpg"), g_base("mouse.png",alpha=1)] # list with a g_base class instances

def Image_loader(img):
    # ..... code to load the image and convert it into pygame surface...
    return img_load

def main ():
    imgs_in_pygame_format = [] # create an empty list
    for image in imgs_to_load:
        loaded_image = Image_loader(image) 
        imgs_in_pygame_format.append(loaded_image) # add to the list 

    for image in imgs_in_pygame_format:
        print image # every image in the list is a pygame surface

    # or
    print image[0]
    print image[1]

main()

Or if you want to reference to images by name, you can put them into dictionary:

imgs_to_load = {} 
imgs_to_load["bg"] = g_base("bg.jpg")
imgs_to_load["mouse"] = g_base("mouse.png",alpha=1)

imgs_in_pygame_format = {} # create a global dictionary for loaded images

def main ():
    global imgs_in_pygame_format # import that global name into the local scope for write access
    for name, data in imgs_to_load.items():
        imgs_in_pygame_format[name] = Image_loader(data) # add to the dictionary

    for image in imgs_in_pygame_format:
        print image # every image in the dictionary is a pygame surface

    # or    
    print imgs_in_pygame_format["bg"]
    print imgs_in_pygame_format["mouse"]

main()

But most importantly, global variables are bad idea. The better solution would be to use a class:

def Image_loader(img):
    # ..... code to load the image and convert it into pygame surface...
    return img_load

class Image:
    def __init__(self, image):
        self.data = Image_loader(image)

def main ():
    bg = Image(g_base("bg.jpg"))
    mouse = Image(g_base("mouse.png",alpha=1))

    print bg.data
    print mouse.data

main()

For more examples see Python Tutorial.

Hope it helps, and welcome to StackOverflow!

kolobos
  • 908
  • 7
  • 12
  • The fact that strings are immutable has nothing to do with this. It would work exactly the same way if `item` was a list. – aaronasterling Nov 15 '10 at 00:04
  • Very clear, thanks. I 've had this problem in pygame. If I don't know how many grafic_elements I'll need, every time I add a new grafic to load I have to declare it global manually. I thought the for loop + global could solve the problem, but I've understood it can't. There is no way to do this task for every new graphic I add? – User Nov 15 '10 at 18:45
  • Thank you again. But this doesn't solve my problem... :( I don't know why but pygame seems not to accept bg.data or something linked to a class, infact returns "argument 1 must be pygame.Surface, not instance", which is very strange because bg.data is Surface... strange ! – User Nov 15 '10 at 22:41
  • @User: Probably the name "Image" conflicts with some of the pygame internals. Try to rename the class and variables. And you can also load images into dictionary, see the example above the class one. – kolobos Nov 15 '10 at 23:08
  • Done, it was my fault. There were two similar expressions and I changed only the first, but the error was in the second, which was "screen.blit (background, (0,0))" insted of background.data. Now all runs correctly. Thank you very much for your help. Thank you everybody too, bye bye! – User Nov 16 '10 at 13:50
  • "The `name` item itself lives only in the scope where it was declared, when the "for" loop ends, it's destructed." - no, it can still be accessed when the for loop ends, because of `global item`. There is no need for item to exist before you `global` it. – Brecht Machiels Sep 16 '16 at 10:00
5

As I said in my comment to your question, list_ab does not contain references to mouse and background. You can simulate that to some degree in this case by putting their names in the list, like this:

mouse = "a"
background = "b"

list_ab = ['mouse', 'background']

def func():
    for name in list_ab:
        globals()[name] = "modified"
    print mouse # must be "modified" not "a"
    print background # must be "modified" not "b"

func()
# modified
# modified

globals() returns a dictionary-like object that represents the non-local name bindings at that point in your script/modules's execution. It's important to note that list_ab is not changed by the for loop in func(). Also you should be aware that this is only an illustration of one simple way to make your sample code work like you wanted, not of an especially good or generic way to accomplish such things.

martineau
  • 119,623
  • 25
  • 170
  • 301
2

I am not sure what you are trying do to. The global keyword is intended to import a global name into the current scope, not to make a local name global.

Björn Pollex
  • 75,346
  • 28
  • 201
  • 283
  • Thanks for the sudden answer. I want the function "Image_loader" to be able to load images from a list "imgs_to_load" and assign, for every item of the list, name = imageloaded. Wait, I'm a little confusing. I'll post the code... Ops I cannot post it, it's too long... – User Nov 14 '10 at 22:41
  • I'll try to explain better. Function image loader take "mouse" from the list imgs_to_load, processes it and assign "mouse = mouse_processed". But this name lives only inside for loop, not in Main. Infact mouse in Main is still g_base("mouse.png",alpha=1) – User Nov 14 '10 at 22:49
2

You are having two distinct problems and one of them occurs twice. The first is that you are attempting to use global on elements of a list which is useless. You can already do:

def func():
    list_ab[0] = 'modified'
    list_ab[1] = 'modified'

This will change the values which are referenced by list_ab which is further than your code is getting now. It works because you are not changing the binding represented by list_ab and so it doesn't need to be global. You can already read a global indexing and into it is just an item lookup and doesn't (in itself) overwrite any bindings. However it will not actually change which values are referenced by a and b

The second is that when you bind the first and second indices of list_ab to a and b, it creates entirely new bindings so that changing the values of the bindings in the list does nothing to change the values referenced by a and b. If you want to do that, you need to do it directly.

def func():
    global a, b
    a = 'modified'
    b = 'modified'

You are running into the second problem again when you try to modify the elements of list_ab by iterating over them. You can see it more clearly with the code

def func():
    list_ab = ['a', 'b']
    for item in list_ab:
        item = 'modified'
    print list_ab        # prints ['a', 'b']

Again, this occurs because you are creating a new binding to the value and then modifying that binding instead of the original one.

If you need to modify a list in place while iterating over it you can do

def func():
    list_ab = ['a', 'b']
    for i in xrange(len(list_ab)): 
        list_ab[i] = 'modified'

So as things currently stand to update a, b and list_ab, and keeping your current patterns (i.e. not introducing comprehensions), you need to do:

def func():
    global a, b
    for i in xrange(len(list_ab)):
        list_ab[i] = 'modified'
    a = 'modified'
    b = 'modified'
    print list_ab
    print a, b

That looks pretty ugly. Why do you need to keep them as free variables and in a list? Isn't just a list good enough? If you need to access them by name then you can use a dictionary:

dict_ab = {'a': 'a', 'b': 'b'}
for key in dict_ab:
    dict_ab[key] = modified

print dict_ab['a']
print dict_ab['b']
aaronasterling
  • 68,820
  • 20
  • 127
  • 125
  • FWIW, the OP doesn't have variables referenced by the names `a` and `b`. There _are_ some variables with the string values of `"a"` and `"b"` (and `"modified"`)—and these differences (and similarities) makes it unnecessarily difficult to follow your answer and easily grasp the points being made. – martineau Oct 06 '17 at 17:48
1

The issue here is that you're trying to update variables in-place, but your functions return new instances. Try something like this:

def main ():
    converted_images = []
    for image in [mouse, background]:
        converted_images.append(Image_loader(img))
    # now re-assign the pygame surfaces to the old names
    background, mouse = converted_images

And if you want to be especially concise, here's a one-liner that does the same thing:

background, mouse = [Image_loader(img) for img in [background, mouse]]

But really, if you only have two images, it might make more sense to do this explicitly:

background = Image_loader(background)
mouse = Image_loader(mouse)
perimosocordiae
  • 17,287
  • 14
  • 60
  • 76
  • Sorry for the previous code, it was useless to explain my problem. I've done a new clear example – User Nov 14 '10 at 23:07
0

I'm not sure what you are trying to do either, but here's an example of using global:

value = 7

def display():
    print value # global values are read-only any time

def increment():
    global value # must declare global to write it
    value += 1

def local():
    value = 9 # this is a local value
    print value

increment() # changes global value to 8
display()   # displays it
local()     # changes local value to 9 and displays it
print value # displays global value (still 8)
Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251