Table of Contents

Class: MethodExporter ./src/peak/model/method_exporter.py

Support for generating "verbSubject()" methods from template functions

MethodExporter is a metaclass whose instances are singletons that install "verbSubject()"-style methods in classes which contain them. The verbs are implemented as method templates in the class statement which defines the MethodExporter, and the subject name comes from the name of the class being defined. (Note: the target class must have a metaclass based on binding.Activator, either through explicit definition or via inheritance from e.g. binding.Component.)

Usage example:

            class slappableFeature(object):

                __metaclass__ = MethodExporter

                message = "Ow, that hurts!"

                newVerbs = [ ('slap', "slap%(initCap)s") ]

                def slap(feature, self, extra):
                    print "%s: %s (%s)" % (self.name, feature.message, extra)

            class Person(object):

                __metaclass__ = binding.Activator

                def __init__(self,name):
                    self.name = name

                class face(slappableFeature):
                    message = "Watch out for my glasses!"

                class back(slappableFeature):
                    pass

            joe = Person('Joe')

            # Prints: "Joe: Watch out for my glasses! (Thanks, I needed that!)"
            joe.slapFace("Thanks, I needed that!")

            # Prints: "Joe: Ow, that hurts!  (Am I great or what?)"
            joe.slapBack("Am I great or what?")

While a bit silly, this demonstrates how the method template slap() is used to generate slapFace() and slapBack() methods, how to define a newVerbs list, and the calling conventions for verbs.

Typically, you will define MethodExporters with multiple verbs, and you may also use template variants, which are chosen by metadata in subclasses. For example, suppose we wanted slappableFeatures to implement the slap verb differently depending on skin thickness:

            class slappableFeature(object):

                __metaclass__ = MethodExporter

                thickness = 5

                newVerbs = [ ('slap', "slap%(initCap)s") ]

                def slap_thick(feature, self):
                    print "THUD!", feature.thickness

                slap_thick.installIf = (
                    lambda feature,func: feature.thickness > 10
                )

                slap_thick.verb = "slap"

                def slap_thin(feature, self):
                    print "SMACK!", feature.thickness

                slap_thin.installIf = (
                    lambda feature,func: feature.thickness <= 10
                )

                slap_thin.verb = "slap"

            class Person(object):

                __metaclass__ = binding.Activator

                class face(slappableFeature):
                    thickness = 5

                class back(slappableFeature):
                    thickness = 15

            joe = Person()

            # two ways to print "SMACK! 5"
            joe.slapFace()
            Person.face.slap(joe)

            # two ways to print "THUD! 15"
            joe.slapBack()
            Person.back.slap(joe)

As you can see from this example, face and back actually do exist as real attributes of the Person class. They are instances of MethodExporter, class-like objects created by their respective class statements. MethodExporter instances are singleton-like: any functions defined in a MethodExporter which are not templates (i.e., do not have a verb and aren't listed in the newVerbs list) will become methods of the MethodExporter itself, rather than methods of the containing class. To distinguish such methods, and to keep clear the distinction between the feature and its container, we suggest using feature as the first parameter of such methods. For example, we could rewrite the slap verb as:

            def yelp(feature):
                # This will be a method of the feature, because it is not
                # defined in 'newVerbs' of this class or a superclass
                print 'Y%sow!  My %s hurts!" % (
                    'e'*(20-feature.thickness), feature.attrName
                )

            def slap(feature, self):
                feature.yelp()

This will print "Yeeeeeeeeeeeeeeeow! My face hurts!" or "Yeeeeeow! My back hurts!", as is appropriate for the specific feature. We could also simply call Person.back.yelp() or Person.face.yelp() to obtain the same results.

By now, the sharp-eyed reader will be wondering about the bits of magic code that keep showing up, such as the (feature, self) parameter pattern, and the newVerbs list. Not to mention our earlier usage of the installIf and verb attributes. So let's move on to the reference sections...

Specifying Verbs with the newVerbs class attribute

The newVerbs class attribute is a list of (key,value) tuples listing verbs introduced in the MethodExporter being defined. The keys are verb (inner method) names, and the values are format strings that are used to generate the outer method names.

The format strings are applied to a nameMapping object supplied by MethodExporter's subjectNames() method. The format strings are normal Python '% formatting strings as applied to a mapping. The names in parentheses, such as "initCap" in "slap%(initCap)s"', are looked up in the mapping object.

initCap is the most commonly used name in a format string: it supplies the MethodExporter instance's name, with the first character capitalized. But there are many other options. For example, any attribute of the MethodExporter being defined can be used, like this:

                class Collection(someFeature):
                    single = 'item'
                    newVerbs = Items(add='add%(single)s')

The above example will generate an additem() method, unless a subclass changes the single attribute to a different string. This lets you have subclasses change the name of what kind of objects are in a collection, and have the exported method names change accordingly. (Notice also that you can use the peak.api Items() function to create a newVerbs list; you don't have to do it by hand.)

You can also use more complex formatting, using dotted names:

                class Collection(someFeature):
                    single = 'item'
                    newVerbs = Items(add='add%(single.initCap)s')

This will apply the nameMapping.initCap() method to the single attribute before formatting, resulting in an addItem() method. See the nameMapping documentation for more details on how names used in format strings are interpreted.

The installIf function attribute

The installIf function attribute, if supplied, is a callable that takes two arguments: the feature (i.e. the MethodExporter instance which is being created), and the function. If the callable returns a true value, the function will be used as the template for the appropriate verb (inner and outer forms). If the callable returns false, the function will not be used. Note that installIf conditions should be arranged such that at most one can be true at a time, because if more than one is true, a TypeError will be raised at class creation time. If none of the installIf conditions are true, the verb will not appear as a method of the feature class, nor will it be exported to any containing class.

The verb function attribute

The normal use of installIf is to select variant implementations of the same verb, based on metadata supplied in a subclass. However, since the variants must have different function names, the verb function attribute is used to indicate the name which will be used in the finished MethodExporter. Thus, in our "thick and thin" slap example, using slap_thick.verb="slap" and slap_thin.verb="slap" caused either slap_thick or slap_thin to be installed as the slap method of the MethodExporter. If you do not supply a verb function attribute, the function's name within the class is used. Note that this does not have to be the function name; for example, the below defines a baz verb, not 'foo':

                def foo(self): pass

                class bar(object):
                    __metaclass__ = MethodExporter
                    newVerbs = Items(baz='baz%(initCap)s')
                    baz = foo

Also note that methods are only exported if their verb is listed in the newVerbs of the class or one of its base classes. You can make use of this behavior to create "private" verbs that are only accessible from the feature. Just set the verb attribute of your function templates to the name of the desired "private" method.

Parameters and Calling Conventions

All methods of a MethodExporter are defined as Python classmethod objects. (This is done for you automatically by MethodExporter, so you don't have to explicitly declare them as such.)

Being class methods, the first parameter of any method or method template that you place in a MethodExporter class is always the class. By convention in PEAK, this parameter is named feature, but you may use any name that makes sense to you. When the method executes, this parameter will refer to the MethodExporter, so you can access its attributes and methods. For simple methods of the MethodExporter itself, this is the only parameter which you must define.

Method templates, however, must take a second parameter, named self by convention in PEAK. This parameter will receive the instance of the containing class that the MethodExporter is being used in. Any further parameters beyond this point belong to the signature of your method template and are not defined or used by the infrastructure.

This approach allows method templates to do double duty. They can be used as class methods and passed an instance of the outer class, or they can be used directly as instance methods of the outer class.

Dynamic Method Templates

By using the isTemplate function attribute, you can indicate that the function should be called in order to obtain the actual code to be used. Here's the earlier code generation example, rewritten using dynamic templates:

                class slappableFeature(object):

                    __metaclass__ = MethodExporter

                    thickness = 5

                    newVerbs = [ ('slap', "slap%(initCap)s") ]

                    def slap(feature):

                        if feature.thickness > 10:

                            def slap(feature, self):
                                print "THUD!", feature.thickness

                        else:
                            def slap(feature, self):
                                print "SMACK!", feature.thickness

                        return slap

                    slap.isTemplate = True

As you can see, this can often be a more readable approach, if you don't need to redefine the variants in subclasses. The outer slap function shown above is passed the feature object (or one of its subclasses) being defined, and must return the function to be used as a verb implementation. If it returns None, the verb will not exist in the defined feature, nor will it be exported to any class the feature is placed in.

The same rules apply for recognizing a template as any other verb implementation: it must be named the same as a verb, or it must have a verb function attribute identifying the appropriate verb. It can also have an installIf attribute, if desired. The installIf attribute is checked first, and the template function is only called if the installIf check returns a true value.

Inner and Outer Method Definitions

Sometimes, one needs to modify an "off-the-shelf" method exported by a MethodExporter. For this reason, MethodExporters do not overwrite methods which are already defined in their containing class. For example, if we defined Person as follows:

                class Person(object):

                    class face(slappableFeature):
                        thickness = 5

                    class back(slappableFeature):
                        thickness = 15

                    def slapBack(self):
                        self.__class__.back.slap(self)
                        print "Do you really have to do that?"

                    def slapFace(self):
                        print "Don't even think about it!"

Then aPerson.slapFace() would print "Don't even think about it", while aPerson.slapBack() would carry out the generated behavior, and then ask, "Do you really have to do that?". Subclasses of Person will retain these modified behaviors, even if back is redefined in the subclass. (In which case, the redefined behavior will be followed by the "Do you really have to do that?" question.)

Overriding Templates in Subclasses

If you override a template in a subclass, you don't need to specify its verb, since that method name is already associated with the verb. However, if you need an installIf condition you must specify it in the subclass as well; otherwise it will be treated as an unconditional template, which may lead to conflict between install conditions.

Note that the above rules apply to any template you override, regardless of its name, as long as it existed in the superclass. If you add a new template for an existing or new verb, you must specify its verb attribute, or it must be an exported verb defined in the subclass' newVerbs list.

Why self.__class__.back?

Sharp-eyed Pythonistas may wonder why we don't just say self.back and be done with it. The reason is that features can also be properties, so self.back might actually be the instance value of the property defined by the feature. self.__class__.back will always be the class' definition of the feature back, which is what we want.

The SEF framework adds __get__, __set__, and __delete__ methods to a subclass of MethodExporter, which lets you create features that are properties; look at the peak.model.api.FeatureClass class for more details.

Implementation Notes

It's amazing that it takes so much documentation to describe so little code. The combined docstrings for this metaclass are over three times the size of the actual code. So if you've already read this far, you might as well read the source if you have a need to understand the actual implementation. :)

Base Classes   
protocols.ProviderMixin
ActiveClass
Methods   
__init__
activateInClass
getMethod
subjectNames
  __init__ 
__init__ (
        self,
        className,
        bases,
        classDict,
        )

Set up method templates, name mapping, etc.

Exceptions   
TypeError(( "More than one version of '%s' template applies" % verb ), self, candidates )
  activateInClass 
activateInClass (
        self,
        klass,
        attrName,
        )

Install the feature's "verbSubject()" methods upon use in a class

Exceptions   
TypeError("Feature %s installed in %s as %s; must be named %s" %( self.attrName, klass, attrName, self.attrName ) )
  getMethod 
getMethod (
        self,
        ob,
        verb,
        )

Look up a method dynamically

Sometimes, one wishes to access a possibly-overridden method of a feature, knowing the verb and subject, but not the method name. That is, one knows about slap, and face, but not slapFace. In that case, face.getMethod(object,"slap") can be used to obtain the object.slapFace method, without needing to know the naming convetions used by the object.

Note that this is different than simply accessing face.slap, because the containing class might have defined its own slapFace() method. That method would refer to face.slap in order to call the original, un-overridden method generated by the face feature.

  subjectNames 
subjectNames ( self )

Return a nameMapping object for this feature


Table of Contents

This document was automatically generated on Mon Nov 11 01:11:05 2024 by HappyDoc version 2.1