File: src/multimethods.py

1 ################################################################################ 2 # File: dist/srcb-0.20050801/src/multimethods.py 3 # Version: $Id: multimethods.py,v 1.3 2005/06/11 08:44:13 vyzo Exp $ 4 # Content: multimethods 5 # 6 # Copyright (C) 2005 Dimitris Vyzovits [vyzo@media.mit.edu] 7 # 8 # This program is free software; you can redistribute it and/or 9 # modify it under the terms of the GNU General Public License 10 # as published by the Free Software Foundation; either version 2 11 # of the License, or (at your option) any later version. 12 # 13 # This program is distributed in the hope that it will be useful, 14 # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 # GNU General Public License for more details. 17 # 18 # You should have received a copy of the GNU General Public License 19 # along with this program; if not, write to the Free Software 20 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 # MA 02110-1301, USA 22 # 23 # 24 # Note: This file is part of the Peers Project 25 # Note: http://viral.media.mit.edu/peers 26 # Note: Peers Id: multimethod.py,v 1.3 2005/05/21 03:08:46 vyzo Exp 27 ################################################################################ 28 29 ##%> (:alias (:module srcb.multimethods) 'multimethods) 30 ##%% Multimethods for python objects 31 32 class multimethod_dispatch( object ): 33 def __init__( self, name ): 34 self.name = name 35 self.methods = {} 36 self.instance_class = None 37 38 def __call__( self, *args ): 39 ## unbound method behavior 40 ## look for static methods first 41 method = self.lookup( args ) 42 if method is None: 43 ## instance methods, first arg must be an instance of 44 ## the instance_class 45 if (self.instance_class is not None) \ 46 and (len(args) > 0) \ 47 and isinstance( args[0], self.instance_class ): 48 method = self.lookup( args[1:] ) 49 if method is None: 50 sig = tuple( x.__class__.__name__ for x in args ) 51 raise TypeError, "signature mismatch: %s" % (sig,) 52 return method( *args ) 53 54 def __repr__( self ): 55 return "<multimethod %s: %s>" % (self.name, self.methods) 56 57 def register( self, method, sig ): 58 if sig in self.methods: 59 raise TypeError, "duplicate signature" 60 self.methods[sig] = method 61 62 def lookup( self, args ): 63 def cmp_type( a, b ): 64 for x in xrange( len( a ) ): 65 if issubclass( a[x], b[x] ): return -1 66 elif issubclass( b[x], a[x] ): return 1 67 else: 68 v = cmp( a[x], b[x] ) 69 if v: return v 70 return 0 71 72 ## try perfect match first 73 sig = tuple( x.__class__ for x in args ) 74 75 method = self.methods.get( sig ) 76 if method is None: 77 ## try partial matches 78 partial = filter( lambda xsig: len(xsig) == len( sig ), 79 self.methods.keys() ) 80 ## sort the partial methods by specificity in the number 81 ## of object base-classes to allow contravariance. 82 ## The default cmp does A < B if B is subclass of A 83 ## while it does anything < object 84 ## The latter ensures that for generic object cmp does 85 ## the right thing, but it doesnt for the general contravariance 86 ## case. Hence we sort with cmp_type 87 partial.sort( cmp_type ) 88 for xsig in partial: 89 match = True 90 for x in xrange( len( xsig ) ): 91 if not isinstance( args[x], xsig[x] ): 92 match = False 93 break 94 if match: 95 method = self.methods[xsig] 96 break 97 98 return method 99 100 def bind( self, obj ): 101 class bound_method( object ): 102 def __init__( self, dispatch ): 103 self.dispatch = dispatch 104 105 def __call__( self, *args ): 106 method = self.dispatch.lookup( args ) 107 if method is None: 108 sig = tuple( x.__class__.__name__ for x in args ) 109 raise TypeError, "signature mismatch %s" % (sig,) 110 return method( obj, *args ) 111 112 def __repr__( self ): 113 desc = (self.dispatch.instance_class, 114 self.dispatch.name, 115 self.dispatch.methods) 116 return "<bound multimethod %s.%s: %s>" % desc 117 118 self.instance_class = obj.__class__ 119 setattr( obj, self.name, bound_method( self ) ) 120 121 ##%> (:class multimethods.multimethod) 122 ##%% Decorator class for creating multimethods. 123 ##%% Instances of the class decorate methods with the signature as arguments. 124 ##%% The method dispatch uses best match on the argument types. 125 ##%% <p/>See @srcb.interp.interpreter and @srcb.interp.interpreter.eval 126 ##%% for an example of usage. 127 ##%> (:~see concepts.multimethod) 128 class multimethod( object ): 129 def __init__( self ): 130 self.dispatch = {} 131 132 def __call__( self, *sig ): 133 def register( method ): 134 name = method.__name__ 135 dispatch = self.dispatch.get( name ) 136 if dispatch is None: 137 dispatch = multimethod_dispatch( name ) 138 self.dispatch[name] = dispatch 139 140 dispatch.register( method, sig ) 141 return dispatch 142 143 return register 144 145 def bind( self, obj ): 146 for method in self.dispatch.itervalues(): 147 method.bind( obj )