Reputation: 2132
I am trying to learn about bound methods in python and have implemented the code below:
class Point:
def __init__(self, x,y):
self.__x=x
self.__y=y
def draw(self):
print(self.__x, self.__y)
def draw2(self):
print("x",self.__x, "y", self.__y)
p1=Point(1,2)
p2=Point(3,4)
p1.draw()
p2.draw()
p1.draw=draw2
p1.draw(p1)
When I run this code, the following output is produced:
1 2
3 4
Traceback (most recent call last):
File "main.py", line 17, in <module>
p1.draw(p1)
File "main.py", line 10, in draw2
print("x",self.__x, "y", self.__y)
AttributeError: 'Point' object has no attribute '__x'
Why is it not possible for me to change p1.draw() after changing it so that it points at draw2?
Upvotes: 7
Views: 2697
Reputation: 1360
You have 2 issues with your code.
Using double-underscore fields, causing their name to be "mangled". In other words, when you specify field __x
in class Point
its name is actually _Point__x
. The point is to enable accessing this field only from within the same class (and then it will work). You have 2 options fixing that - access this field from outside using __Point_x
or simply rename it to a single-underscore field (_x
).
Your re-bounding is wrong. You should replace it on the class and not the specific object. The way you are doing it now, you have to invoke explicitly passing self
: p1.draw(p1)
. If however you would do:
Point.draw = draw2
p1.draw() # works as expected
Upvotes: 1
Reputation: 78690
Well, that's what you get for trying to enforce privacy in Python. ;)
Outside the body of the class, you must refer to the attributes __x
and __y
as _Point__x
and _Point__y
because of name mangling.
If you change the two attributes to non-mangled names (e.g. _x
and _y
) or use the names _Point__x
and _Point__y
in draw2
, your code won't throw an error.
In my opinion you should think thrice before using mangled names. Write proper docstrings, but don't restrict the user of your class in such an annoying manner. Using single underscore names is well understood as "don't touch this" in the community.
As you already seem to have noticed, p1.draw
behaves differently after your monkey patch because draw2
is not a bound method of the instance p1
, so you need to pass p1
explicitly as the argument. I suggest that you bind the instance p1
to draw2
by leveraging the function's descriptor protocol before you reassign the name draw
.
Putting everything together, the code
class Point:
def __init__(self, x,y):
self._x=x
self._y=y
def draw(self):
print(self._x, self._y)
def draw2(self):
print("x",self._x, "y", self._y)
p1 = Point(1,2)
p2 = Point(3,4)
p1.draw()
p2.draw()
p1.draw = draw2.__get__(p1)
p1.draw()
produces the output
1 2
3 4
x 1 y 2
where draw2.__get__(p1)
produces a callable that behaves like draw2
, but automatically passes p1
as the first argument.
Upvotes: 14
Reputation: 11073
This is because of __x
that you have defined, as you see if you just:
p1=Point(1,2)
p1.__x
it says that 'Point' object has no attribute '__x'
again. and so this problem is not because of your draw function. it is for your attributes. so if you define your class like this:
class Point:
def __init__(self, x,y):
self.x=x
self.y=y
def draw(self):
print(self.x, self.y)
def draw2(self):
print("x",self.x, "y", self.x)
this will work just fine.
additionally, if you go with your fist implementation, with dir
function you can see that __x
and __y
is accessible like _Point__x
and _Point__y
. so you can also do this:
class Point:
def __init__(self, x,y):
self.__x=x
self.__y=y
def draw(self):
print(self.__x, self.__y)
def draw2(self):
print("x",self._Point__x, "y", self._Point__y)
Note that using
__
is to define a private attributes.
Upvotes: 1
Reputation: 9858
Double underscores cause Python to 'mangle' the name of the attribute. It will actually be stored as _Point__x
instead of __x
. If you change your function like this, it will work:
def draw2(self):
print("x",self._Point__x, "y", self._Point__y)
The double underscores are supposed to indicate a variable that is private to the class and should not be accessed outside it. Name mangling makes it more difficult to do so accidentally.
Upvotes: 2