summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuido van Rossum <guido@python.org>1997-08-26 00:08:51 (GMT)
committerGuido van Rossum <guido@python.org>1997-08-26 00:08:51 (GMT)
commit0cdb88767647cc4a684cdb61b9fd9aa9a89d75cb (patch)
treed159aaa8a69f2e94ca083277d6782a43b6c61fa7
parent910105515ebe340fafe357f8fe1b898787a292a0 (diff)
downloadcpython-0cdb88767647cc4a684cdb61b9fd9aa9a89d75cb.zip
cpython-0cdb88767647cc4a684cdb61b9fd9aa9a89d75cb.tar.gz
cpython-0cdb88767647cc4a684cdb61b9fd9aa9a89d75cb.tar.bz2
Completed first draft.
-rw-r--r--Demo/metaclasses/Eiffel.py2
-rw-r--r--Demo/metaclasses/Simple.py45
-rw-r--r--Demo/metaclasses/Synch.py256
-rw-r--r--Demo/metaclasses/index.html182
4 files changed, 467 insertions, 18 deletions
diff --git a/Demo/metaclasses/Eiffel.py b/Demo/metaclasses/Eiffel.py
index e3efa7f..38ee6bc 100644
--- a/Demo/metaclasses/Eiffel.py
+++ b/Demo/metaclasses/Eiffel.py
@@ -107,7 +107,7 @@ def _test():
assert Result > arg
x = C()
x.m1(12)
- x.m1(-1)
+## x.m1(-1)
if __name__ == '__main__':
_test()
diff --git a/Demo/metaclasses/Simple.py b/Demo/metaclasses/Simple.py
new file mode 100644
index 0000000..03ed259
--- /dev/null
+++ b/Demo/metaclasses/Simple.py
@@ -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()
diff --git a/Demo/metaclasses/Synch.py b/Demo/metaclasses/Synch.py
new file mode 100644
index 0000000..1fb9160
--- /dev/null
+++ b/Demo/metaclasses/Synch.py
@@ -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()
diff --git a/Demo/metaclasses/index.html b/Demo/metaclasses/index.html
index 378ceb3..269dc69 100644
--- a/Demo/metaclasses/index.html
+++ b/Demo/metaclasses/index.html
@@ -6,9 +6,9 @@
<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
HREF="http://grail.cnri.reston.va.us/python/1.5a3/">restricted alpha
@@ -267,7 +267,7 @@ class Instance:
value = self.__klass__.__namespace__[name]
except KeyError:
raise AttributeError, name
- if type(value) is not types.FuncType:
+ if type(value) is not types.FunctionType:
return value
return BoundMethod(value, self)
@@ -276,20 +276,150 @@ class BoundMethod:
self.function = function
self.instance = instance
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)
-<HR>
-Confused already?
+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>
-<P>XXX More text is needed here. For now, have a look at some very
-preliminary examples that I coded up to teach myself how to use this
-feature:
+<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>
-<H2>Real-life Examples</H2>
+<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>
+
+<H1>Real-life Examples</H1>
+
+<P>Have a look at some very preliminary examples that I coded up to
+teach myself how to use metaprogramming:
<DL>
@@ -313,13 +443,13 @@ and ``Color.red + 1'' raise a TypeError exception.
<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
-setting a special class or instance attribute __trace_output__ to
-point to a file, all calls to the class's methods are traced. It was
-a bit of a struggle to get this right. This should probably redone
-using the generic metaclass below.
+<DD>The resulting classes work much like standard
+classes, but by setting a special class or instance attribute
+__trace_output__ to point to a file, all calls to the class's methods
+are traced. It was a bit of a struggle to get this right. This
+should probably redone using the generic metaclass below.
<P>
@@ -338,13 +468,31 @@ hooks tough; we provide a similar hook _getattr_ instead.
<P>
<DT><A HREF="Eiffel.py">Eiffel.py</A>
-
+ppp
<DD>Uses the above generic metaclass to implement Eiffel style
pre-conditions and post-conditions.
<P>
+
+<DT><A HREF="Synch.py">Synch.py</A>
+
+<DD>Uses the above generic metaclass to implement synchronized
+methods.
+
+<P>
+
</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>
</HTML>