mirror of
https://github.com/python/cpython.git
synced 2025-08-02 16:13:13 +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
|
||||
x = C()
|
||||
x.m1(12)
|
||||
x.m1(-1)
|
||||
## x.m1(-1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
_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">
|
||||
|
||||
<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)
|
||||
|
||||
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>
|
||||
|
||||
Confused already?
|
||||
<H1>Real-life Examples</H1>
|
||||
|
||||
|
||||
<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:
|
||||
|
||||
|
||||
|
||||
<H2>Real-life Examples</H2>
|
||||
<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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue