Strange issue with Python class inheritance - python

I'm getting an issue with class inheritance in Python that I can't seem to make sense of. It may just be my inexperience with Python.
I was able to replicate the issue with this example (I'm using Python 3.3.4):
class R(object):
def __init__(self, x, y, w, h):
self._R = [x, y, w, h]
#property
def x(self):
return self._R[0]
#x.setter
def x(self, value):
self._R[0] = value
#property
def y(self):
return self._R[1]
#y.setter
def y(self, value):
self._R[1] = value
#property
def width(self):
return self._R[2]
#width.setter
def width(self, value):
self._R[2] = value
#property
def height(self):
return self._R[3]
#height.setter
def height(self, value):
self._R[3] = value
class Base(object):
def __init__(self):
self.pos = (0, 0)
class A(Base):
def __init__(self):
Base.__init__(self)
self.rect = R(0, 0, 0, 0)
#property
def pos(self):
return (self.rect.x, self.rect.y)
#pos.setter
def pos(self, value):
(self.rect.x, self.rect.y) = value
class B(A):
def __init__(self):
A.__init__(self)
self.foo = "bar"
o = B()
o.pos = (50, 50)
which produces the following error:
Traceback (most recent call last):
File "main.py", line 52, in <module>
o = B()
File "main.py", line 49, in __init__
A.__init__(self)
File "main.py", line 37, in __init__
Base.__init__(self)
File "main.py", line 33, in __init__
self.pos = (0, 0)
File "main.py", line 45, in pos
(self.rect.x, self.rect.y) = value
AttributeError: 'B' object has no attribute 'rect'

You are trying to use the self.pos setter before self.rect is set:
class Base(object):
def __init__(self):
self.pos = (0, 0)
class A(Base):
def __init__(self):
Base.__init__(self)
self.rect = R(0, 0, 0, 0)
Because self.pos on A is a property, trying to set self.pos requires self.rect to already be set.
move the Base.__init__(self) call down:
class A(Base):
def __init__(self):
self.rect = R(0, 0, 0, 0)
Base.__init__(self)
Now self.rect is set by the time Base.__init__ tries to assign a value to self.pos.

i think if you change it to this
class A(Base):
def __init__(self):
self.rect = R(0, 0, 0, 0)
Base.__init__(self)
it will work
the problem is that Base.__init__ sets pos , which is a setter method of B that references self.rect but self.rect is not created until after the init call.
so to resolve the issue, simple ensure that self.rect exists before calling __init__ constructor

Related

Attribute error: can't set attribute for pygame Sprite inheritance

I have a working code with an HPBar class --> inherits from ProgressBar class --> inherits from pygame.sprite.Sprite. I decided to create a Widget class to have the following inheritance flow: HPBar --> ProgressBar --> Widget --> pygame.sprite.Sprite. The point in doing so is for flexibility especially when adding more widgets like buttons, textboxes, etc. However, in my revision I encountered Attribute error: can't set attribute. Details are as follows.
Somewhere in my code I have this HPBar instantiation:
hp_bar = HPBar(
x=x, y=y,
entity=self.player,
groups=[self.camera, self.extras],
)
Working Code:
This worked prior to the revision.
class HPBar(ProgressBar):
def __init__(self, entity, *args, **kwargs):
max_value = entity.stats["max_hp"]
value = entity.stats["hp"]
super().__init__(
max_value=max_value, value=value,
width=32, height=5,
*args, **kwargs
)
class ProgressBar(pygame.sprite.Sprite):
def __init__(
self,
x: float,
y: float,
width: int,
height: int,
groups: List[pygame.sprite.AbstractGroup] = [],
max_value: int,
value: int,
*args, **kwargs
):
super().__init__(groups)
#property
def image(self):
_image = # pygame surface
return _image
Revised Code:
The code with the Attribute error.
class ProgressBar(Widget):
def __init__(
self,
max_value: int,
value: int,
*args, **kwargs
):
super().__init__(*args, **kwargs)
#property
def image(self):
_image = # pygame surface
return _image
class Widget(pygame.sprite.Sprite):
def __init__(
self,
x: float, y: float,
width: int, height: int,
groups: List[pygame.sprite.AbstractGroup] = [],
):
super().__init__(groups)
self.image = pygame.Surface((width, height))
Traceback Error:
File "C:\Users\Hp\Documents\Working\Personal\platformer1\game_models\windows\platformer_window.py", line 127, in load_level
hp_bar = HPBar(
File "C:\Users\Hp\Documents\Working\Personal\platformer1\game_models\sprites\hp_bar.py", line 16, in __init__
super().__init__(
File "C:\Users\Hp\Documents\Working\Personal\platformer1\contrib\models\widgets\progress_bars\progress_bar.py", line 24, in __init__
super().__init__(*args, **kwargs)
File "C:\Users\Hp\Documents\Working\Personal\platformer1\contrib\models\widgets\widget.py", line 22, in __init__
self.image = pygame.Surface((width, height))
AttributeError: can't set attribute
Few debugging attempts:
I tried to print out the width and height arguments inside the Widget class to make sure I'm receiving and sending the correct data type:
In Widget class:
super().__init__(groups)
print(width, height)
print(type(width), type(height))
self.image = pygame.Surface((width, height))
Print result:
32 5
<class 'int'> <class 'int'>
Moreover, I have had this implementation resembling my Widget class implementation and this works fine:
class Player(pygame.sprite.Sprite):
def __init__(self, pos):
super().__init__()
self.image = pygame.Surface((16, 32))
Yes, of course. You can't have a method/property and an attribute with the same name. image can be either an attribute or a property. But you can't have 2 objects with the same name.
The following is not possible:
class Foo:
def __init__(self):
self.bar = 1
def bar(self):
return 2
print(Foo().bar())
print(Foo().bar())
TypeError: 'int' object is not callable
Also not possible:
class Foo:
def __init__(self):
self.bar = 1
#property
def bar(self):
return 2
print(Foo().bar)
self.bar = 1
AttributeError: can't set attribute 'bar'
However you can define a setter:
class Foo:
def __init__(self):
self._bar = 1
self.bar = 2
#property
def bar(self):
return self._bar
#bar.setter
def bar(self, value):
self._bar = value
print(Foo().bar)
2

How to access the stored array in class

I want to access the array i have filled throughout the for loop,however what i get it is still empty array when call fill_particles().particles, is there any way to get rid of this problem? here is my code.
class particle(object):
def __init__(self,x,y,z):
self.x=x
self.y=y
self.z=z
class fill_particles():
def __init__(self):
self.particles=[]
def fill(self):
for i in range(5):
self.particles.append(particle(i,i+1,i+2))
You need to instantiate the class and call fill(). Here's a working example, along with extra functions for display purposes:
class Particle(object):
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def __repr__(self):
return f'Particle(x={self.x}, y={self.y}, z={self.z})'
class Particles:
def __init__(self):
self.particles = []
def fill(self):
for i in range(5):
self.particles.append(Particle(i, i + 1, i + 2))
def __repr__(self):
return f'Particles({self.particles!r})'
particles = Particles()
print(particles)
particles.fill()
print(particles)
Output:
Particles([])
Particles([Particle(x=0, y=1, z=2), Particle(x=1, y=2, z=3), Particle(x=2, y=3, z=4), Particle(x=3, y=4, z=5), Particle(x=4, y=5, z=6)])

How to assign a data attribute of a subclass to attributes of its parent class

I have a class Rectangle with data attributes width and height, I want a subclass Square with data attribute side_length.
How do I make it so that square.width and square.height give its side length? i.e same as square.side
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
class Square(Rectangle):
def __init__(self, side)
self.side = side
This is what I have so far.
You could call the constructor for Rectangle.
super(Square,self).__init__(side, side)
or you could have properties to return those attributes. I'd edge towards the super.
#property
def length(self):
return self.side
#property
def width(self):
return self.side
Things get more complicated if you can change the side, height or width attribute after creating the object. You need to keep width and height synched and coherent. One possible way is to do away entirely with side as a stored attribute on Square and instead have it as a read-write property that updates Rectangle's width and height.
to keep height/width/side sorted after the initial constructors:
class Rectangle:
#property
def height(self):
return self._height
#height.setter
def height(self, value):
self._height = value
#property
def width(self):
return self._width
#width.setter
def width(self, value):
self._width = value
def __repr__(self):
return(f"{self.__class__.__name__}:{self.height=} {self.width=}")
def __init__(self, height, width):
self.height = height
self.width = width
class Square(Rectangle):
def __repr__(self):
return(f"{self.__class__.__name__}:{self.side=}")
#property
def side(self):
return self._width
#side.setter
def side(self, value):
self._width = value
self._height = value
def __init__(self, side):
super(Square, self).__init__(side, side)
#these are here so you can't cheat and vary height and width
#independently on a square
#property
def width(self):
return self.side
#width.setter
def width(self, value):
self.side = value
#property
def height(self):
return self._side
#height.setter
def height(self, value):
self.side = value
rectangle = Rectangle(5,2)
print(rectangle)
rectangle.height = 6
print(rectangle)
square = Square(3)
print(square)
square.side = 6
print(square)
square.height = 9
print(square)
output:
$ py test_square.py
Rectangle:self.height=5 self.width=2
Rectangle:self.height=6 self.width=2
Square:self.side=3
Square:self.side=6
Square:self.side=9

why can't I call my method from within class in python?

I am trying to make a button class for my game, using pygame. But in the button class, I cannot call methods that are contained in the class itself.
I am new to classes so I may be missing something important, I'm sorry
I tried to add self to the isHovering() Method but it still doesn't work.
import pygame
class Button():
def __init__(self, pos, value, img, screen):
self.pos = pos
self.value = value
self.img = img
self.screen = screen
### Getters and Setters ###===============
### Get/Set Value : True/False ###
def setValue(self, value):
self.value = value
def getValue(self):
return self.value
### Get/Set Pos ###
def setPos(self, pos):
self.pos = pos
def getPos(self):
return self.pos
### Get/Set Img ###
def setImg(self, img):
self.img = img
def getImg(self):
return self.img
#==========================================
def isHovering(self):
pos = getPos()
imgRect = pygame.rect(pos[0], pos[1], 105, 105)
if imgRect.collidepoint(pygame.mouse.get_pos()):
return True
else:
return False
def update(self, screen):
if isHovering():
image = pygame.transform.scale(self.img(95, 95))
else:
image = pygame.transform.scale(self.img(105, 105))
screen.blit(image, self.pos)
I thought that when update(screen) was called in my main loop, that it would call isHovering(), and return a True or False, but instead I get this error:
NameError: name 'isHovering' is not defined
In def update(self, screen),
The if statement should be if self.isHovering().
If not, the interpreter will look for a isHovering function somewhere in the current module, like if you had defined it outside your class.
Using the self. prefix will indicate that you are indeed trying to call a method of the instance as JacoblRR already pointed out in a comment.

How should I implement, and what should I call, a common base class for Area and Point?

So I want a Point and an Area classes similar to how C# has Point and Size. Here are simple implementations of the two classes:
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
# Many other magic methods too!
class Area:
def __init__(self, width=0, height=0):
self.width = width
self.height = height
def __add__(self, other):
return Area(self.width + other.width, self.height + other.height)
# Many other magic methods too!
As you can see, the two classes are exact duplicates, except one has x, y while the other has width, height.
What would be a good solution for implementing some kind of base class for these two?
If you don't mind using immutable objects, you could subclass tuple to craete a base class for all of the two dimensional stuff:
class _2dTuple(tuple):
def __new__(cls, hor=0, ver=0):
super().__new__(cls, (hor, ver))
def __add__(self, other):
return type(self)(self[0] + other[0], self[1] + other[1])
Now when you subclass your _2dTuple, you can just create property getters for your x, y and width, height:
class Point(_2dTuple):
#property
def x(self):
return self[0]
#property
def y(self):
return self[1]
class Area(_2dTuple):
#property
def width(self):
return self[0]
#property
def height(self):
return self[1]

Categories