Page MenuHome

Python: Change type of deferred property definitions from tuple to type.
AbandonedPublic

Authored by Jacques Lucke (JacquesLucke) on Sep 2 2020, 4:14 PM.

Details

Reviewers
None
Summary

Blender's use of annotations is a bit unusual in the Python world.
Currently, this is not a problem, but it will be in Python 3.10.

The way annotations work is different in Python 3.10. The new behaviors
can be activated with from __future__ import annotations in the first
source code line.

The specific issue can be seen when running the following code snippet:

from __future__ import annotations
import bpy
from bpy.props import StringProperty

class MyOperator(bpy.types.Operator):
    bl_idname = "object.my_operator"
    bl_label = "My Operator"
    
    my_prop: StringProperty()
    
    def execute(self, context):
        return {'FINISHED'}
    
import typing
hints = typing.get_type_hints(MyOperator)
print(hints)
Traceback (most recent call last):
  File "/home/jacques/Downloads/future_annotations.blend/Text", line 15, in <module>
  File "/home/jacques/blender-git/build_linux/bin/2.91/python/lib/python3.7/typing.py", line 978, in get_type_hints
    value = _eval_type(value, base_globals, localns)
  File "/home/jacques/blender-git/build_linux/bin/2.91/python/lib/python3.7/typing.py", line 263, in _eval_type
    return t._evaluate(globalns, localns)
  File "/home/jacques/blender-git/build_linux/bin/2.91/python/lib/python3.7/typing.py", line 469, in _evaluate
    is_argument=self.__forward_is_argument__)
  File "/home/jacques/blender-git/build_linux/bin/2.91/python/lib/python3.7/typing.py", line 142, in _type_check
    raise TypeError(f"{msg} Got {arg!r:.100}.")
TypeError: Forward references must evaluate to types. Got (<built-in function StringProperty>, {}).

The typing.get_type_hints function expects that all annotations have
types on the right side. However, currently StringProperty() returns
a tuple (bpy.props.StringProperty, {}), which is not a type.

This patch implements changes the return type of StringProperty() to
be a new type. Obviously, this is a bit hacky and it's kind of a bummer
that this change is necessary. This is my first attempt on solving this,
so maybe there are better solutions.

Fixing this is not very urgent but should be done in time. It should
be noted that fixing this will break some third party addons, but the fixes
should be relatively simple.

Also see:

Diff Detail

Repository
rB Blender
Branch
property-definitions (branched from master)
Build Status
Buildable 9934
Build 9934: arc lint + arc unit

Event Timeline

Jacques Lucke (JacquesLucke) requested review of this revision.Sep 2 2020, 4:14 PM
Jacques Lucke (JacquesLucke) created this revision.

@Campbell Barton (campbellbarton) Can you think of a better way to fix this? Do you even think this should be fixed?

I know you are not a big fan of automatic class registration in addons (which probably is the main thing that might break in some addons with this change). However, I still use a script to automatically register all classes in some of my addons and don't really plan to stop using that unless I really have to (there are hundreds of things that need to be registered in hundreds of files; making the registration explicit adds nothing but overhead and makes extending e.g. Animation Nodes more difficult). Auto-registration scripts need access to the type used in pointer and collection properties to register classes them in the right order.

I'm wary of using types, mainly because creating classes is a heavier operation.

By default we register over 1100 types, with most of the addons enabled, over 6000.

A quick comparison of timing:

 > python3 -m timeit 'for i in range(10000): type("BpyProperty", (), {"func": None, "kw": {}})'
5 loops, best of 5: 89.7 msec per loop
 > python3 -m timeit 'for i in range(10000): ("BpyProperty", (), {"func": None, "kw": {}})'
200 loops, best of 5: 1.47 msec per loop

While it's not likely to be a bottleneck, it's over 60x slower.


typing.get_type_hints accepts callable objects, why not define our own type that stores the values and is callable?

typing.get_type_hints accepts callable objects, why not define our own type that stores the values and is callable?

That might work as well, I'll look into it.