[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