Inheritance of Properties in Subclasses

In the spirit of EAFP (Easier to Ask Forgiveness than Permission), I’ve copied my post here from the “properties and inheritance” topic where it hadn’t gotten any replies. I’m new to the forum, so please forgive me if reposting a question breaks any rules.

I am part of a team working on a fairly complex Blender addon and we’d like to use inheritance in our design. Here’s one of the simplest “regular python” inheritance examples:

########################################################

class Base(object):
        s = "ABC"

class Child(Base):
        pass

class App:

        b = Base()
        c = Child()

        def print_self(self):
              print ( "App Members:" )
              print ( " b.s = " + self.b.s )
              print ( " c.s = " + self.c.s )

########################################################

if __name__ == "__main__":
        app = App()
        app.print_self()

This works fine and prints out:

App Members:b.s = ABC
c.s = ABC

In other words, the “Child” class inherits the string “s” from its parent (“Base”) as expected.

Here’s an attempt to do the same thing using Blender Properties (full addon included below):

########################################################

class Base(bpy.types.PropertyGroup):
        s = StringProperty(name="s",default="ABC")

class Child(Base):
        pass

class AppPropertyGroup(bpy.types.PropertyGroup):

       b = PointerProperty(name="b", type=Base)
       c = PointerProperty(name="c", type=Child)

       def print_self (self, context):
              print ( "App Members:" )
              print ( " b.s = " + self.b.s ) # OK
              print ( " c.s = " + self.c.s ) # Fails

########################################################

As the comments indicates, the reference to self.b.s works, but the reference to self.c.s fails. Here’s the output:

App Members:b.s = ABC
Traceback (most recent call last):
File “/home/user/.config/blender/2.68/scripts/addons/InheritBlender_forum.py”, line 45, in execute
context.scene.app.print_self(context)
File “/home/user/.config/blender/2.68/scripts/addons/InheritBlender_forum.py”, line 34, in print_self
print ( " c.s = " + self.c.s ) # Fails
TypeError: Can’t convert ‘tuple’ object to str implicitly

If I wrap self.c.s in the print statement with a string conversion ( str(self.c.s) ), it prints out this:

App Members:b.s = ABC
c.s = (<built-in function StringProperty>, {‘attr’: ‘s’, ‘default’: ‘ABC’, ‘name’: ‘s’})

It appears that using inheritance has somehow removed Blender’s ability to treat a StringProperty as a string (lost it’s “RNA” wrapper?). Is this a bug?

Finally, I apologize in advance that I’m relatively new to both Blender and Python, so please forgive any beginner misconceptions in my post … but please feel free to point them out!!

Full Addon Example:

"""
This program demonstrates a potential bug
in Blender's Property inheritance.
"""

bl_info = {
       "version": "0.1",
       "name": "Inherit Blender",
       "author": "BlenderHawk",
       "location": "Properties &gt; Scene",
       "category": "Blender Experiments"
   }

import bpy
from bpy.props import *

########################################################

class Base(bpy.types.PropertyGroup):
       s = StringProperty(name="s",default="ABC")

class Child(Base):
       pass

class AppPropertyGroup(bpy.types.PropertyGroup):

       b = PointerProperty(name="b", type=Base)
       c = PointerProperty(name="c", type=Child)

       def print_self (self, context):
              print ( "App Members:" )
              print ( " b.s = " + self.b.s ) # OK
              print ( " c.s = " + self.c.s ) # Fails

########################################################

class APP_OT_print_operator(bpy.types.Operator):
       bl_idname = "app.run_example"
       bl_label = "Run Example"
       bl_description = "Run Example"
       bl_options = {'REGISTER'}

       def execute(self, context):
             context.scene.app.print_self(context)
             return {'FINISHED'}

class APP_PT_Inheritance(bpy.types.Panel):
       bl_label = "Property Inheritance"
       bl_space_type = "PROPERTIES"
       bl_region_type = "WINDOW"
       bl_context = "scene"
       def draw(self, context):
              row = self.layout.row()
              row.operator("app.run_example", text="Run Example")

def register():
       print ("Registering ", __name__)
       bpy.utils.register_module(__name__)
       bpy.types.Scene.app = bpy.props.PointerProperty(type=AppPropertyGroup)

def unregister():
       print ("Unregistering ", __name__)
       del bpy.types.Scene.app
       bpy.utils.unregister_module(__name__)

if __name__ == "__main__":
       register()

Thanks in advance for any help.

The bpy.props are somewhat special, and don’t seem to support inheritance. I couldn’t solve the problem of parent props not being registered. Tried different class set-ups, eval/exec and function decorators. You could look deeper into how bpy.props are managed and see if there’s a manual way to register them in a child class. But I recommend you don’t use inheritance with properties. Shouldn’t be much of problem.

Do you think this might be considered a bug in the Python integration?

If so, can you suggest how to get it added to the appropriate bug list?

Thanks in advance.

Deffo not a bug, but missing feature, and would be rarely needed feature. You should overthink your approach, there might be better ways to represent your data.

It works if you use the base as pointerprop in the child btw:

import bpy
from bpy.props import *

class Base(bpy.types.PropertyGroup):
    s = StringProperty()

bpy.utils.register_class(Base)

class Child(bpy.types.PropertyGroup):    
    c = StringProperty()
    b = PointerProperty(type=Base)

bpy.utils.register_class(Child)

bpy.types.Scene.p = PointerProperty(type=Child)[/cde]

Thanks CoDEmanX for your effort in reading, understanding, recreating, and experimenting with this problem.

Thanks. That’s been our current work around. We include an instance of the Base in the Child, and we reference it using something like:

Child.Base.property

rather than referencing it directly:

Child.property

But that means that each time a subclass is created, it has to contain another instance of all of its base class properties (so the subclass of Child would also include the same property … again). If we use the same name for each included instance, maybe that will mask out - and not create? - the instances inherited from the parent classes of the same name (I’m not sure). But this seems to defeat the purpose of data inheritance.

If not a bug report … maybe a “feature request report”?

From: http://www.blender.org/documentation/blender_python_api_2_69_3/info_overview.html

Regarding inheritance, blender doesn’t impose restrictions on the kinds of class inheritance used, the registration checks will use attributes and functions defined in parent classes.

Based on that description, it’s looking more like a bug than a missing feature.

I believe what’s happening here is that the metaclass that wraps accessors around bpy.props.PropertyGroup instances is only doing so for the particular class, not considering any parent classes. Unless that’s by design, it’s arguably a bug, so you could open an issue over at developer.blender.org.

Thanks. That does sound like what’s happening. Since I’m relatively new to Blender and Python, I mostly wanted to be sure I wasn’t missing something obvious in my test cases before reporting it. Thanks to both of you for looking into it and posting your insights.

In case anyone is still wondering about this or finds it through Google, I too was trying to use inheritance in a similar manner and was failing dismally in my efforts. And then just yesterday I read something in the API documentation which pointed to a way it can be achieved, in this case using a mixin as opposed to just a parent class for inheritance.

Let’s say I wanted to create a number of operators which share common properties, myint, mystring, myfloat. The following code illustrates how this can be achieved:

import bpy
from bpy.types import Operator
from bpy.props import IntProperty, StringProperty, FloatProperty


class CommonProps:
    '''Class containing common properties to
    be inherited by various operator classes'''


    myint = IntProperty(name="My integer", default=1)
    mystring = StringProperty(name="My string", default="Hello")
    myfloat = FloatProperty(name="My float", default=3.141592654)


class TestOp1(Operator, CommonProps):
    bl_label = "My Test Op1"
    bl_idname = "test.op1"
    bl_options = {'REGISTER', 'UNDO'}


    op2prop = StringProperty(name="Op1 only prop", default="Will only appear in Op1")


    def invoke(self, context, event):
        wm = bpy.context.window_manager
        return wm.invoke_props_dialog(self)


    def execute(self, context):
        return {'FINISHED'}


class TestOp2(Operator, CommonProps):
    bl_label = "My Test Op2"
    bl_idname = "test.op2"
    bl_options = {'REGISTER', 'UNDO'}


    op2prop = StringProperty(name="Op2 only prop", default="Will only appear in Op2")


    def invoke(self, context, event):
        wm = bpy.context.window_manager
        return wm.invoke_props_dialog(self)


    def execute(self, context):
        return {'FINISHED'}


bpy.utils.register_module(__name__)
bpy.ops.test.op1('INVOKE_DEFAULT')
#bpy.ops.test.op2('INVOKE_DEFAULT')

The script defines two operators, TestOp1 and TestOp2, which appear in popups just for illustrative purposes. Each defines a single property, but you will also note that each shows the three common properties we are interested in above.

Now, each of these operators still inherits from bpy.types.Operator directly, however each also inherits from the mixin class CommonProps where the three common properties are declared. For those who aren’t aware, “mixin” is the name given to a class which defines properties and functionality which can be shared by several classes alongside a class’s parent class via multiple-inheritance, which is exactly what is going on here.

@BlenderHawkBob if you’re still out there and reading this, the following can be done to your code to make inheritance work:

class CommonProps:
    s = StringProperty(name="s",default="ABC")


class Base(bpy.types.PropertyGroup, CommonProps):
    pass
class Child(bpy.types.PropertyGroup, CommonProps):
    pass


class AppPropertyGroup(bpy.types.PropertyGroup):


   b = PointerProperty(name="b", type=Base)
   c = PointerProperty(name="c", type=Child)


   def print_self (self, context):
      print ( "App Members:" )
      print ( " b.s = " + self.b.s ) # OK
      print ( " c.s = " + self.c.s ) # Fails



Of course here the names Base and Child don’t make any sense because they are both in fact children of CommonProps. However, this should achieve what it was you were trying to do in the first place.

For reference, this approach was suggested here:

http://www.blender.org/documentation/blender_python_api_2_70_release/bpy.types.Panel.html

specifically under the “Mix-in Classes” section:

“A mix-in parent class can be used to share common properties and Menu.poll function.”

good find and thanks for sharing Outrovurt!

Have you watched “Stop Writting Classes”?

Thanks for that link Atom.
Being an old guy it’s nice to see some sanity coming back into coding.
They will be saying jumps and goto’s are ok again in another 20 years

Anyway
After reading this http://www.ianlewis.org/en/mixins-and-python
it seams to me that the classes
class Base(bpy.types.PropertyGroup, CommonProps):
pass
class Child(bpy.types.PropertyGroup, CommonProps):
pass

should be

class Base( CommonProps,bpy.types.PropertyGroup):
pass
class Child(CommonProps,bpy.types.PropertyGroup):
pass

or have I got this wrong

If Ian Lewis is right

it seems to me to read that mixins work like

CommonProps extends/inherits bpy.types.PropertyGroup