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