summaryrefslogtreecommitdiff
path: root/pypers/meta/metatype.html
diff options
context:
space:
mode:
Diffstat (limited to 'pypers/meta/metatype.html')
-rwxr-xr-xpypers/meta/metatype.html197
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">
+#&lt;noconflict.py&gt;
+
+def _generatemetaclass(bases,metas):
+ &quot;&quot;&quot;Internal function called by inferred. 'bases' is tuple of base classes
+ and 'metas' a tuple of metaclasses.&quot;&quot;&quot;
+
+ 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=&quot;_&quot;+''.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):
+ &quot;&quot;&quot;This closure remembers all f invocations&quot;&quot;&quot;
+ 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):
+ &quot;&quot;&quot;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.&quot;&quot;&quot;
+ return lambda n,b,d: _generatemetaclass(b,metas or (type,))(n,b,d)
+
+#&lt;/noconflict.py&gt;
+</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">
+&gt;&gt;&gt; class M_A(type):
+... pass
+&gt;&gt;&gt; class M_B(type):
+... pass
+&gt;&gt;&gt; class A(object):
+... __metaclass__=M_A
+&gt;&gt;&gt; class B(object):
+... __metaclass__=M_B
+&gt;&gt;&gt; class C(A,B):
+... pass
+Traceback (most recent call last):
+ File &quot;&lt;stdin&gt;&quot;, 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 &quot;Putting metaclasses to work&quot; 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">
+&gt;&gt;&gt; class M_AM_B(M_A,M_B): pass
+...
+&gt;&gt;&gt; class C(A,B):
+... __metaclass__=M_AM_B
+&gt;&gt;&gt; C,type(C)
+(&lt;class 'C'&gt;, &lt;class 'M_AM_B'&gt;)
+</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">
+&gt;&gt;&gt; import sys; sys.path.append('.') # for doctest purposes
+&gt;&gt;&gt; from noconflict import inferred
+&gt;&gt;&gt; class C(A,B):
+... __metaclass__=inferred()
+&gt;&gt;&gt; C
+&lt;class 'C'&gt;
+&gt;&gt;&gt; type(C) # automatically generated metaclass
+&lt;class 'noconflict._M_AM_B'&gt;
+</pre>
+<pre class="doctest-block">
+&gt;&gt;&gt; class M_A(type): pass
+...
+&gt;&gt;&gt; class M_B(type): pass
+...
+&gt;&gt;&gt; class A: __metaclass__=M_A
+...
+&gt;&gt;&gt; class B: __metaclass__=M_B
+...
+&gt;&gt;&gt; class E(A,B):
+... __metaclass__=inferred()
+&gt;&gt;&gt; type(E)
+&lt;class 'noconflict._M_AM_B'&gt;
+&gt;&gt;&gt; class F(A,B):
+... __metaclass__=inferred()
+&gt;&gt;&gt; type(F)
+&lt;class 'noconflict._M_AM_B'&gt;
+&gt;&gt;&gt; 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">
+&gt;&gt;&gt; class D(A):
+... __metaclass__=M_B
+Traceback (most recent call last):
+ File &quot;&lt;string&gt;&quot;, 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">
+&gt;&gt;&gt; class D(A):
+... __metaclass__=inferred(M_B)
+&gt;&gt;&gt; type(D)
+&lt;class 'noconflict._M_BM_A'&gt;
+</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>