Why are these two references to an object of type `bpy.types.Area` not the same despite the memory addresses being equal?

Today, I experimented with draw callbacks. In invoke(), I do this:

self.area = context.area
args = (self, context, event)
self.drawCallbackHandle = bpy.types.SpaceView3D.draw_handler_add(drawCallback, args, 'WINDOW', 'POST_PIXEL')

drawCallback():

def drawCallback(self, context, event):
    if context.area is not self.area:
        print(context.area, context.area.type, "is not", self.area, self.area.type)
        return
    # Draw something here

Now, disregard for a moment that the if-condition as is does not yet have its intended effect of opting out when I am going to draw in a 3D View which I am not interested in.

How the hell can this code produce the following output:

<bpy_struct, Area at 0x0000019E285213D8> VIEW_3D is not <bpy_struct, Area at 0x0000019E285213D8> VIEW_3D

Note that the memory addresses are equal, hence they should be the same object. What is the is keyword doing here? Is this some Python Foo?

(On another note, how do I know what 3D View I am going to draw into, in case the user has opened more than one?)

When you access an area through context you may be getting a new python object (as for why, I don’t know - blender’s context is a black box). I’m guessing the string you’re getting from print is the address for the c struct of that particular area. The is operator is strictly for testing if two references are one and the same python object, use equality or hash checks instead.

>>> area = context.area

>>> hex(id(area))
'0x28f54e69588'
>>> hex(id(bpy.context.area))
'0x28f54e6aa88'

>>> hex(area.as_pointer())
'0x28f53601218'
>>> hex(bpy.context.area.as_pointer())
'0x28f53601218'

>>> str(area)
'<bpy_struct, Area at 0x0000028F53601218>'  (same as pointer)

>>> hash(area)
-9223371860942126815
>>> hash(bpy.context.area)
-9223371860942126815

You can usually find the active area by using mouse coordinates and looping context.screen.areas, then use an equality check == in the draw function to tests against it.

Ah so they are overriding __str__() to print the address of the C struct. It’s really bad design to use == for an identity comparison, but it indeed seems to work here. Maybe an early Blender dev, having a strong C background, didn’t like the is keyword? Heh.

Regarding what I originally wanted to do… surprisingly, it does work as it is. The context and event objects passed to invoke() appear to get reused for calls of the drawback handler. Not for modal, however.

invoke(): 1401823615496 1401823755528
drawCallback(): 1401823615496 1401823755528
drawCallback(): 1401823615496 1401823755528
drawCallback(): 1401823615496 1401823755528
drawCallback(): 1401823615496 1401823755528
drawCallback(): 1401823615496 1401823755528
modal(): 1401829837640 1401823755912
drawCallback(): 1401823615496 1401823755528
drawCallback(): 1401823615496 1401823755528
modal(): 1401823816712 1401823813832

First number is id(context). Second number is id(event).

Can I take this as reliable behavior?

Yeah. Equality checks should work fine as long as an object’s __hash__ method is reliably implemented (seems to be the case). There’s likely something in the RNA definition that makes these checks work. From a performance perspective the is operator is faster since python is just grabbing a pointer, but it would require objects to be static from blender’s side.

For the draw callback, that’s expected because of python’s pass-by-object-reference.

Event structs are dormant when there are no active modals. There’s usually a few idle ones you can find at any time using the gc module.

Not sure why the modal is called back by different events, but a pattern that suggests a cyclic order. Could have something to do with workload distribution to ensure a modal doesn’t claim or occupy an event.

Subsequent different event ids in a modal. Note the pattern:

2814631062472
2814631012616
2814581127560
2814631062472
2814631012616
2814581127560
2814631062472
2814631012616
2814581127560
2814631062472
2814631012616

Events probably shouldn’t need to be checked against. You can ensure you always get the appropriate regional event by passing the operator instance to the draw function, then grab the event using self.event. Obviously you would need to store the event in the modal self.event = event.