Decorators and Polling - Have I Got This Right?

I spent most of the morning today digging for information on BP decorators and this is the understanding I’ve come to (please correct me if I’ve got it wrong):

First, a Python concept…
Decorators wrap functions so you, as a coder, can have something happen before and/or after the function is called without having to mess with changing the function itself.

Now, the BP concept…
The @classmethod decorator wraps any function so it will be called before its class’s execute() method.

EDIT:
Okay, I just found out @classmethod is a Python thing, not BP. But what I read also said it’s rarely used in classes, but BP code seems to use it in classes a lot. Also, the mention I found of this didn’t distinguish the difference between classmethod and staticmethod. Is staticmethod similar to singletons in other OOP languages?
END EDIT:

The poll() method is used to make sure the class is being called while the user has Blender set to an appropriate mode such as Edit Mode, Object Mode, etc.

So far, so good?

I tried to find a list of BP decorators and what they’re used for, but didn’t find anything. I Googled, I dug into the API, and I scoured this forum. Most of what I’ve written above I gleaned from postings by batFINGER, CoDEmanX and tmaes.

Is there comprehensive documentation on this stuff? If so, would someone kindly post a link?

Hi rontarrant,

The @classmethod decorator makes the method specific to the class, rather than an instance of the class. The first argument of a class method will refer to the class rather than the instance. It’s good practice to distinguish this by



@classmethod
def poll(<b>cls</b>, context):
    .....

def execute(self, context):
    .....

Blender uses this to poll against the class rather than having to instance it.

From the console

The object delete operator class, the poll method is available to the class.



&gt;&gt;&gt; type(bpy.ops.object.delete)
&lt;class 'bpy.ops.BPyOpsSubModOp'&gt;

&gt;&gt;&gt; bpy.ops.object.delete.poll
&lt;bound method BPyOpsSubModOp.poll of # Delete selected objects
bpy.ops.object.delete(use_global=False)&gt;




&gt;&gt;&gt; bpy.ops.object.delete.poll()
True

Ok I now know I have the correct context to invoke or execute this operator.

Notice the execute method is not available directly from the class.




&gt;&gt;&gt; bpy.ops.object.delete.execute
Traceback (most recent call last):
  File "&lt;blender_console&gt;", line 1, in &lt;module&gt;
AttributeError: 'BPyOpsSubModOp' object has no attribute 'execute'

And lastly if we create an instance, the invoke execute draw etc methods are available.


&gt;&gt;&gt; bpy.ops.object.delete()
{'FINISHED'}




Wow, that’s a lot to absorb. Thanks very much for the reply, batFINGER.

I’m working for the next two days, so I’m going to have to put off going further with this until Monday. Sorry. I will get back to you on this for sure, though. I think I will definitely have more questions. :slight_smile:

Okay, I had some time this morning to pore over this and I think I got it. I’d like to put this in my own words and if you (or someone else) could tell me whether I’ve got the concepts, I’d appreciate it…

Using @classmethod is a way of saying: This method is available BEFORE the class is instanced.

On the other hand, execute() is only available AFTER the class is instanced.

cls is to a class what self is to an instance of a class. It’s the implicit passing of the class itself as the first argument to a method just as self is the implicit passing of the instance of the class.

Since Python is typeless, poll() is used to make sure you’ve got the right class for the job at hand before trying to do it, kind of like making sure you’ve got a Philips head screwdriver before trying to screw in a Philips head screw.

Does all that sound accurate?

And, BTW, thanks for such a comprehensive answer!

Now, as for use…

I have a situation where I’ve created a class that stores and keeps track of meshes of a particular type. Would I, before instantiating the class, use @classmethod to make sure I’m:

  • dealing with a mesh, and
  • the mesh has the particular characteristics needed by the functions in the class.

Once the mesh has passed those checks, I can safely instantiate the class.

The other use-case I can think of (as illustrated by batFINGER) is to make sure a class has a method or function I’m hoping to use.

that really sounds like a static method in other OOP languages?!

like

a_class::method_call_without_class_instance()

in C++ right?

so is @classmethod identical to @staticmethod ? I bet there is still a difference…

Here’s a comparison between the two: @staticmethod vs. @classmethod.

My understanding is that these are the differences between the two is:
@staticmethod doesn’t get the class as a first argument.
@classmethod DOES get the class as a first argument.

@staticmethod is outmoded, mainly because it was a sort-of stopgap between old-style functions and new-style (whatever that means). I didn’t look into it any further because, frankly, I have enough trouble wrapping my head around some of this stuff without trying to understand an older version of Python that I’ll never use.

A call to @staticmethod expects an argument list that doesn’t include the class it’s part of and it simply returns the method and nothing more.

A call to @classmethod expects the class as its first argument and returns a function that’s implicitly part of the class its part of, which I assume means it also has direct access to that class’s data.

That’s the best I can come up with for now.

hm ok, makes sense.

@classmethod is really for class-methods, while usual methods are class-instance-methods.

Usual methods got “self” as first argument (mandatory),
classmethods got “cls” instead, so the first parameter is always a class reference.
staticmethods don’t have self/cls, so it may have 0 arguments.

I guess you could use a classmethod if you got a class with a class-variable, which you wanna manipulate from outside the class. You could do that like MyClass.var = 1337, but if you wanna restrict the var values, you could add a setter classmethod:

class MyClass:
    var = 1
    @classmethod
    def var_inc(cls):
        if 0 &lt; cls.var &lt; 10:
            cls.var += 1

MyClass.var_inc()
print(MyClass.var)

You could do the same with @statismethod, but you would have to specify the class name:

class MyClass:
    var = 1
    @staticmethod
    def var_inc():
        if 0 &lt; MyClass.var &lt; 10:
            MyClass.var += 1

MyClass.var_inc()
print(MyClass.var)

Ideasman42 uses @staticmethod for static draw handler functions, which is required to remove them on unregister(). But I wonder why he did not use @classmethod, as you don’t have to write the full class name inside of the class for these kinds of methods… Maybe it’s not possible for derived classes / mixed-in classes (different cls arg)?

That looks right to me.

Hopefully Ideasman42 will drop by and tell us. My guess would be sort of along the lines of yours: because the draw handler functions are static and can’t be inherited by a dynamic class, even if said class can be manipulated without being instantiated.

If Ideasman42 doesn’t drop by to share, this will definitely require further study.