I’m working on a nice little tool for project management in Autodesk Maya. It has a few drop down menus that contain folders and files inside a project root directory. This tool automates a few steps and helps the user quickly switch from one project to another without browsing for files.
While working on this tool, I ran into an annoying scope issue with my algorithm when iterating over a loop, that prevented me from assigning the value I wished to a variable. I wanted to track an index value for each drop down menu, and instead I got the same value for all menus.
I then remembered reading on a very similar issue on my favourite JavaScript book series — You Don’t Know JS by Kyle Simposon. In his book Scope & Closures, he explains that while assigning a variable to a function from inside of a loop captures a copy of that variable, it is still sharing the same scope. This means that each function call inside my loop refers to a copy of the variable that eventually points to the same value. But I write my tool in Python and not JS, as it is the language used in Maya for writing scripts.
From my experience in visual effects, problems are usually similar across different platforms, and the solutions are usually similar as well. But can this bit of wisdom serve me in the world of code? Not surprisingly, the answer is yes! I figured if this scope issue happens in JavaScript, it might as well happen in Python.
Simpson’s solution in Scope & Closures to this problem was to pass that variable to the same function, but enclosing it inside an IIFE. Now you can assign that variable to a local variable inside the IIFE (which is inside the loop). The local variable now holds a copy of the value for each iteration and passes that value to the function and it does not change after the loop iterates again and again.
In Python, as far as I can tell, there are no IIFEs and no blocks of code, like in JavaScript. To solve this issue, I just enclosed the code inside the loop in a new method and called this method within the loop. This new method essentially serves as an inner scope, similar to JavaScript’s IIFE, which “locks” the value I require and pass it down safely to the method inside.
This is basically it! Following below is a detailed explanation of how I implemented this in my code.
My script has a main class that contains all the methods and variables for the tool.
The first method is initUI(), which creates a window and adds some buttons and the dropdown menus (Maya’s cmds.optionMenu()) and stores them in a dictionary called self.menus.
The cmds.optionMenu() function in line 6 above takes a label for the dropdown menu, which is “Menu”+str(i). The cc attribute passes a callback event handler that executes once a user selects an item from the menu. To pass in additional custom parameters, I use a lambda function and call my event handler self.indexFolders() within it.
The important part here is the variable i that is passed to every optionMenu’s indexFolders() method. It holds the dropdown menu’s index, the variable I wish to use later when I want to update the content of my menus.
As it is now, once the script executes, every dropdown menu’s indexFolders() has i passed in each iteration of the loop, but eventually every indexFolders() holds the value i had at the last iteration of the loop, instead of the value i has at the time of declaration. This means that every time I select an item from any optionMenu object, I get the same value (in this case, 3), instead of an index from 0 to 3 for each menu respectively.
In Scopes & Enclosures, Simpson suggests enclosing the body of the loop in a block {} or an IIFE. The solution I found in Python was to put my code in another function and call it within the loop, like this:
The createMenu() method has the exact line of code as in the previous snippet’s line 6. I just changed the name of the variable from the loop’s global i to the method’s local level. Now I call createMenu() from within my for loop and assign it i every time, and createMenu() assigns it’s local level to the event handler with the value I intended it to have — the index for the loop.
Now each optionMenu object has a different value, based on it’s index, and I can use that value to know exactly which menus to update!
Learning JavaScript is great, and it’s even better when the knowledge I acquire helps me indirectly solving problems with other languages and establishing a deeper understanding of coding and computer science.
Top comments (0)