diff options
Diffstat (limited to 'pypers/meta/metatype.html')
-rwxr-xr-x | pypers/meta/metatype.html | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/pypers/meta/metatype.html b/pypers/meta/metatype.html new file mode 100755 index 0000000..f83b6bb --- /dev/null +++ b/pypers/meta/metatype.html @@ -0,0 +1,197 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<meta name="generator" content="Docutils 0.2.8: http://docutils.sourceforge.net/" /> +<title>SOLVING THE METACLASS CONFLICT</title> +<link rel="stylesheet" href="default.css" type="text/css" /> +</head> +<body> +<div class="document" id="solving-the-metaclass-conflict"> +<h1 class="title">SOLVING THE METACLASS CONFLICT</h1> +<div class="section" id="summary"> +<h1><a name="summary">Summary:</a></h1> +<p>Any serious user of metaclasses has been bitten at least once by the +infamous metaclass/metatype conflict. Here I give a general recipe to +solve the problem, as well as some theory and some examples.</p> +<blockquote> +<pre class="literal-block"> +#<noconflict.py> + +def _generatemetaclass(bases,metas): + """Internal function called by inferred. 'bases' is tuple of base classes + and 'metas' a tuple of metaclasses.""" + + metas=[m for m in metas if m is not type] + trivial=lambda m: m is type or m in metas + metabases=metas+[mb for mb in map(type,bases) if not trivial(mb)] + metaname="_"+''.join([m.__name__ for m in metabases]) + if not metabases: # trivial metabase + return type + elif len(metabases)==1: # single metabase + return metabases[0] + else: # multiple metabases + # creates new metaclass,shift possible conflict to meta-metaclasses + return type(metaname,tuple(metabases),{}) + +def memoize(f): + """This closure remembers all f invocations""" + argskw,result = [],[] + def _(*args,**kw): + akw=args,kw + try: # returns a previously stored result + return result[argskw.index(akw)] + except ValueError: # there is no previously stored result + argskw.append(akw) # update argskw + result.append(f(*args,**kw)) # update result + return result[-1] # return the new result + _.argskw=argskw #makes the argskw list accessible outside + _.result=result #makes the result list accessible outside + return _ + +_generatemetaclass=memoize(_generatemetaclass) + +def inferred(*metas): + """Class factory avoiding metatype conflicts: if the base classes have + metaclasses conflicting within themselves or with the given metaclass, + it automatically generates a compatible metaclass and instantiate the + inferred class from it.""" + return lambda n,b,d: _generatemetaclass(b,metas or (type,))(n,b,d) + +#</noconflict.py> +</pre> +</blockquote> +</div> +<div class="section" id="discussion"> +<h1><a name="discussion">Discussion</a></h1> +<p>I think that not too many programmers are familiar with metaclasses and +metatype conflicts, therefore let me be pedagogical ;)</p> +<p>The simplest case where a metatype conflict happens is the following. +Consider a class <tt class="literal"><span class="pre">A</span></tt> with metaclass <tt class="literal"><span class="pre">M_A</span></tt> and a class <tt class="literal"><span class="pre">B</span></tt> with +an independent metaclass <tt class="literal"><span class="pre">M_B</span></tt>; suppose we derive <tt class="literal"><span class="pre">C</span></tt> from <tt class="literal"><span class="pre">A</span></tt> +and <tt class="literal"><span class="pre">B</span></tt>. The question is: what is the metaclass of <tt class="literal"><span class="pre">C</span></tt> ? +Is it <tt class="literal"><span class="pre">M_A</span></tt> or <tt class="literal"><span class="pre">M_B</span></tt> ?</p> +<blockquote> +<pre class="doctest-block"> +>>> class M_A(type): +... pass +>>> class M_B(type): +... pass +>>> class A(object): +... __metaclass__=M_A +>>> class B(object): +... __metaclass__=M_B +>>> class C(A,B): +... pass +Traceback (most recent call last): + File "<stdin>", line 1, in ? +TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +</pre> +</blockquote> +<p>The correct answer (see the book "Putting metaclasses to work" for a +thought discussion) is <tt class="literal"><span class="pre">M_C</span></tt>, where <tt class="literal"><span class="pre">M_C</span></tt> is a metaclass that inherits +from <tt class="literal"><span class="pre">M_A</span></tt> and <tt class="literal"><span class="pre">M_B</span></tt>, as in the following graph, where instantiation +is denoted by colon lines:</p> +<blockquote> +<pre class="literal-block"> +M_A M_B + : \ / : + : \ / : + A M_C B + \ : / + \ : / + C +</pre> +</blockquote> +<p>However, Python is not that magic, and it does not automatically create +<tt class="literal"><span class="pre">M_C</span></tt>. Instead, it raises a <tt class="literal"><span class="pre">TypeError</span></tt>, warning the programmer of +the possible confusion. The metatype conflict can be avoided +by assegning the correct metaclass to <tt class="literal"><span class="pre">C</span></tt> by hand:</p> +<blockquote> +<pre class="doctest-block"> +>>> class M_AM_B(M_A,M_B): pass +... +>>> class C(A,B): +... __metaclass__=M_AM_B +>>> C,type(C) +(<class 'C'>, <class 'M_AM_B'>) +</pre> +</blockquote> +<p>In general, a class <tt class="literal"><span class="pre">A(B,</span> <span class="pre">C,</span> <span class="pre">D</span> <span class="pre">,</span> <span class="pre">...)</span></tt> can be generated without conflicts +only if <tt class="literal"><span class="pre">type(A)</span></tt> is a subclass of each of <tt class="literal"><span class="pre">type(B),</span> <span class="pre">type(C),</span> <span class="pre">...</span></tt></p> +<p>It is possible to automatically avoid conflicts, by defining a smart +class factory that generates the correct metaclass by looking at the +metaclasses of the base classes. This is done via the <tt class="literal"><span class="pre">inferred</span></tt> +class factory, wich internally invokes the <tt class="literal"><span class="pre">_generatemetaclass</span></tt> +function. When <tt class="literal"><span class="pre">_generatemetaclass</span></tt> is invoked with the same bases and +the same metaclasses it should return the same metaclass. This is done by +keeping a list of the generated metaclasses thanks to the <tt class="literal"><span class="pre">with_memory</span></tt> +closure. Now, when <tt class="literal"><span class="pre">_generatemetaclass</span></tt> is invoked with the same arguments +it returns the same metaclass.</p> +<blockquote> +<pre class="doctest-block"> +>>> import sys; sys.path.append('.') # for doctest purposes +>>> from noconflict import inferred +>>> class C(A,B): +... __metaclass__=inferred() +>>> C +<class 'C'> +>>> type(C) # automatically generated metaclass +<class 'noconflict._M_AM_B'> +</pre> +<pre class="doctest-block"> +>>> class M_A(type): pass +... +>>> class M_B(type): pass +... +>>> class A: __metaclass__=M_A +... +>>> class B: __metaclass__=M_B +... +>>> class E(A,B): +... __metaclass__=inferred() +>>> type(E) +<class 'noconflict._M_AM_B'> +>>> class F(A,B): +... __metaclass__=inferred() +>>> type(F) +<class 'noconflict._M_AM_B'> +>>> type(E) is type(F) +True +</pre> +</blockquote> +<p>Another example where <tt class="literal"><span class="pre">inferred()</span></tt> can solve the conflict is the +following:</p> +<blockquote> +<pre class="doctest-block"> +>>> class D(A): +... __metaclass__=M_B +Traceback (most recent call last): + File "<string>", line 1, in ? +TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +</pre> +</blockquote> +<p>Here the problem is that since <tt class="literal"><span class="pre">D</span></tt> inherits from <tt class="literal"><span class="pre">A</span></tt>, its metaclass +must inherit from <tt class="literal"><span class="pre">M_A</span></tt> and cannot be <tt class="literal"><span class="pre">M_B</span></tt>.</p> +<p><tt class="literal"><span class="pre">inferred</span></tt> solves the problem by automatically inheriting both from +<tt class="literal"><span class="pre">M_B</span></tt> and <tt class="literal"><span class="pre">M_A</span></tt>:</p> +<blockquote> +<pre class="doctest-block"> +>>> class D(A): +... __metaclass__=inferred(M_B) +>>> type(D) +<class 'noconflict._M_BM_A'> +</pre> +</blockquote> +<p>Note: these examples here have been checked with doctest on Python 2.3b1.</p> +</div> +</div> +<hr class="footer"/> +<div class="footer"> +<a class="reference" href="metatype.txt">View document source</a>. +Generated on: 2003-06-06 22:08 UTC. +Generated by <a class="reference" href="http://docutils.sourceforge.net/">Docutils</a> from <a class="reference" href="http://docutils.sourceforge.net/rst.html">reStructuredText</a> source. +</div> +</body> +</html> |