mirror of
https://github.com/python/cpython.git
synced 2025-11-02 03:01:58 +00:00
Completed first draft.
This commit is contained in:
parent
910105515e
commit
0cdb887676
4 changed files with 470 additions and 21 deletions
|
|
@ -107,7 +107,7 @@ def _test():
|
||||||
assert Result > arg
|
assert Result > arg
|
||||||
x = C()
|
x = C()
|
||||||
x.m1(12)
|
x.m1(12)
|
||||||
x.m1(-1)
|
## x.m1(-1)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
_test()
|
_test()
|
||||||
|
|
|
||||||
45
Demo/metaclasses/Simple.py
Normal file
45
Demo/metaclasses/Simple.py
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import types
|
||||||
|
|
||||||
|
class Tracing:
|
||||||
|
def __init__(self, name, bases, namespace):
|
||||||
|
"""Create a new class."""
|
||||||
|
self.__name__ = name
|
||||||
|
self.__bases__ = bases
|
||||||
|
self.__namespace__ = namespace
|
||||||
|
def __call__(self):
|
||||||
|
"""Create a new instance."""
|
||||||
|
return Instance(self)
|
||||||
|
|
||||||
|
class Instance:
|
||||||
|
def __init__(self, klass):
|
||||||
|
self.__klass__ = klass
|
||||||
|
def __getattr__(self, name):
|
||||||
|
try:
|
||||||
|
value = self.__klass__.__namespace__[name]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError, name
|
||||||
|
if type(value) is not types.FunctionType:
|
||||||
|
return value
|
||||||
|
return BoundMethod(value, self)
|
||||||
|
|
||||||
|
class BoundMethod:
|
||||||
|
def __init__(self, function, instance):
|
||||||
|
self.function = function
|
||||||
|
self.instance = instance
|
||||||
|
def __call__(self, *args):
|
||||||
|
print "calling", self.function, "for", self.instance, "with", args
|
||||||
|
return apply(self.function, (self.instance,) + args)
|
||||||
|
|
||||||
|
Trace = Tracing('Trace', (), {})
|
||||||
|
|
||||||
|
class MyTracedClass(Trace):
|
||||||
|
def method1(self, a):
|
||||||
|
self.a = a
|
||||||
|
def method2(self):
|
||||||
|
return self.a
|
||||||
|
|
||||||
|
aninstance = MyTracedClass()
|
||||||
|
|
||||||
|
aninstance.method1(10)
|
||||||
|
|
||||||
|
print aninstance.method2()
|
||||||
256
Demo/metaclasses/Synch.py
Normal file
256
Demo/metaclasses/Synch.py
Normal file
|
|
@ -0,0 +1,256 @@
|
||||||
|
"""Synchronization metaclass.
|
||||||
|
|
||||||
|
This metaclass makes it possible to declare synchronized methods.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import thread
|
||||||
|
|
||||||
|
# First we need to define a reentrant lock.
|
||||||
|
# This is generally useful and should probably be in a standard Python
|
||||||
|
# library module. For now, we in-line it.
|
||||||
|
|
||||||
|
class Lock:
|
||||||
|
|
||||||
|
"""Reentrant lock.
|
||||||
|
|
||||||
|
This is a mutex-like object which can be acquired by the same
|
||||||
|
thread more than once. It keeps a reference count of the number
|
||||||
|
of times it has been acquired by the same thread. Each acquire()
|
||||||
|
call must be matched by a release() call and only the last
|
||||||
|
release() call actually releases the lock for acquisition by
|
||||||
|
another thread.
|
||||||
|
|
||||||
|
The implementation uses two locks internally:
|
||||||
|
|
||||||
|
__mutex is a short term lock used to protect the instance variables
|
||||||
|
__wait is the lock for which other threads wait
|
||||||
|
|
||||||
|
A thread intending to acquire both locks should acquire __wait
|
||||||
|
first.
|
||||||
|
|
||||||
|
The implementation uses two other instance variables, protected by
|
||||||
|
locking __mutex:
|
||||||
|
|
||||||
|
__tid is the thread ID of the thread that currently has the lock
|
||||||
|
__count is the number of times the current thread has acquired it
|
||||||
|
|
||||||
|
When the lock is released, __tid is None and __count is zero.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Constructor. Initialize all instance variables."""
|
||||||
|
self.__mutex = thread.allocate_lock()
|
||||||
|
self.__wait = thread.allocate_lock()
|
||||||
|
self.__tid = None
|
||||||
|
self.__count = 0
|
||||||
|
|
||||||
|
def acquire(self, flag=1):
|
||||||
|
"""Acquire the lock.
|
||||||
|
|
||||||
|
If the optional flag argument is false, returns immediately
|
||||||
|
when it cannot acquire the __wait lock without blocking (it
|
||||||
|
may still block for a little while in order to acquire the
|
||||||
|
__mutex lock).
|
||||||
|
|
||||||
|
The return value is only relevant when the flag argument is
|
||||||
|
false; it is 1 if the lock is acquired, 0 if not.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.__mutex.acquire()
|
||||||
|
try:
|
||||||
|
if self.__tid == thread.get_ident():
|
||||||
|
self.__count = self.__count + 1
|
||||||
|
return 1
|
||||||
|
finally:
|
||||||
|
self.__mutex.release()
|
||||||
|
locked = self.__wait.acquire(flag)
|
||||||
|
if not flag and not locked:
|
||||||
|
return 0
|
||||||
|
try:
|
||||||
|
self.__mutex.acquire()
|
||||||
|
assert self.__tid == None
|
||||||
|
assert self.__count == 0
|
||||||
|
self.__tid = thread.get_ident()
|
||||||
|
self.__count = 1
|
||||||
|
return 1
|
||||||
|
finally:
|
||||||
|
self.__mutex.release()
|
||||||
|
|
||||||
|
def release(self):
|
||||||
|
"""Release the lock.
|
||||||
|
|
||||||
|
If this thread doesn't currently have the lock, an assertion
|
||||||
|
error is raised.
|
||||||
|
|
||||||
|
Only allow another thread to acquire the lock when the count
|
||||||
|
reaches zero after decrementing it.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.__mutex.acquire()
|
||||||
|
try:
|
||||||
|
assert self.__tid == thread.get_ident()
|
||||||
|
assert self.__count > 0
|
||||||
|
self.__count = self.__count - 1
|
||||||
|
if self.__count == 0:
|
||||||
|
self.__tid = None
|
||||||
|
self.__wait.release()
|
||||||
|
finally:
|
||||||
|
self.__mutex.release()
|
||||||
|
|
||||||
|
|
||||||
|
def _testLock():
|
||||||
|
|
||||||
|
done = []
|
||||||
|
|
||||||
|
def f2(lock, done=done):
|
||||||
|
lock.acquire()
|
||||||
|
print "f2 running in thread %d\n" % thread.get_ident(),
|
||||||
|
lock.release()
|
||||||
|
done.append(1)
|
||||||
|
|
||||||
|
def f1(lock, f2=f2, done=done):
|
||||||
|
lock.acquire()
|
||||||
|
print "f1 running in thread %d\n" % thread.get_ident(),
|
||||||
|
try:
|
||||||
|
f2(lock)
|
||||||
|
finally:
|
||||||
|
lock.release()
|
||||||
|
done.append(1)
|
||||||
|
|
||||||
|
lock = Lock()
|
||||||
|
lock.acquire()
|
||||||
|
f1(lock) # Adds 2 to done
|
||||||
|
lock.release()
|
||||||
|
|
||||||
|
lock.acquire()
|
||||||
|
|
||||||
|
thread.start_new_thread(f1, (lock,)) # Adds 2
|
||||||
|
thread.start_new_thread(f1, (lock, f1)) # Adds 3
|
||||||
|
thread.start_new_thread(f2, (lock,)) # Adds 1
|
||||||
|
thread.start_new_thread(f2, (lock,)) # Adds 1
|
||||||
|
|
||||||
|
lock.release()
|
||||||
|
import time
|
||||||
|
while len(done) < 9:
|
||||||
|
print len(done)
|
||||||
|
time.sleep(0.001)
|
||||||
|
print len(done)
|
||||||
|
|
||||||
|
|
||||||
|
# Now, the Locking metaclass is a piece of cake.
|
||||||
|
# As an example feature, methods whose name begins with exactly one
|
||||||
|
# underscore are not synchronized.
|
||||||
|
|
||||||
|
from Meta import MetaClass, MetaHelper, MetaMethodWrapper
|
||||||
|
|
||||||
|
class LockingMethodWrapper(MetaMethodWrapper):
|
||||||
|
def __call__(self, *args, **kw):
|
||||||
|
if self.__name__[:1] == '_' and self.__name__[1:] != '_':
|
||||||
|
return apply(self.func, (self.inst,) + args, kw)
|
||||||
|
self.inst.__lock__.acquire()
|
||||||
|
try:
|
||||||
|
return apply(self.func, (self.inst,) + args, kw)
|
||||||
|
finally:
|
||||||
|
self.inst.__lock__.release()
|
||||||
|
|
||||||
|
class LockingHelper(MetaHelper):
|
||||||
|
__methodwrapper__ = LockingMethodWrapper
|
||||||
|
def __helperinit__(self, formalclass):
|
||||||
|
MetaHelper.__helperinit__(self, formalclass)
|
||||||
|
self.__lock__ = Lock()
|
||||||
|
|
||||||
|
class LockingMetaClass(MetaClass):
|
||||||
|
__helper__ = LockingHelper
|
||||||
|
|
||||||
|
Locking = LockingMetaClass('Locking', (), {})
|
||||||
|
|
||||||
|
def _test():
|
||||||
|
# For kicks, take away the Locking base class and see it die
|
||||||
|
class Buffer(Locking):
|
||||||
|
def __init__(self, initialsize):
|
||||||
|
assert initialsize > 0
|
||||||
|
self.size = initialsize
|
||||||
|
self.buffer = [None]*self.size
|
||||||
|
self.first = self.last = 0
|
||||||
|
def put(self, item):
|
||||||
|
# Do we need to grow the buffer?
|
||||||
|
if (self.last+1) % self.size != self.first:
|
||||||
|
# Insert the new item
|
||||||
|
self.buffer[self.last] = item
|
||||||
|
self.last = (self.last+1) % self.size
|
||||||
|
return
|
||||||
|
# Double the buffer size
|
||||||
|
# First normalize it so that first==0 and last==size-1
|
||||||
|
print "buffer =", self.buffer
|
||||||
|
print "first = %d, last = %d, size = %d" % (
|
||||||
|
self.first, self.last, self.size)
|
||||||
|
if self.first <= self.last:
|
||||||
|
temp = self.buffer[self.first:self.last]
|
||||||
|
else:
|
||||||
|
temp = self.buffer[self.first:] + self.buffer[:self.last]
|
||||||
|
print "temp =", temp
|
||||||
|
self.buffer = temp + [None]*(self.size+1)
|
||||||
|
self.first = 0
|
||||||
|
self.last = self.size-1
|
||||||
|
self.size = self.size*2
|
||||||
|
print "Buffer size doubled to", self.size
|
||||||
|
print "new buffer =", self.buffer
|
||||||
|
print "first = %d, last = %d, size = %d" % (
|
||||||
|
self.first, self.last, self.size)
|
||||||
|
self.put(item) # Recursive call to test the locking
|
||||||
|
def get(self):
|
||||||
|
# Is the buffer empty?
|
||||||
|
if self.first == self.last:
|
||||||
|
raise EOFError # Avoid defining a new exception
|
||||||
|
item = self.buffer[self.first]
|
||||||
|
self.first = (self.first+1) % self.size
|
||||||
|
return item
|
||||||
|
|
||||||
|
def producer(buffer, wait, n=1000):
|
||||||
|
import time
|
||||||
|
i = 0
|
||||||
|
while i < n:
|
||||||
|
print "put", i
|
||||||
|
buffer.put(i)
|
||||||
|
i = i+1
|
||||||
|
print "Producer: done producing", n, "items"
|
||||||
|
wait.release()
|
||||||
|
|
||||||
|
def consumer(buffer, wait, n=1000):
|
||||||
|
import time
|
||||||
|
i = 0
|
||||||
|
tout = 0.001
|
||||||
|
while i < n:
|
||||||
|
try:
|
||||||
|
x = buffer.get()
|
||||||
|
if x != i:
|
||||||
|
raise AssertionError, \
|
||||||
|
"get() returned %s, expected %s" % (x, i)
|
||||||
|
print "got", i
|
||||||
|
i = i+1
|
||||||
|
tout = 0.001
|
||||||
|
except EOFError:
|
||||||
|
time.sleep(tout)
|
||||||
|
tout = tout*2
|
||||||
|
print "Consumer: done consuming", n, "items"
|
||||||
|
wait.release()
|
||||||
|
|
||||||
|
pwait = thread.allocate_lock()
|
||||||
|
pwait.acquire()
|
||||||
|
cwait = thread.allocate_lock()
|
||||||
|
cwait.acquire()
|
||||||
|
buffer = Buffer(1)
|
||||||
|
n = 1000
|
||||||
|
thread.start_new_thread(consumer, (buffer, cwait, n))
|
||||||
|
thread.start_new_thread(producer, (buffer, pwait, n))
|
||||||
|
pwait.acquire()
|
||||||
|
print "Producer done"
|
||||||
|
cwait.acquire()
|
||||||
|
print "All done"
|
||||||
|
print "buffer size ==", len(buffer.buffer)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
_testLock()
|
||||||
|
_test()
|
||||||
|
|
@ -6,9 +6,9 @@
|
||||||
|
|
||||||
<BODY BGCOLOR="FFFFFF">
|
<BODY BGCOLOR="FFFFFF">
|
||||||
|
|
||||||
<H1>Metaprogramming in Python 1.5</H1>
|
<H1>Metaprogramming in Python 1.5 (DRAFT)</H1>
|
||||||
|
|
||||||
<H4>XXX Don't link to this page! It is very much a work in progress.</H4>
|
<H4>XXX This is very much a work in progress.</H4>
|
||||||
|
|
||||||
<P>While Python 1.5 is only out as a <A
|
<P>While Python 1.5 is only out as a <A
|
||||||
HREF="http://grail.cnri.reston.va.us/python/1.5a3/">restricted alpha
|
HREF="http://grail.cnri.reston.va.us/python/1.5a3/">restricted alpha
|
||||||
|
|
@ -267,7 +267,7 @@ class Instance:
|
||||||
value = self.__klass__.__namespace__[name]
|
value = self.__klass__.__namespace__[name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise AttributeError, name
|
raise AttributeError, name
|
||||||
if type(value) is not types.FuncType:
|
if type(value) is not types.FunctionType:
|
||||||
return value
|
return value
|
||||||
return BoundMethod(value, self)
|
return BoundMethod(value, self)
|
||||||
|
|
||||||
|
|
@ -276,20 +276,150 @@ class BoundMethod:
|
||||||
self.function = function
|
self.function = function
|
||||||
self.instance = instance
|
self.instance = instance
|
||||||
def __call__(self, *args):
|
def __call__(self, *args):
|
||||||
print "calling", self.function, "for", instance, "with", args
|
print "calling", self.function, "for", self.instance, "with", args
|
||||||
return apply(self.function, (self.instance,) + args)
|
return apply(self.function, (self.instance,) + args)
|
||||||
|
|
||||||
|
Trace = Tracing('Trace', (), {})
|
||||||
|
|
||||||
|
class MyTracedClass(Trace):
|
||||||
|
def method1(self, a):
|
||||||
|
self.a = a
|
||||||
|
def method2(self):
|
||||||
|
return self.a
|
||||||
|
|
||||||
|
aninstance = MyTracedClass()
|
||||||
|
|
||||||
|
aninstance.method1(10)
|
||||||
|
|
||||||
|
print "the answer is %d" % aninstance.method2()
|
||||||
|
</PRE>
|
||||||
|
|
||||||
|
Confused already? The intention is to read this from top down. The
|
||||||
|
Tracing class is the metaclass we're defining. Its structure is
|
||||||
|
really simple.
|
||||||
|
|
||||||
|
<P>
|
||||||
|
|
||||||
|
<UL>
|
||||||
|
|
||||||
|
<LI>The __init__ method is invoked when a new Tracing instance is
|
||||||
|
created, e.g. the definition of class MyTracedClass later in the
|
||||||
|
example. It simply saves the class name, base classes and namespace
|
||||||
|
as instance variables.<P>
|
||||||
|
|
||||||
|
<LI>The __call__ method is invoked when a Tracing instance is called,
|
||||||
|
e.g. the creation of aninstance later in the example. It returns an
|
||||||
|
instance of the class Instance, which is defined next.<P>
|
||||||
|
|
||||||
|
</UL>
|
||||||
|
|
||||||
|
<P>The class Instance is the class used for all instances of classes
|
||||||
|
built using the Tracing metaclass, e.g. aninstance. It has two
|
||||||
|
methods:
|
||||||
|
|
||||||
|
<P>
|
||||||
|
|
||||||
|
<UL>
|
||||||
|
|
||||||
|
<LI>The __init__ method is invoked from the Tracing.__call__ method
|
||||||
|
above to initialize a new instance. It saves the class reference as
|
||||||
|
an instance variable. It uses a funny name because the user's
|
||||||
|
instance variables (e.g. self.a later in the example) live in the same
|
||||||
|
namespace.<P>
|
||||||
|
|
||||||
|
<LI>The __getattr__ method is invoked whenever the user code
|
||||||
|
references an attribute of the instance that is not an instance
|
||||||
|
variable (nor a class variable; but except for __init__ and
|
||||||
|
__getattr__ there are no class variables). It will be called, for
|
||||||
|
example, when aninstance.method1 is referenced in the example, with
|
||||||
|
self set to aninstance and name set to the string "method1".<P>
|
||||||
|
|
||||||
|
</UL>
|
||||||
|
|
||||||
|
<P>The __getattr__ method looks the name up in the __namespace__
|
||||||
|
dictionary. If it isn't found, it raises an AttributeError exception.
|
||||||
|
(In a more realistic example, it would first have to look through the
|
||||||
|
base classes as well.) If it is found, there are two possibilities:
|
||||||
|
it's either a function or it isn't. If it's not a function, it is
|
||||||
|
assumed to be a class variable, and its value is returned. If it's a
|
||||||
|
function, we have to ``wrap'' it in instance of yet another helper
|
||||||
|
class, BoundMethod.
|
||||||
|
|
||||||
|
<P>The BoundMethod class is needed to implement a familiar feature:
|
||||||
|
when a method is defined, it has an initial argument, self, which is
|
||||||
|
automatically bound to the relevant instance when it is called. For
|
||||||
|
example, aninstance.method1(10) is equivalent to method1(aninstance,
|
||||||
|
10). In the example if this call, first a temporary BoundMethod
|
||||||
|
instance is created with the following constructor call: temp =
|
||||||
|
BoundMethod(method1, aninstance); then this instance is called as
|
||||||
|
temp(10). After the call, the temporary instance is discarded.
|
||||||
|
|
||||||
|
<P>
|
||||||
|
|
||||||
|
<UL>
|
||||||
|
|
||||||
|
<LI>The __init__ method is invoked for the constructor call
|
||||||
|
BoundMethod(method1, aninstance). It simply saves away its
|
||||||
|
arguments.<P>
|
||||||
|
|
||||||
|
<LI>The __call__ method is invoked when the bound method instance is
|
||||||
|
called, as in temp(10). It needs to call method1(aninstance, 10).
|
||||||
|
However, even though self.function is now method1 and self.instance is
|
||||||
|
aninstance, it can't call self.function(self.instance, args) directly,
|
||||||
|
because it should work regardless of the number of arguments passed.
|
||||||
|
(For simplicity, support for keyword arguments has been omitted.)<P>
|
||||||
|
|
||||||
|
</UL>
|
||||||
|
|
||||||
|
<P>In order to be able to support arbitrary argument lists, the
|
||||||
|
__call__ method first constructs a new argument tuple. Conveniently,
|
||||||
|
because of the notation *args in __call__'s own argument list, the
|
||||||
|
arguments to __call__ (except for self) are placed in the tuple args.
|
||||||
|
To construct the desired argument list, we concatenate a singleton
|
||||||
|
tuple containing the instance with the args tuple: (self.instance,) +
|
||||||
|
args. (Note the trailing comma used to construct the singleton
|
||||||
|
tuple.) In our example, the resulting argument tuple is (aninstance,
|
||||||
|
10).
|
||||||
|
|
||||||
|
<P>The intrinsic function apply() takes a function and an argument
|
||||||
|
tuple and calls the function for it. In our example, we are calling
|
||||||
|
apply(method1, (aninstance, 10)) which is equivalent to calling
|
||||||
|
method(aninstance, 10).
|
||||||
|
|
||||||
|
<P>From here on, things should come together quite easily. The output
|
||||||
|
of the example code is something like this:
|
||||||
|
|
||||||
|
<PRE>
|
||||||
|
calling <function method1 at ae8d8> for <Instance instance at 95ab0> with (10,)
|
||||||
|
calling <function method2 at ae900> for <Instance instance at 95ab0> with ()
|
||||||
|
the answer is 10
|
||||||
|
</PRE>
|
||||||
|
|
||||||
|
<P>That was about the shortest meaningful example that I could come up
|
||||||
|
with. A real tracing metaclass (for example, <A
|
||||||
|
HREF="#Trace">Trace.py</A> discussed below) needs to be more
|
||||||
|
complicated in two dimensions.
|
||||||
|
|
||||||
|
<P>First, it needs to support more advanced Python features such as
|
||||||
|
class variables, inheritance, __init__ methods, and keyword arguments.
|
||||||
|
|
||||||
|
<P>Second, it needs to provide a more flexible way to handle the
|
||||||
|
actual tracing information; perhaps it should be possible to write
|
||||||
|
your own tracing function that gets called, perhaps it should be
|
||||||
|
possible to enable and disable tracing on a per-class or per-instance
|
||||||
|
basis, and perhaps a filter so that only interesting calls are traced;
|
||||||
|
it should also be able to trace the return value of the call (or the
|
||||||
|
exception it raised if an error occurs). Even the Trace.py example
|
||||||
|
doesn't support all these features yet.
|
||||||
|
|
||||||
|
<P>
|
||||||
|
|
||||||
<HR>
|
<HR>
|
||||||
|
|
||||||
Confused already?
|
<H1>Real-life Examples</H1>
|
||||||
|
|
||||||
|
<P>Have a look at some very preliminary examples that I coded up to
|
||||||
<P>XXX More text is needed here. For now, have a look at some very
|
teach myself how to use metaprogramming:
|
||||||
preliminary examples that I coded up to teach myself how to use this
|
|
||||||
feature:
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<H2>Real-life Examples</H2>
|
|
||||||
|
|
||||||
<DL>
|
<DL>
|
||||||
|
|
||||||
|
|
@ -313,13 +443,13 @@ and ``Color.red + 1'' raise a TypeError exception.
|
||||||
|
|
||||||
<P>
|
<P>
|
||||||
|
|
||||||
<DT><A HREF="Trace.py">Trace.py</A>
|
<DT><A NAME=Trace></A><A HREF="Trace.py">Trace.py</A>
|
||||||
|
|
||||||
<DD>The resulting classes work much like standard classes, but by
|
<DD>The resulting classes work much like standard
|
||||||
setting a special class or instance attribute __trace_output__ to
|
classes, but by setting a special class or instance attribute
|
||||||
point to a file, all calls to the class's methods are traced. It was
|
__trace_output__ to point to a file, all calls to the class's methods
|
||||||
a bit of a struggle to get this right. This should probably redone
|
are traced. It was a bit of a struggle to get this right. This
|
||||||
using the generic metaclass below.
|
should probably redone using the generic metaclass below.
|
||||||
|
|
||||||
<P>
|
<P>
|
||||||
|
|
||||||
|
|
@ -338,13 +468,31 @@ hooks tough; we provide a similar hook _getattr_ instead.
|
||||||
<P>
|
<P>
|
||||||
|
|
||||||
<DT><A HREF="Eiffel.py">Eiffel.py</A>
|
<DT><A HREF="Eiffel.py">Eiffel.py</A>
|
||||||
|
ppp
|
||||||
<DD>Uses the above generic metaclass to implement Eiffel style
|
<DD>Uses the above generic metaclass to implement Eiffel style
|
||||||
pre-conditions and post-conditions.
|
pre-conditions and post-conditions.
|
||||||
|
|
||||||
<P>
|
<P>
|
||||||
|
|
||||||
|
<DT><A HREF="Synch.py">Synch.py</A>
|
||||||
|
|
||||||
|
<DD>Uses the above generic metaclass to implement synchronized
|
||||||
|
methods.
|
||||||
|
|
||||||
|
<P>
|
||||||
|
|
||||||
</DL>
|
</DL>
|
||||||
|
|
||||||
|
<P>A pattern seems to be emerging: almost all these uses of
|
||||||
|
metaclasses (except for Enum, which is probably more cute than useful)
|
||||||
|
mostly work by placing wrappers around method calls. An obvious
|
||||||
|
problem with that is that it's not easy to combine the features of
|
||||||
|
different metaclasses, while this would actually be quite useful: for
|
||||||
|
example, I wouldn't mind getting a trace from the test run of the
|
||||||
|
Synch module, and it would be interesting to add preconditions to it
|
||||||
|
as well. This needs more research. Perhaps a metaclass could be
|
||||||
|
provided that allows stackable wrappers...
|
||||||
|
|
||||||
</BODY>
|
</BODY>
|
||||||
|
|
||||||
</HTML>
|
</HTML>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue