Reputation: 1539
I have the following basic class involving a circle. My question is, when I directly update the diameter with c.diameter(10), how come the radius is updated to 5? As the output shows, it is still at 4:
from math import pi
class Circle:
def __init__(self, radius=1):
self.radius = radius
self.diameter = self.radius * 2
def diameter(self, diameter):
self.diameter = diameter
self.radius = self.diameter / 2
return self.radius, self.diameter
def area(self):
return self.radius ** 2 * pi
c = Circle(3)
print("c.radius:", c.radius)
print("c.area():", c.area())
c.radius = 4 # change radius
print("c.radius:", c.radius)
print("c.area():", c.area())
c.diameter = 10 # change diameter
print("c.diameter:", c.diameter)
print("c.radius:", c.radius)
Output:
c.radius: 3
c.area(): 28.274333882308138
c.radius: 4
c.area(): 50.26548245743669
c.diameter: 10
c.radius: 4 <--- Radius should be 5, since the Radius is Diameter / 2
Upvotes: 0
Views: 10002
Reputation: 105
One way is to use properties with according setters to make sure you update your internal data:
Something like this:
from math import pi
class Circle:
def __init__(self, radius=1):
self._radius = radius
self._diameter = radius * 2
@property
def diameter(self):
return self._diameter
@diameter.setter
def diameter(self, diameter):
self._diameter = diameter
self._radius = diameter / 2
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, radius):
self._radius = radius
self._diameter = radius * 2
def area(self):
return self.radius ** 2 * pi
if __name__ == "__main__":
c = Circle(3)
print("c.radius:", c.radius)
print("c.area():", c.area())
c.radius = 4 # change radius
print("c.radius:", c.radius)
print("c.area():", c.area())
c.diameter = 10 # change diameter
print("c.diameter:", c.diameter)
print("c.radius:", c.radius)
Upvotes: 0
Reputation: 1121924
You have two things named diameter
:
self.diameter
in __init__
and with c.diameter = 10
later on.diameter
. This method is never accessed. You can't access it on the instance, because there the diameter
attribute masks the method.On Python classes, methods are still just attributes. c.area
returns the method object, and only c.area()
actually calls the method.
So, just referencing c.diameter
will not actually give you the method, it gives you the attribute on the instance, which is just an integer object.
You have two options:
set_diameter()
for example.diameter
attribute a property object. Properties are used as you would an attribute, but getting or setting the attribute on an instance triggers methods to be called.The latter option is the 'pythonic' option, the method experienced Python developers would use:
class Circle:
def __init__(self, radius=1):
self.radius = radius
@property
def diameter(self):
return self.radius * 2
@diameter.setter
def diameter(self, value):
self.radius = value / 2
def area(self):
return self.radius ** 2 * pi
The @property
/ @diameter.setter
pair of decorators define the getter and setter for the property; the first def diameter
is called whenever you want to read (get) the value of the attribute, and the second is used when writing called when you try to assign a new value (the setter):
>>> c = Circle(3)
>>> c.diameter
6
>>> c.diameter = 4
>>> c.radius
2.0
>>> c.diameter
4.0
Note that we never set an attribute named diameter
in the setter! The value is instead always calculated when you access c.diameter
for reading.
You'll also notice that when you assign an integer to the diameter
attribute, that the radius becomes a float
value; that's because the /
operator always produces a float value, even for integer inputs. Use //
(floor division), if you always must have an integer.
Upvotes: 3