overload builtin operator

Bengt Richter bokr at oz.net
Sun Aug 28 00:09:10 EDT 2005


On Thu, 25 Aug 2005 16:12:20 +0200, Reinhold Birkenfeld <reinhold-birkenfeld-nospam at wolke7.net> wrote:

>Shaun wrote:
>> Hi,
>> 
>> I'm trying to overload the divide operator in python for basic arithmetic.
>> eg. 10/2 ... no classes involved.
>> 
>> I am attempting to redefine operator.__div__ as follows:
>> 
>>      # my divide function
>>      def safediv(a,b):
>>          return ...
>> 
>>      # reassign buildin __div__
>>      import operator
>>      operator.__div__ = safediv
>> 
>> The operator.__dict__ seems to be updated OK but the '/' operator still  
>> calls buildin __div__
>
>It won't work that way. You cannot globally modify the behaviour of an operator,
>but you can customize how an operator works for your type.
>
>Consider:
>
>class safeint(int):
>    def __div__(self, other):
>        return safediv(self, other)
>
>safeint(10)/2
>
You are right that you cannot globally modify the behaviour of an operator in the
sense the OP seems to be envisioning, but with some trouble I think it would be possible
to interfere with the translation of '<numerator-term>/<denominator-term>' to become
'safediv(<numerator-term>, <denominator-term>)' by transforming the AST during a custom
import process, such that wherever a Div node is found, a CallFunc node is substituted. E.g.,

for a node like

    Div((Name('numerator'), Name('denominator')))

substitute another node like

    CallFunc(Name('safediv'), [Name('numerator'), Name('denominator')], None, None)

where the Name('numerator') and Name('denominator') in the latter are actually
the Div node's .left and .right, so as to operate on the same args (which can
be abitrary expression-representing subtrees).

Of course, you probably also want to replace

    AugAssign(Name('a'), '/=', Name('b'))

with

    Assign([AssName('a', 'OP_ASSIGN')], CallFunc(Name('safediv'), [Name('a'), Name('b')], None, None))

Unfortunately, the AST tree does not seem to be designed to be edited easily wrt
_replacing_ nodes found by walking via flattened lists vs. just _using_ them in order.

I think I can create special walker that can do replacements of nodes found anywhere, and
calling node-type-specific or default supplied callbacks to supply replacements for original nodes
passed to them, but this will require a number of specialized visitor methods etc., so I will
have to get back to this later. But at least the OP and you nudged me into thinking about
AST rewriting again ;-) (also affects my @@sourcedeco ideas ;-)

... this didn't go out due to news server problem, so I'll add some clips from what I did re ast editing:

----< testdiv.py >-------------------------------------------------------------------------
def test():
    print 1.0/2.0
    print 12/3
    a=12; b=3
    print a/b
    print 2**a/(b+1)
    try:
        print 'print 1/0 =>'
        print 1/0
    except Exception, e: print '%s: %s' %(e.__class__.__name__, e)
    try:
        print 'print a/(b*(a-12)) =>'
        print a/(b*(a-12))
    except Exception, e: print '%s: %s' %(e.__class__.__name__, e)
    try:
        print 'def foo(x=1/(b-3)): return x =>'
        def foo(x=1/(b-3)): return x
        print 'print foo() =>'
        print foo()
    except Exception, e: print '%s: %s' %(e.__class__.__name__, e)
    try:
        print 'b /= (a-12) =>'
        b /= (a-12)
        print 'b after b/=(a-12):', b
    except Exception, e: print '%s: %s' %(e.__class__.__name__, e)
    
if __name__ == '__main__': test()
-------------------------------------------------------------------------------------------
Outputfrom run without conversion of / :

0.5
4
4
1024
print 1/0 =>
ZeroDivisionError: integer division or modulo by zero
print a/(b*(a-12)) =>
ZeroDivisionError: integer division or modulo by zero
def foo(x=1/(b-3)): return x =>
ZeroDivisionError: integer division or modulo by zero
b /= (a-12) =>
ZeroDivisionError: integer division or modulo by zero


----< import_safediv.py >------------------------------------------------------------------
# import_safediv.py
from importast import importast
from compiler.ast import Div, CallFunc, Name
from compiler.ast import AugAssign, Assign, AssName

def safediv(num, den):
    if den==0: result = 0*num
    else: result = num/den
    print 'safediv(%r, %r) => %r'%(num, den, result)
    return result

def div2safediv(divnode):
    """replace Div nodes with CallFunc nodes calling safediv with same args"""
    return  CallFunc(Name('safediv'),[divnode.left, divnode.right], None, None, divnode.lineno)

def divaugass2safediv(auganode):
    if auganode.op != '/=': return auganode
    assname =auganode.node.name
    return Assign([AssName(assname, 'OP_ASSIGN')],
        CallFunc(Name('safediv'),[Name(assname), auganode.expr], None, None, auganode.lineno))

callbacks = {Div:div2safediv, AugAssign:divaugass2safediv}
def import_safediv(modname, verbose=False):
    return importast(modname, callbacks, verbose)

if __name__ == '__main__':
    import sys
    modname, args = sys.argv[1], sys.argv[2:]
    verbose = (args[0:] and args[0]=='-verbose') or False
    modsafe = import_safediv(modname, verbose)
    modsafe.safediv = safediv
    if hasattr(modsafe, 'test'): modsafe.test()
-------------------------------------------------------------------------------------------

Result from running the above and specifying testdiv as the module
to import and enforce safediv on, and run test() on:

safediv(1.0, 2.0) => 0.5
0.5
safediv(12, 3) => 4
4
safediv(12, 3) => 4
4
safediv(4096, 4) => 1024
1024
print 1/0 =>
safediv(1, 0) => 0
0
print a/(b*(a-12)) =>
safediv(12, 0) => 0
0
def foo(x=1/(b-3)): return x =>
safediv(1, 0) => 0
print foo() =>
0
b /= (a-12) =>
safediv(3, 0) => 0
b after b/=(a-12): 0

Note that the exceptions went away ;-)

It was much messier than it should have been, and the code is not very efficient,
but at least it's a kind of proof of concept ;-)

Regards,
Bengt Richter



More information about the Python-list mailing list