0

I'm doing some coding for Maya with PyMel and I'm trying to create some properties in my rig class to wrap some PyMel code. The code for all of the properties was pretty similar, so I figured this would be a good place to use a closure.

import pymel.core as pm
import riggermortis.utils as utils

class RigModule(object):
    def __init__:
        # blah blah irrelevant code here
        pass

    def createRootProperty(attrName):
        def getter(self):
            return pm.getAttr(self.root+'.'+attrName)
        def setter(self, nodeToLink):
            if self.root.hasAttr(attrName):
                pm.setAttr(self.root+'.'+attrName, nodeToLink)
            else:
                utils.linkNodes(self.root, nodeToLink, attrName)
        return property(fget=getter,fset=setter)

    hookConstraint = createRootProperty('hookConstraint')
    unhookTarget = createRootProperty('unhookTarget')
    moduleGrp = createRootProperty('moduleGrp')
    hookGrp = createRootProperty('hookGrp')

Functionally it works, but Eclipse/PyDev is telling me that my 'createRootProperty' function needs 'self' as its first argument so I'm wondering if what I'm doing is incorrect.

  • The closure is not a method. It does not require `self`. You could place it into the namespace of the module (one level up) to avoid the problem. – Klaus D. Jun 06 '18 at 15:23
  • 1
    What you are doing is fine. Just move the function from the class scope to the module scope to satisfy the linter. And maybe start the name with an underscore to mark it as private to the module. – Sven Marnach Jun 06 '18 at 15:24

1 Answers1

1

For what you're doing a closure is not really needed except for cleanliness. The linter think it's an incorrectly formatted member function, even though it's doing what you want.

You can just move the function of the class scope and the linter will stop complaining -- you can rename the function with an underscore so nobody accidentally thinks it is a tool rather than a piece of infrastructure.

If you expect to do this a lot, you could automate it into a metaclass that reads a list of names from a class field and creates properies as appropriate. There's a more detailed example of that strategy here, but in essence the metaclass will get a copy of the class dictionary when the class is defined and it has the opportunity to mess with the definition before it gets compiled. You can create a property easily at that step:

def createRootProperty(name):
    # this is a dummy, but as long as
    # the return from this function
    # is a property descriptor you're good
    @property
    def me(self):
        return name, self.__class__

    return me


class PropertyMeta(type):

    # this gets called when a class using this meta is
    # first compiled.  It gives you a chance to intervene
    # in the class creation project

    def __new__(cls, name, bases, properties):
        # if the class has a 'PROPS' member, it's a list 
        # of properties to add
        roots = properties.get('PROPS', [])
        for r in roots:
            properties[r] = createRootProperty(r)
            print ("# added property '{}' to {}".format(r, name))

        return type.__new__( cls, name, bases, properties)


class RigModule(object):
    __metaclass__ = PropertyMeta
    PROPS = ['arm', 'head', 'leg']

    def __init__(self):
        pass

test = RigModule()
print test.arm

class Submodule(RigModule):
    # metaclass added properties are inheritable
    pass


test2 = Submodule()
print test2.leg

class NewProperties(RigModule):
    # they can also be augmented in derived classes
    PROPS = ['nose', 'mouth']

print NewProperties().nose
print NewProperties().arm


# added property 'arm' to RigModule
# added property 'head' to RigModule
# added property 'leg' to RigModule
#  ('arm', <class '__main__.RigModule'>)
#  ('leg', <class '__main__.Submodule'>)
# added property 'nose' to NewProperties
# added property 'mouth' to NewProperties
# ('nose', <class '__main__.NewProperties'>)
# ('arm', <class '__main__.NewProperties'>)

Metaclasses get a bad rep -- sometimes deservedly so -- for adding complexity. Don't use them when a simpler approach will do. But for boilerplate reduction in cases like this they are a great tool.

theodox
  • 12,028
  • 3
  • 23
  • 36