I want to understand how the class attributes are manipulated here for example please look the sample code below
class Rectangle():
length = 0
breadth = 0
r1 = Rectangle()
#now change the value
r1.length = 20
r1.breadth = 30
print("r1.length called class again = ", r1.length) #output r1.length = 20
print("r1.breadth called class again = ", r1.breadth) #output r1.breadth = 30
# now call the class again then **print it should give zero**
r1 = Rectangle()
print("r1.length called class again = ", r1.length) #output r1.length = 0
print("r1.breadth called class again = ", r1.breadth) #output r1.breadth = 0
But here I got confused while doing the same things but in the code I just created or designed class inside class. please check the below sample code...
class Shape():
class Rectangle():
length = 0
breadth = 0
r1 = Shape()
r1.Rectangle.breadth = 40
r1.Rectangle.length = 50
print(r1.Rectangle.breadth) #output 40
print(r1.Rectangle.length) #output 50
r1 = Shape()
print(r1.Rectangle.breadth) #output 40 **How come it is giving 40 ?**
print(r1.Rectangle.length) #output 50 **How come it is giving 50 ?**
Now tell me what is the exact difference here. To understand little more check the star words in the comments code.
CodePudding user response:
What's happening here:
class Rectangle():
length = 0
breadth = 0
r1 = Rectangle()
#now change the value
r1.length = 20
r1.breadth = 30
is that you are first defining class attributes Rectangle.length
and Rectangle.breadth
, and then you are shadowing those attributes with instance attributes that share the same names by rebinding the names r1.length
and r1.breadth
. This is confusing and unnecessary. To define those attributes as instance attributes up front, you'd do:
class Rectangle():
def __init__(self):
self.length = 0
self.breadth = 0
r1 = Rectangle()
r1.length = 20
r1.breadth = 30
In this code:
class Shape():
class Rectangle():
length = 0
breadth = 0
r1 = Shape()
r1.Rectangle.breadth = 40
the extra level of nesting/indirection is adding extra confusion. r1
is a Shape
instance. r1.Rectangle
is a reference to Shape.Rectangle
, i.e. a class attribute of Shape
, which is itself a class with its own class attributes. Because you aren't rebinding r1.Rectangle
(just accessing one of its attributes), you aren't shadowing it to create a new instance attribute -- you're actually modifying the class attribute breadth
of the class Shape.Rectangle
. Since this is a class attribute (within a class that is itself a class attribute of another class -- see how this is unnecessarily confusing?), the change persists when you access it via another instance.
It is not recommended to nest classes in this way; you are getting nothing useful from doing that, aside from amplifying the confusion between class and instance attributes. Just define a Rectangle
class with instance attributes. Aside from the earlier example I gave where those attributes are defined in an __init__
method, my recommendation would be to use a dataclass
, e.g.:
from dataclasses import dataclass
@dataclass
class Rectangle:
length: int
breadth: int
r1 = Rectangle(length=20, breadth=30)
CodePudding user response:
If you're familiar with Java, then Shape.Rectangle
(or in your example, r1.Rectangle
) here is essentially the same as a static class within Shape
. It is a class, not an instance. In Python, there isn't really such a thing as instance-bound subclasses. What you're writing here is essentially equivalent to
class Shape():
pass
class Rectangle():
length = 0
breadth = 0
with the only difference being that you can access the static attributes of Rectangle
immediately instead of Shape.Rectangle
or an instance shape.Rectangle
.
CodePudding user response:
In the natural order of things once you have set the class variables of Rectangle to be 40 for breadth and 50 for length it will give the same output to any new instance of the Shape class
. Below I added another new instance r2
to show you that once the variables are set to some value it will stay there with the flow of the program
class Shape():
class Rectangle():
length = 0
breadth = 0
r1 = Shape()
r1.Rectangle.breadth = 40
r1.Rectangle.length = 50
print(r1.Rectangle.breadth) #output 40
print(r1.Rectangle.length) #output 50
r1 = Shape()
print(r1.Rectangle.breadth) #output 40 **How come it is giving 40 ?**
print(r1.Rectangle.length) #output 50 **How come it is giving 50 ?**
r2 = Shape()
print(r2.Rectangle.breadth) #output 40
print(r2.Rectangle.length) #output 50
output for all prints:
40
50
40
50
40
50
CodePudding user response:
You appearing to be confusing class and instance attributes.
By declaring the attributes in the way that you have for the Rectangle
class, you are creating class attributes, which allow these attributes to be accessed at the class level:
class Rectangle:
length = 0
breadth = 0
print(Rectangle.length) # 0
print(Rectangle.breadth) # 0
When creating instances of these classes, the class attributes mutate to be instance attributes, thereby making them accessible at the instance level:
r = Rectangle()
print(r.length) # 0
Python allows you to add new properties directly to instances of classes even if those properties were not in the class declaration:
class Thing:
pass
thing = Thing()
print(hasattr(thing, "my_attribute")) # False
thing.my_attribute = "works on instances"
print(hasattr(thing, "my_attribute")) # True
print(thing.my_attribute) # works on instances
But Python also allows you to add attributes to classes after class declaration:
class Thing:
pass
print(hasattr(Thing, "my_attribute")) # False
Thing.my_attribute = "and works on the class"
print(hasattr(Thing, "my_attribute")) # True
print(Thing.my_attribute) # and works on the class
This is what is resulting in the behaviour you're seeing with your Shape
class example.