Module Inheritance and Module Advice
The APIs defined here let you create modules which derive from other
modules, by defining a module-level __bases__ attribute which lists
the modules you wish to inherit from. For example:
from TW.API import *
import BaseModule1, BaseModule2
__bases__ = BaseModule1, BaseModule2
class MyClass:
...
setupModule()
The setupModule() call will convert the calling module, BaseModule1 ,
and BaseModule2 into specially altered bytecode objects and execute
them (in "method-resolution order") rewriting the calling module's
dictionary in the process. The result is rather like normal class
inheritance, except that classes (even nested classes) are merged by name,
and metaclass constraints are inherited. So an inheriting module need
not list all the classes from its base module in order to change them
by altering a base class in that module.
Note: All the modules listed in __bases__ must call setupModule() , even
if they do not define a __bases__ or desire module inheritance
themselves. This is because TransWarp cannot otherwise get access to
their bytecode in a way that is compatible with the many "import hook"
systems that exist for Python. (E.g. running bytecode from zip files or
frozen into an executable, etc.)
Function Rebinding
All functions inherited via "module inheritance" using setupModule()
(including those which are instance or class methods) have their
globals rebound to point to the inheriting module. This means that if
a function or method references a global in the base module you're
inheriting from, you can override that global in the inheriting module,
without having to recode the function that referenced it. (This is
especially useful for super() calls, which usually use global
references to class names!)
In addition to rebinding general globals, functions which reference
the global name __proceed__ are also specially rebound so that
__proceed__ is the previous definition of that function, if any, in
the inheritance list. (It is None if there is no previous
definition.) This allows you to do the rough equivalent of a super()
call (or AspectJ "around advice") without having to explicitly import
the old version of a function. Note that __proceed__ is always
either a function or None , so you must include self as a parameter
when calling it from a method definition.
Inheritance of Metaclass Constraints
Python 2.2 enforces metaclass constraints for "new-style" classes.
That is, it requires that a new class' metaclass be "compatible" with
the metaclass of each of the base classes of the class, where
"compatible" means "is the same as, or is a subclass of".
Python, however, does not automatically generate such a metaclass for
you. You must ordinarily supply that metaclass yourself, either as
an explicit __metaclass__ definition, or by having one of the base
classes supply a suitable metaclass. This is fine for simple programs,
where metaclasses are infrequently mixed, but more problematic for
complex frameworks like TransWarp, where a variety of metaclasses are
mixed and matched to supply various properties.
So, using setupModule() gives you an additional bonus: TransWarp
will automatically generate the necessary metaclasses for you, so long
as you use TransWarp's alternate API for specifying metaclasses.
Please see the documentation of the makeClass function in the
TW.Utils.Meta module for more information about how this works.
Please note that metaclasses are not combined across modules, unless
they are specified with same-named class statements in both modules.
(In which case, they are combined because they are following the normal
class combination rules of module inheritance, not because of anything
to do with metaclasses.)
Special Considerations for Mutables and Dynamic Initialization
Both inheritance and advice are implemented by running hacked,
module-level code under a "simulator" that intercepts the setting of
variables. This works great for static definitions like class
and def statements, constant assignments, import , etc. It also
works reasonably well for many other kinds of static initialization
of immutable objects
Mutable values, however, may require special considerations. For
example, if a module sets up some kind of registry as a module-level
variable, and an inheriting module overrides the definition, things
can get tricky. If the base module writes values into that registry as
part of module initialization, those values will also be written into
the registry defined by the derived module.
Another possible issue is if the base module performs other externally
visible, non-idempotent operations, such as registering classes or
functions in another module's registry, printing things to the console,
etc. The simple workaround for all these considerations, however, is
to move your dynamic initialization code to a module-level __init__
function.
Module-level __init__() Functions
The last thing setupModule() does before returning, is to check for a
module-level __init__() function, and call it with no arguments, if
it exists. This allows you to do any dynamic initialization operations
(such as modifying or resetting global mutables) after inheritance
has taken effect. As with any other function defined in the module,
__proceed__ refers to the previous (i.e. preceding base module)
definition of the function or None . This lets you can chain to your
predecessors' initialization code, if needed/desired.
Note, by the way, that if you have an if __name__=="__main__" block
in your module, it would probably be best if you move it inside the
__init__() function, as this ensures that it will not be run
repeatedly if you do not wish it to be. It will also allow other
modules to inherit that code and wrap around it, if they so desire.
To-do Items
The simulator should issue warnings for a variety of questionable
situations, such as...
Code matching the following pattern, which doesn't do what it looks
like it does, and should probably be considered a "serious order
disagreement":
BaseModule:
class Foo: ...
class Bar: ...
DerivedModule:
class Bar: ...
class Foo(Bar): ...
This docstring is woefully inadequate to describe all the interesting
subtleties of module inheritance; a tutorial is really needed. But
there does need to be a reference-style explanation as well, that
describes the precise semantics of interpretation for assignments,
def , and class , in modules running under simulator control.
Add LegacyModule("name") and loadLegacyModule("name") APIs to
allow inheriting from and/or giving advice to modules which do not
call setupModule() .
Imported modules
|
|
from TW.Utils.Code import *, BUILD_CLASS, STORE_NAME, MAKE_CLOSURE, MAKE_FUNCTION, LOAD_CONST, STORE_GLOBAL, CALL_FUNCTION, IMPORT_STAR, IMPORT_NAME, JUMP_ABSOLUTE, POP_TOP, ROT_FOUR, LOAD_ATTR, LOAD_GLOBAL, ROT_TWO, LOAD_LOCALS, STORE_SLICE, DELETE_SLICE, STORE_ATTR, STORE_SUBSCR, DELETE_SUBSCR, DELETE_ATTR, DELETE_NAME, DELETE_GLOBAL
from TW.Utils.Meta import makeClass
import sys
from sys import _getframe
from types import ModuleType
from warnings import warn, warn_explicit
|
Functions
|
|
adviseModule
configure
getCodeListForModule
prepForSimulation
setupModule
|
|
adviseModule
|
adviseModule ( moduleName )
"Advise" a module - like a runtime patch
Usage::
from TW.API import * ...
adviseModule(moduleToAdvise )
adviseModule() works much like setupModule() . The main difference
is that it applies the current module as a patch to the supplied module
name. The module to be advised must not have been imported yet, and it
must call setupModule() . The result will be as though the advised
module had been replaced with a derived module, using the standard module
inheritance rules to derive the new module.
Note that more than one advising module may advise a single target module,
in which case the order of importing is significant. Advice modules
imported later take precedence over those imported earlier. (The target
module must always be imported last.)
Advice modules may advise other advice modules, but there is little point
to doing this, since both advice modules will still have to be explicitly
imported before their mutual target in order for the advice to take effect.
Exceptions
|
|
SpecificationError( "%s is already imported and cannot be advised" % moduleName )
SpecificationError( "Advice modules cannot use '__bases__'" )
|
|
|
configure
|
configure ( obj, **attrs )
Set attributes without overwriting values defined in a derived module
|
|
getCodeListForModule
|
getCodeListForModule ( module, code=None )
Exceptions
|
|
TypeError("%s is not a module in %s __bases__" %( baseModule, name ) )
|
|
|
prepForSimulation
|
prepForSimulation (
code,
path='',
depth=0,
)
|
|
setupModule
|
setupModule ()
setupModule() - Build module, w/advice and inheritance
setupModule() should be called only at the very end of a module's
code. This is because any code which follows setupModule() will be
executed twice. (Actually, the code before setupModule() gets
executed twice, also, but the module dictionary is reset in between,
so its execution is cleaner.)
|
Classes
|
|
|
|