Why not an __assign__ method?

Roeland Rengelink r.b.rigilink at chello.nl
Fri Apr 6 04:15:40 EDT 2001


Carlos Alberto Reis Ribeiro wrote:

[extensively about a proposal to add a new magic method  __onassign__ to
solve a problem in arithmetic expressions]

Hi,

I've looked at another approach to solve the problem under
consideration. This approach seems to work and does not require a magic
__onassign__ method. It does require some refcount magic, which makes
this procedure somewhat unobvious.

Let me restate it again. We wish to evaluate the expression:

z = a+b+c

with only one copy and two in-place additions.
This is easy.

class A:
    def __init__(self, data):
        self.data = data
        self.is_temp = 0

    def __iadd__(self, other):
        print "Adding in place..."
        self.data += other.data
        return self

    def __add__(self, other):
        if self.is_temp:
            return self.__iadd__(other)
        else:
            return self.copy_to_temp().__iadd__(other)

    def copy_to_temp(self):
        print "Copying to temporary..."
        temp = self.__class__(copy.copy(self.data))
        temp.is_tmp = 1
        return tmp

with:

a = A(1)
b = A(2)
c = A(3)

d = a+b+c
print "Result :", d.data
print "d.is_temp :",d.is_temp
e = d+b+c 
print "Results e, d :", e.data, d.data
print "id(d), id(e) :", id(d), id(e)

We will get

Copying to temporary...
Adding in place...
Adding in place...
Result : 6
d.is_temp : 1
Adding in place...
Adding in place...
Results e, d : 11 11
id(d), id(e) : 135333068 135333068

Hence, the real problem is that the second evaluation (e=d+b+c) regards
d as a temporary which is subsequently modified in place, after which e
is bound to it.

Your proposal would add an __assign__ method to A, which does
self.is_temp = 0. I've proposed something similar in the past. However
another solution would be to modify __add__ as follows:

class A:
    ...
    def __add__(self, other):
        if self.is_temp and self.is_bound():
            self.is_temp = 0
        if self.is_temp:
            return self.__iadd__(other)
        else:
            return self.copy_to_temp().__iadd__(other)

So, how do we implement self.is_bound()

A foolish solution I attempted in the past is to search all namespaces
for a reference to self (except 'self' itself). which can easily become
more expensive than just doing  the extra copies.

I found a simpler solution:

import sys
class A:
    ...
    def __add__(self, other):
        if self.is_temp and sys.getrefcount(self) > 5:
            self.is_temp = 0
        if self.is_temp:
            return self.__iadd__(other)
        else:
            return self.copy_to_temp().__iadd__(other)

Compare the following to the previous result

Copying to temporary...
Adding in place...
Adding in place...
Result : 6
d.is_temp : 1
Copying to temporary...
Adding in place...
Adding in place...
Results e, d : 11 6
id(d), id(e) : 135330780 135105796

Note that we now do get the correct behaviour on the second evaluation
(e=d+b+c)

So, where does the magic 5 come from. Or, rather, how can an unbound
object have  a refcount of 5?

Let's modify A slightly to see what happens:

class A:
    ...
    def __add__(self, other):
        print "Refcount of self :", sys.getrefcount(self)
        if self.is_temp and sys.getrefcount(self) > 5:
            self.is_temp = 0
        if self.is_temp:
            return self.__iadd__(other)
        else:
            return self.copy_to_temp().__iadd__(other)

With:

a = A(1)
b = A(2)

c = A.__add__(A(1), b)     # equivalent to A(1).__add__(b)
c = A.__add__(a, b)
c = A(1)+b
c = a+b

we'll get

Refcount for self :  3
Refcount for self :  4
Refcount for self :  5
Refcount for self :  6

Looking at A.__add__(A(1), b), which results in a refcount of 3 for self
in __add__
The refcount comes from 
1 for A(1)
1 for assignment of A(1) to self
1 for the reference in sys.getrefcount(self)

Apparently the internal modification from a+b to a.__add__(b) adds
another two refcounts

It is clear that operations on unbound temporaries are equivalent to

c = A(1)+b

while opearations on bound temporaries are equivalent to

c = a+b

Hence, the number 5 in the test for 'boundedness'.

I think this solution solves the problem in arithmetic expressions. More
generally, this
refcount idiom provides a general method to test for boundedness.

Hope this help,

Roeland



More information about the Python-list mailing list