Scoping problem

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Post Reply
Message
Author
Jake
Support Hero
Posts: 3826
Joined: Sat Jun 17, 2006 7:28 pm
Contact:

Scoping problem

#1 Post by Jake »

So, this is quite possibly a Jake-doesn't-remember-enough-about-Python problem, but I'm having some trouble with scoping that I don't get. Renpy 6.10.2e, I have something more or less like the following test case:

Code: Select all

init python:
    
    def loopedFunction():
        renpy.say(narrator, "list item: %(l)s" % {"l":myList[1]})
        renpy.say(narrator, "int: %(i)s" % {"i":myInt}) # A
        renpy.say(narrator, "bool: %(b)s" % {"b":myBool}) # B
        
        myInt = myInt - 1
        if myInt <= 0:
            myBool = False

label start:

    python:
        myList = ["a", "b", "c"]
        myBool = True
        myInt = 3
        
        while myBool:
            loopedFunction()
What I'm seeing is that when the code gets to the line with the comment '# A', I get an error "UnboundLocalError: local variable 'myInt' referenced before assignment".

The weird thing is that in my actual code, I get an error (effectively) on the line '# B', for much the same thing.

What I don't understand is why a list defined in one label/block/whatever is accessible from another place with no problems - the first thing you see if you run this code is a "b", as expected - but an int isn't. And why in my actual code, a list and and int are both accessible in a similar nesting of calls and blocks, but a bool isn't. I'm sure I'm probably just forgetting or ignorant of something, can anyone explain it?





...

Now, you want to know something else weird? If I change my code to this:

Code: Select all

init python:
    
    def myFunction():
        renpy.say(narrator, "list item: %(l)s" % {"l":myList[1]})
        renpy.say(narrator, "int: %(i)s" % {"i":myInt})
        renpy.say(narrator, "bool: %(b)s" % {"b":myBool})


label start:

    python:
        myList = ["a", "b", "c"]
        myBool = True
        myInt = 3
        
        myFunction()
then all three variables are accessible without error in myFunction and it all works as I'd expected it all to in the first place! Why should the use of the bool outside of the function affect it's visibility in the function? Why should it affect the visibility of the int that hasn't even been used outside the function?!
Server error: user 'Jake' not found

chronoluminaire
Eileen-Class Veteran
Posts: 1153
Joined: Mon Jul 07, 2003 4:57 pm
Completed: Elven Relations, Cloud Fairy, When I Rule The World
Tumblr: alextfish
Skype: alextfish
Location: Cambridge, UK
Contact:

Re: Scoping problem

#2 Post by chronoluminaire »

I think what's changing is that in your first example, you're changing myInt and myBool within the function loopedFunction. Python sees this and decides that they must be local variables.

Python's general policy on locals and globals is: if a function assigns to it, it must be local unless told otherwise; if a function doesn't assign to it, it must be global unless told otherwise.

Within Ren'Py, you can bypass this behaviour by referring to objects as properties of the store, as seen on pages like ui.adjustment and ui.jumpsoutofcontext.
I released 3 VNs, many moons ago: Elven Relations (IntRenAiMo 2007), When I Rule The World (NaNoRenO 2005), and Cloud Fairy (the Cute Light & Fluffy Project, 2009).
More recently I designed the board game Steam Works (published in 2015), available from a local gaming store near you!

Jake
Support Hero
Posts: 3826
Joined: Sat Jun 17, 2006 7:28 pm
Contact:

Re: Scoping problem

#3 Post by Jake »

chronoluminaire wrote: Python's general policy on locals and globals is: if a function assigns to it, it must be local unless told otherwise; if a function doesn't assign to it, it must be global unless told otherwise.
I see! I'll add this to my mental list of design decisions I don't like about Python, then...
chronoluminaire wrote: Within Ren'Py, you can bypass this behaviour by referring to objects as properties of the store
I'd considered this since writing the post, and it's on my list of things to try at lunchtime, but I wasn't holding out much hope due to the Store Variables page:
These are variables that are placed in the store (the default scope of python code)
(emphasis mine).

That kind of suggests that my Python variables are by-default already in the store - that there's no semantic difference between "store.myInt" and "myInt" in a python block - and explicitly referencing them through the store shouldn't make any difference. And I'm fairly sure that I've used variables in this capacity before, without having to explicitly reference them through the store.

I had forgotten about the 'global' statement, though, so I'll give that a go as well. It would be more convenient...
Server error: user 'Jake' not found

chronoluminaire
Eileen-Class Veteran
Posts: 1153
Joined: Mon Jul 07, 2003 4:57 pm
Completed: Elven Relations, Cloud Fairy, When I Rule The World
Tumblr: alextfish
Skype: alextfish
Location: Cambridge, UK
Contact:

Re: Scoping problem

#4 Post by chronoluminaire »

Jake wrote:I see! I'll add this to my mental list of design decisions I don't like about Python, then...
That's entirely fair! I'm not keen on it myself either, though I have seen a couple of arguments for it.
Jake wrote:That kind of suggests that my Python variables are by-default already in the store - that there's no semantic difference between "store.myInt" and "myInt" in a python block - and explicitly referencing them through the store shouldn't make any difference.
Mmm... this is only part accurate.
Your Python variables are by default already in the store, which means "store.myInt" and "myInt" in a python block refer to the same variable. But it's sadly/confusingly not quite true that there's no semantic difference. The first is able to get past scoping rules, basically as an alternative to using the global statement: semantically it's setting a property on a global object, which Python doesn't count as modifying that object, so it's happy to let that be a global. The second is treated as an assignment to a variable, so Python counts it as a local.

Yeah, this is one of the ugliest/most confusing bits of Python I've come across.
Jake wrote:I had forgotten about the 'global' statement, though, so I'll give that a go as well. It would be more convenient...
I think this is a perfectly viable alternative, if you'd prefer using global to accessing variables via the store.
I released 3 VNs, many moons ago: Elven Relations (IntRenAiMo 2007), When I Rule The World (NaNoRenO 2005), and Cloud Fairy (the Cute Light & Fluffy Project, 2009).
More recently I designed the board game Steam Works (published in 2015), available from a local gaming store near you!

Jake
Support Hero
Posts: 3826
Joined: Sat Jun 17, 2006 7:28 pm
Contact:

Re: Scoping problem

#5 Post by Jake »

chronoluminaire wrote:
Jake wrote:I see! I'll add this to my mental list of design decisions I don't like about Python, then...
That's entirely fair! I'm not keen on it myself either, though I have seen a couple of arguments for it.
To be honest, given that there's no explicit variable declaration which would make it clear which scope a name was supposed to be in, the language has to make a clean-cut decision one way or another... and if it had been decided the other way around, then I could potentially mess up my global variables by using a third-party bit of library code which hadn't carefully explicitly defined all of its locals as local. So I guess I can see why it works this way around.
Server error: user 'Jake' not found

Jake
Support Hero
Posts: 3826
Joined: Sat Jun 17, 2006 7:28 pm
Contact:

Re: Scoping problem

#6 Post by Jake »

chronoluminaire wrote: Mmm... this is only part accurate.
Your Python variables are by default already in the store, which means "store.myInt" and "myInt" in a python block refer to the same variable. But it's sadly/confusingly not quite true that there's no semantic difference.
Given the bit I quoted earlier, and the knowledge that Ren'Py already monkeys with bits of the Python script to (for example) replace my vanilla Python list with a Ren'Py-specific participates-in-rollback list (ReversibleList? Something like that) I'd presumed that it probably went through and replaced "variable" names with "store.variable" before handing it off to Python, or something similar, meaning it would work the same regardless of what I wrote.

Anyway, 'global' worked fine, so I'm happy for now. ;-)
Server error: user 'Jake' not found

chronoluminaire
Eileen-Class Veteran
Posts: 1153
Joined: Mon Jul 07, 2003 4:57 pm
Completed: Elven Relations, Cloud Fairy, When I Rule The World
Tumblr: alextfish
Skype: alextfish
Location: Cambridge, UK
Contact:

Re: Scoping problem

#7 Post by chronoluminaire »

Ah, yes, I can very much see how you'd think that. That's a more sensible mental model for whatever Ren'Py does than I have, in fact. I have no idea how Ren'Py works its magic on embedded Python.
I released 3 VNs, many moons ago: Elven Relations (IntRenAiMo 2007), When I Rule The World (NaNoRenO 2005), and Cloud Fairy (the Cute Light & Fluffy Project, 2009).
More recently I designed the board game Steam Works (published in 2015), available from a local gaming store near you!

Post Reply

Who is online

Users browsing this forum: FAST WebCrawler [Crawler]