How to apply class methods to my own class' property in Python - python

I created a class called Thing for Pygame, which will later be subclassed to develop more specific classes and objects. This class needs to store among other attributes, the position, and size of the object.
However, Pygame has this very powerful class called Rect that can already do that, and has many useful built-in methods and properties. I could use a single Rect attribute in my Thing class, but it stores position in integers, and I'm looking to store it in floats (a Thing might need to move less than one pixel per frame).
This I what I had in mind.
class Thing:
def __init__(self, pos, size):
self.x: float = pos[0]
self.y: float = pos[1]
self.size = size
#property
def rect(self):
return pygame.Rect((self.x, self.y), self.size)
#rect.setter
def rect(self, var: pygame.Rect):
self.x = var.x
self.y = var.y
self.size = var.size
This would allow me to get and set the Thing's Rect, but I wouldn't be able to use the Rect's built-in setters as desired:
foo = Thing((10, 10), (20, 20))
foo.draw(win)
foo.x += 100
foo.draw(win)
# This has no effect
foo.rect.y += 100
foo.draw(win)
temp = foo.rect
temp.x -= 100
foo.rect = temp
foo.draw(win)
The desired behavior for the above code would be to have the square move right, down, then left.
Is there a way to either:
Have a Rect as a Thing attribute, but update x and y whenever it's changed?
Apply methods to Rect property
Some other solution you can think of
TL;DR: If I have a Rect as a property, I can't do self.rect.center = (500,500)
Thanks in advance!

Your class should inherit from pygame.Rect. That is the only way to get the Rect methods automatically. However by doing this you will inherits also the int typecasting of the coordinates since that is in the original implementation of pygame.Rect. I'm afraid inheritance won't solve your problem.
You know what the Rect methods are supposed to do (The documentation is well written), so I'm afraid the only way is to reimplement them (or at least reimplement those you need) for your own Thing class, to mimic the Rect behaviour with float numbers.
I did something similar: here is a portion of the class I wrote (I won't show it all because is too long) to give you an idea:
class FlRect:
"""Similar to pygame.Rect but uses float numbers.
The class stores internally only coordinates, width and height.
Other attributes are rendered through properties, with getter and setter:
x, y: coordinates of the top-left corner of the rectangle.
top, bottom: y coordinates of the top and bottom edges respectively.
left, right: x coordinates of the left and right edges respectively.
centerx, centery: coordinates of the centre of the rectangle.
width, height: self-explanatory.
"""
def __init__(self, x, y, w, h):
"""Initialization:
x, y - coordinates of top-left corner of the rectangle
w, h - width and height
"""
self._x = x
self._y = y
self._w = w
self._h = h
#property
def x(self):
return self._x
#x.setter
def x(self, value):
self._x = value
#property
def y(self):
return self._y
#y.setter
def y(self, value):
self._y = value
#property
def width(self):
return self._w
#width.setter
def width(self, value):
self._w = value
#property
def height(self):
return self._h
#height.setter
def height(self, value):
self._h = value
#property
def top(self):
return self._y
#top.setter
def top(self, value):
self._y = value
#property
def bottom(self):
return self._y + self._h
#bottom.setter
def bottom(self, value):
self._y = value - self._h
#property
def left(self):
return self._x
#left.setter
def left(self, value):
self._x = value
#property
def right(self):
return self._x + self._w
#right.setter
def right(self, value):
self._x = value - self._w
#property
def centerx(self):
return self._x + (self._w / 2)
#centerx.setter
def centerx(self, value):
self._x = value - (self._w / 2)
#property
def centery(self):
return self._y + (self._h / 2)
#centery.setter
def centery(self, value):
self._h = value - (self._h / 2)
def get_rect(self):
"""Return a pygame.Rect object with rounded coordinates"""
return Rect(round(self._x), round(self._y), round(self._w), round(self._h))
Of course this is not going to be as efficient as pygame.Rect, since this is written in python and the pygame Rect class is in C.

Related

Setter for a field in subclass should have the same value validation as that of a different field in super class

I want to update the class Square by adding the public getter and setter size
The setter should assign (in this order) the width and the height - with the same value
The setter should have the same value validation as the Rectangle for width and height
No need to change the exception error message (It should be the one from width)
The class Square inherits from Rectangle.
The setter for width in Rectangle:
def width(self):
"""
getter for #__width
"""
return self.__width
#width.setter
def width(self, width):
"""
validate and set the width attribute
"""
if width <= 0:
raise ValueError("width must be > 0")
if not isinstance(width, int):
raise TypeError("width must be an integer")
self.__width = width```
And what i tried to do in ```Square``` for the size attribute:
``` def __init__(self, size, x=0, y=0, id=None):
"""
Initialise a square with same width and height
"""
self.__size = size
super().__init__(self.size, self.size, x, y, id)
#property
def size(self):
"""
Getter for size
"""
return self.__size
#models.rectangle.Rectangle.width.setter
def size(self, size):
"""
Setter for size, using the logic for Rectangle's width
"""
self.__size = size
When I try to assign a value to size I get this error:
AttributeError: can't set attribute
Square is a Rectangle with the same width and height
In Python everything is public, so you can do something like this:
class Rectangle:
def __init__(self, width):
self.__width = width
#property
def width(self):
return self.__width
#width.setter
def width(self, width):
self.setter(Rectangle.__name__, width=width)
def setter(self, class_name, **kwargs):
for k, v in kwargs.items():
if v <= 0:
raise ValueError(f"{k} must be > 0")
if not isinstance(v, int):
raise TypeError(f"{k} must be an integer")
self.__setattr__(f"_{class_name}__" + k, v)
class Square(Rectangle):
def __init__(self, size, x=0, y=0, id=None):
self.__size = size
super().__init__(self.__size)
#property
def size(self):
return self.__size
#size.setter
def size(self, size):
self.setter(Square.__name__, size=size)
s = Square(1)
print("After __init__")
print(s.__dict__)
s.width = 5
s.size = 3
print("After setter")
print(s.__dict__)
print(s.size, s.width)
Output:
After __init__
{'_Square__size': 1, '_Rectangle__width': 1}
After setter
{'_Square__size': 3, '_Rectangle__width': 5}
3 5

Overwriting a passed on function from a parent class

Hey my task is to create a parentclass Shape and pass the functions on to the different shapes. Since a Circlearea calculation takes different parameters I am trying to overwrite the function. It throws following warning: "Signature of method 'Circel.get_area()' does not match Signature in the base method in Shape". Is that possible at all? How would I have to do it? Should it work anyway later on?
class Shape(ABC):
#abstractmethod
def get_area(self, x_l, y_l):
pass
def move(self, newx, newy):
pass
class Circle(Shape):
def __init__(self, rad, x_pos, y_pos):
self.rad = rad
self.pos = (x_pos, y_pos)
self.area = self.get_area(self, self.rad)
def get_area(self, rad):
return(self.rad*self.rad*2*m.pi)

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

Class function doesn't return correct value

This is my code:
class Rectangle(object):
def __init__(self, height, width):
self.height = height
self.width = width
def __str__(self):
return '{} x {} = {}'.format(self.height, self.width, self.area)
def area(self):
self.area=self.height*self.width
return self.area
def primarySchool(height, width):
return str(Rectangle(height, width))
For input height=7 and width=4 the output is
>>> primarySchool(7, 4):
7 x 4 = <bound method _runjcbjp.<locals>.Rectangle.area of
<__main__._runjcbjp.<locals>.Rectangle object at 0x2b482cd637f0>>
instead of 7 x 4 = 28.
How can I fix this?
In your Rectangle class, the area member is defined as a method.
As a consequence, print(self.area) will give you the representation of that method object, which is that <...> thing.
What you want is the result of the area method, not the method itself.
Therefore, you need to call the method, by writing parentheses after its name.
Your code should be:
return '{} x {} = {}'.format(self.height, self.width, self.area())
Additionnally, be careful not to reassign the same name in your method.
In your area method, you write:
self.area = self.height * self.width
As a consequence, after the first call to instance.area(), the area member will be overwritten, from a function to a number.
Subsequent calls would thus fail, with something like "Int object is not callable".
area is a method of your class, so you have to call it to get the return value (and not the method itself).
But given the fact that you assign to self.area inside the method it seems like you want it as "cached" property (accessible without calling it explicitly):
class Rectangle(object):
def __init__(self, height, width):
self.height = height
self.width = width
def __str__(self):
return '{} x {} = {}'.format(self.height, self.width, self.area)
#property
def area(self): # cached version
try:
return self._area
except AttributeError:
self._area=self.height*self.width
return self._area
def primarySchool(height, width):
return str(Rectangle(height, width))
primarySchool(7, 4)
# '7 x 4 = 28'
Or as uncached version:
class Rectangle(object):
def __init__(self, height, width):
self.height = height
self.width = width
def __str__(self):
return '{} x {} = {}'.format(self.height, self.width, self.area)
#property
def area(self):
return self.height*self.width
Or just calculate it in the __init__ and also set it as attribute:
class Rectangle(object):
def __init__(self, height, width):
self.height = height
self.width = width
self.area = height * width
def __str__(self):
return '{} x {} = {}'.format(self.height, self.width, self.area)
You're trying to have both a function and property called "area".
Why not simply:
def area(self):
return self.height*self.width
Call with:
self.area()

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