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.
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.