Calling Python class without creating instance explicitly

Hey, guys.

I have defined ui elements of my tool in a class. Right now I don’t instantiate the class explicitly and just call the class this way:

import my_module
my_module.UI()

this works without a problem, though I want to know if doing this is considered correct or not.

Thanks

The answer here is “It depends, but it’s dangerous”. There’s a good chance you probably should keep the instance around, though.


First, the “Danger” part. The danger of doing something like this is python’s garbage collection can come through and delete the memory that controls your ui. Basically, even though the UI is using the button, Python could say “Well, since I don’t see anything using this memory, I can just delete it.” Then when the UI goes to do something with the button, it can look for something that isn’t there, and the program would crash.

If you ever look into how the C code for python works, there is a thing you have to deal with called “Reference Counting”. The reference count is a number that keeps track of how many variables are referencing a piece of data. If you don’t know what that is, definitely google it. There are some good write-ups online. If you still need more help after that, feel free to ask. The thing you need to know, however, is that when the reference count reaches 0, python’s garbage collector can (but not necessarily immediately) come through and free up the memory for that data.

So, In your line my_module.UI(), what technically happens under the hood is: A temporary instance of your class gets created, and the reference counter is increased to 1. Then, because that instance isn’t assigned to anything, the reference counter drops back down to 0, so that means it’ll get garbage collected at some point. Garbage collection for a class will go through and delete every variable stored in that class (ie. anything you stored using self like self.myButton = ButtonObject()) .


Now for the “It Depends” part.
How you define the class, how you use self variables, and which UI system you use all influence how your UI data is stored, and how it’s reference counts change. You could be doing something that will absolutely work (maya cmds stores the data outside of python), or you could have written a crash waiting to happen (Qt/pyside keeps the data inside python, but some references stick around). Without more information (or, even better, the code) I can’t tell you which yours is.

3 Likes

hey. thank you for a detailed answer. I got it.
my script is too big to share here, but essentially:
the class UI() doesn’t only hold ui elements, but all the methods I’ll need to pass the instance variables to. Basically I decided to use class so that I could use variables in different methods freely. I several buttons on the ui that use methods, create new instance variables for use later. One example would be selecting an index color in the ui creates an instance variable and assigns this color to it. This color will be used for the next created curve.

Sorry if I can’t explain much better.

As I understand assigning an instance to a variable is always better. So what could be a clean way to create it then calling it? I’ve seen people creating functions like this:

def launch(*args):
inst = UI()

A github gist is a good place to put a massive chunk of code if you want to share. Or you could build a smaller setup with the same pattern.

Also, what UI toolkit are you using?

I also wouldn’t want to share the code publicly. I can send you the code privately if you don’t mind.
I use standard maya.cmds, no toolkit.

No worries about the code then.

By using maya.cmds, there’s no actual UI data being stored in python. All of the data is stored in maya, and python just tells maya what to do with it. Because of this, I don’t think there’s much danger of your UI getting garbage collected out from under you and causing a crash. That said, it’s still a good idea to keep that instance around.

I really don’t like global variables, but this is one case where I use them

# Put this in its own file
# For this example I'm saying this file is called ui_starter.py
import my_module
MY_UI = None
def launch(*args):
    global MY_UI
    MY_UI = my_module.UI()

Now you can do something like this:

import ui_starter
ui_starter.launch() # Launch the UI
print ui_starter.MY_UI # You can access this anywhere you've imported ui_starter
1 Like

What is the reason that we are doing this in a new file?

You said your script was too big, so I was assuming you already had multiple files. You can absolutely set things up with the same idea, all in one big file. Personally, I just like to keep the UI separate from the launch process. But as long as you’re making that launch function and global variable, you can put the code wherever you want.

Alternatively, you could run it directly from the shelf with something like this:

import my_module
MY_UI = my_module.UI()

Then, I THINK that shelf buttons are run in the __main__ namespace (somebody correct me if I’m wrong), so if you wanted to access your UI from some other script, you could do something like this:

import __main__
print __main__.MY_UI

okay, I just wanted to make sure there are no technical reasons for this.
I’m still not good at technicalities of Python. Can you tell me what

print ui_starter.MY_UI

would give me?
For me it prints the memory address. How can this be useful?

There is no technical reason for doing a print there. I’m just demonstrating that you can access your UI class from anywhere. That way you could call methods on it or whatever. I could have just as easily written

ui_starter.MY_UI.someMethod() # I have access to MY_UI here

And you definitely don’t need to access your ui from outside, it’s just good to know how for later when you need to do a quick one-off automation, or you start doing unit tests, or stuff like that.

got it. thank you, tfox!

Very nice approach. I do something similar in my command line scripts. I would import my module, sometimes empty, just to store variables to be passed around. When the code gets large I found it’s easier to remember what you have defined this way.