[Zope-Checkins] CVS: Zope - mrohell.py:1.1.2.1
Jim Fulton
cvs-admin at zope.org
Fri Oct 31 14:22:42 EST 2003
Update of /cvs-repository/Zope
In directory cvs.zope.org:/tmp/cvs-serv25841
Added Files:
Tag: mro-advanture-branch
mrohell.py
Log Message:
Utility modules with functions for analysing the effect of switching
from the classic method-lookup algorithm to the C3 algorithm.
This includes an implementation of the C3 algorithm.
=== Added File Zope/mrohell.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Sort out mro differences between EC and new-style classes
Find out if you have mro problems:
>>> base = mrohell.step1()
>>> mrohell.step2(base)
Utility functions:
step1()
Compute a baseline database of attribute-lookup results using old
method resolution.
For all extension classes, compute a dictionary
attr name -> class the attr comes from
Safe this in a file
step2(olddatafromstep1)
Compute new-style mros for each ec, where possible and the new
attr->class mapping. Compare the old mapping to the new and show
differences, if any.
Also report where we can't get an mro
oldmro(cls)
Compute an iterable for an old-style (left-to-right depth-first) mro
for a class.
mro(cls)
Compute a C3 mro
hell(cls)
Try to compute an mro. If one can't be computed, then generate lots
of debugging output to help you figure out what caused the
problem. (hehe)
red(cls)
Show cases where a base class is inherited through multiple paths
and show what the paths were.
$Id: mrohell.py,v 1.1.2.1 2003/10/31 19:22:41 jim Exp $
"""
import sys
import pprint
from ExtensionClass import ExtensionClass, Base
from types import ClassType
ClassTypes = ExtensionClass, type, ClassType
pformat = pprint.PrettyPrinter(width=160).pformat
def format(l):
return ' ' + '\n '.join(pformat(l).split('\n'))
def classname(cls):
return "%s.%s" % (getattr(cls, '__module__', ''), cls.__name__)
def oldmro(cls):
yield cls
seen = {}
for b in cls.__bases__:
for c in oldmro(b):
if c not in seen:
seen[c] = 1
yield c
def old_mapping(cls, result=None):
if result is None:
result = {}
clsname = classname(cls)
for k, v in cls.__dict__.iteritems():
if k not in result:
result[k] = clsname
for c in cls.__bases__:
old_mapping(c, result)
return result
class InconsistentHierarchy(Exception):
pass
def merge(seqs, blather=None):
if blather is None:
blather = []
blather.append('merge:')
blame = {}
for seq in seqs:
if seq:
blame[id(seq)] = classname(seq[0])
blather.append(format([classname(c) for c in seq]))
res = []
i=0
while 1:
nonemptyseqs = [seq for seq in seqs if seq]
if not nonemptyseqs:
return res
i += 1
blather.append('\nRound %s' % i)
for seq in nonemptyseqs:
# find merge candidates among seq heads
cand = seq[0]
blather.append('\n Try: %s (%s)'
% (classname(cand), blame[id(seq)]))
blather.append(format([classname(c) for c in seq]))
nothead = [s for s in nonemptyseqs if cand in s[1:]]
if nothead:
blather.append('\n Reject: ')
for l in nothead:
blather.append(' %s' % blame[id(l)])
blather.append(format([classname(c) for c in l]))
# reject candidate
cand = None
else:
blather.append(' Accepted')
break
if not cand:
raise InconsistentHierarchy(nonemptyseqs, blather)
res.append(cand)
blather.append('\n result:')
blather.append(format([classname(c) for c in res]))
for seq in nonemptyseqs:
# remove cand
if seq[0] == cand:
del seq[0]
def mro(C):
"Compute the class precedence list (mro) according to C3"
return merge([[C]] +
[mro(base) for base in C.__bases__] +
[list(C.__bases__)], ["MRO for %s" % classname(C)])
def imp(c):
if c == ".Base":
return Base
c = c.split('.')
m, n = '.'.join(c[:-1]), c[-1]
return getattr(sys.modules[m], n)
def hell(c):
if isinstance(c, str):
c = imp(c)
try: mro(c)
except InconsistentHierarchy, v:
print '\n'.join(v.args[1])
def new_mapping(cls):
m = mro(cls)
result = {}
for c in m:
clsname = classname(c)
for k, v in c.__dict__.iteritems():
if k not in result:
result[k] = clsname
return result
def all_classes(types=ClassTypes):
seen = {}
for mod in sys.modules.itervalues():
for n in dir(mod):
v = getattr(mod, n)
if isinstance(v, types):
if not v in seen:
seen[v] = 1
yield v
def step1():
results = {}
for cls in all_classes():
results[classname(cls)] = old_mapping(cls)
return results
def dictdiff(d1, d2):
result = []
for k, v1 in d1.iteritems():
v2 = d2.get(k)
if v1 != v2:
result.append((k, v1, v2))
result.sort()
return result
def diffir(d1, d2):
result = []
for k, v1 in d1.iteritems():
v2 = d2.get(k)
if v1 != v2:
if v2 is not None:
try:
c1 = imp(v1)
c2 = imp(v2)
except:
pass
else:
if issubclass(c2, c1):
# The new class is more specific
continue
result.append((k, v1, v2))
result.sort()
return result
difffmt = " %30s %50s %50s"
def step2(original, ignore=(), mroonly=False, ignorerefine=False):
badmro = maddict = n = 0
if ignorerefine:
difff = diffir
else:
difff = dictdiff
for cls in all_classes(ExtensionClass):
n += 1
clsname = classname(cls)
try:
new = new_mapping(cls)
except InconsistentHierarchy:
print "Couldn't get mro for ", clsname
badmro += 1
else:
try:
old = original[clsname].copy()
except KeyError:
if not mroonly:
print "New EC", clsname
continue
for k in ignore:
try:
del old[k]
del new[k]
except KeyError:
pass
if new != old:
diff = difff(old, new)
if diff:
maddict += 1
if not mroonly:
print
print 'For class', clsname
print difffmt % ("attribute", "came from", "now from")
for k, v1, v2 in diff:
print difffmt % (k, v1, v2)
print n, badmro, maddict
def ipaths(cls, prefix, result):
paths = result.setdefault(cls, [])
paths.append(prefix)
for b in cls.__bases__:
ipaths(b, prefix+(classname(cls),), result)
return result
def red(cls):
"""Report redundant inheritence of base classes
"""
if isinstance(cls, str):
cls = imp(cls)
for c, paths in ipaths(cls, (), {}).iteritems():
if len(paths) > 1:
print classname(c)
print format(paths)
More information about the Zope-Checkins
mailing list