PacktPublishing / Python-Scripting-in-Blender

Python Scripting in Blender, published by Packt
MIT License
38 stars 16 forks source link

comparing objects with is and == #6

Open Andrej730 opened 1 year ago

Andrej730 commented 1 year ago

Noticed that in the snippet below objects are compared using is, not ==.

Though the exact code works wanted to put it out there that it used to lead to issues. It's seems better now but haven't seen Blender mentioning anywhere that it was resolved completely so it could be still more safe to use == to make sure it's the same object, not is.

https://github.com/PacktPublishing/Python-Scripting-in-Blender/blob/a9aabf003078baa311c8d25890702ef4e6fe9f33/ch2/0500_understanding_user_context.py#L44-L48

pKrime commented 1 year ago

Hi, thanks for pointing that out and apologies for coming back so late: I wanted to write a proper reply. I also apologise in case you are already knowledgeable about the subject and find my explanation redundant.

The difference between the two statements is that == evaluates to True when both sides have the same value, is evaluates to True when both sides are the same thing. A quick example

>>> a = "Hello"
>>> b = "Hello World"
>>> a + " World" == b
True

>>> a + " World" is b
False

But as you wrote, blender 2.78 presents this weird behavior

>>> C.object == C.object
True

>>> C.object is C.object
False

That happens because those python objects are non-unique containers. The actual blender objects are part of the C core app instead. The bpy module consists of "wrappers" that do the following:

Wrappers are written C++ and have access to blender internal entities, but they are derived from python C++ classes so they can be accessed in python scripts.

When we type C.object in the console and press enter, blender takes the active object, puts it inside a python box and gives it to the interpreter. Now it should be clear how come the two results used to differ. When asked if

>>> C.object == C.object

python is faced with a box on the left and a box on the right. Will look in one box and then in the other: "hey they contain the same C object, so these boxes are equivalent". It returns True.

But confronted with

>>> C.object is C.object

python doesn't bother to look inside: it's either the same box or not.

The function id() returns the memory address of a python object. Asking for the memory address of C.object repeatedly in blender 2.79 gives these results (every time different numbers, not the same as in this example)

>>> id(C.object)
1955119776328

>>> id(C.object)
1955119776136

>>> id(C.object)
1955119776904

Those would fail the is condition, because it's a different box every time, stored at a different memory address.

At a certain release, perhaps 2.8, they improved that policy: blender keeps returning the same box. Asking for the id repeatedly in blender 3.x returns a consistent memory address (you will get a different number)

>>> id(C.object)
2300011633600

>>> id(C.object)
2300011633600

>>> id(C.object)
2300011633600

I could not find the exact change in the source, but I have talked with the developers and they assured me that, from blender 3 on, the identity between selected_object[i] and C.object holds.

I admit that using identity in my example would have required such explanation, and in the hindsight I should have rather used ==.

Now I would rather not change the accompanying code in order not to confuse the reader, but I'll mention the alternative in a comment. In the meantime, I hope that this explanation can be somehow beneficial

Kind Regards, Paolo

Andrej730 commented 1 year ago

Yeah, makes sense, thank you. Some benefit of is that it could be slightly faster too since it's just comparing python objects ids instead of looking up original objects. But it's probably barely noticeable.