Having trouble calling Classes in Python - python

I'm trying to teach myself how to use classes and I'm trying an example I found in a book that asks you to create two classes and then print out some information. Here is my code:
import math
import turtle
import urllib.request
class Shape:
def __init__(self,x=0,y=0):
self.x = x
self.y = y
def calc_area(self):
pass
def calc_perim(self):
pass
def get_shape_type(self):
return "s"
def to_string(self):
return "%s %f %f" % (self.get_shape_type(), self.x, self.y)
def get_draw_params(self):
return [self.x, self.y]
class Circle(Shape):
def __init__(self,x=0,y=0,rad=0):
super().__init__(x,y)
self.radius = rad
def calc_area(self):
area = math.pi * self.radius * self.radius
return area
def calc_perim(self):
perim = 2 * math.pi * self.radius
return perim
def calc_circumference(self):
return self.calc_perim()
def get_shape_type(self):
return "c"
def to_string(self):
return "%s %f %f %f" % (super().to_string(), self.radius, self.calc_area(),self.calc_perim())
def get_draw_params(self):
result = super().get_draw_params()
result.extend([self.radius])
return result
cir = Circle(0,0,150)
print(cir)
When I try to run it, it prints this:
<__main__.Circle object at 0x103d19ef0>
I'm not sure what I'm doing wrong when I'm calling the Circle class. I was hoping that after putting in the values that the init function asks for, there would be some data to print out. Any help would be greatly appreciated.

Try calling the to_string() method that you added to your classes:
>>> cir = Circle(0,0,150)
>>> print(cir)
<__main__.Circle object at 0x7fba2851b400>
>>> print(cir.to_string())
c 0.000000 0.000000 150.000000 70685.834706 942.477796
If you are wanting a customised string representation, try adding __unicode__() and/or __str__() methods to your classes:
def __str__(self):
return self.to_string()
Now you can do this:
>>> c = Circle(0,0,150)
>>> print(c)
c 0.000000 0.000000 150.000000 70685.834706 942.477796

It's actually quite right, this is how python prints your object,
if you want your print(object) print something else, define __str__ method in your class, something like this (it should return an string):
class Circle(Shape):
# your stuff
# ...
def __str__(self):
return "radius: " + self.radius
cir = Circle(0,0,150)
print(cir)
# radius: 150

There is nothing wrong with what you did. Everything is working fine.
This: <__main__.Circle object at 0x103d19ef0>
is indicating to you have you have an object of Circle. So, if you add this:
print(cir.calc_area())
You will end up getting the area result you expect.
Furthermore, a neat bit of information that can help you is if you want to find out more information about what is inside your objects, you can do this:
print(dir(cir))
This will tell you what is housed inside your 'cir' object and you will also see that your methods you created should be there as well. Always handy to find out what is available to you even when you import other modules when you dive deeper in to Python.
Documentation on dir

Well, that is the default representation of a Circle object when you print it.
Now you can call the methods of cir, like
print(cir.calc_area())

Related

What's the best way to design a class that calls sequences of its methods?

I have a class similar to the one below where it goes through a series of methods using variables in the class. The code used to be a massive series of functions, passing variables around and this felt more structured and easy to work with/test. However, it still feels as though there's a better way.
Is there a better design pattern or approach for situations like this? Or is going the object route a mistake?
In terms of testing process() and other methods, I can just mock the methods called and assert_called_once. But, ultimately, it leads to ugly testing code with tons of mocks. So, it makes me wonder about question #1 again.
class Analyzer:
def __init__(self):
self.a = None
self.b = None
def process(self):
self.gather_data()
self.build_analysis()
self.calc_a()
self.calc_b()
self.build_output()
self.export_data()
...
def gather_data(self):
self.get_a()
self.get_b()
self.get_c()
...
def build_analysis(self):
self.do_d()
self.do_e()
self.do_f
...
As for testing, and I know this code isn't technically right, but I just wanted to illustrate how it gets hard to read/sloppy.
class TestAnalyzer:
#patch.object(Analyzer, 'gather_data')
#patch.object(Analyzer, 'build_analysis')
#patch.object(Analyzer, 'calc_a')
#patch.object(Analyzer, 'calc_b')
#patch.object(Analyzer, 'build_output')
#patch.object(Analyzer, 'export_data')
def test_process(self, m_gather_data, m_build_analysis, m_calc_a,
m_calc_b, m_build_output, m_export_data):
analyzer.process()
m_gather_data.assert_called_once()
m_build_analysis.assert_called_once()
m_calc_a.assert_called_once()
...
Any insight or thoughts would be appreciated. Thank you!
Maybe the information expert design principle can help you
Assign responsibility to the class that has the information needed to fulfill it
In your example it seems like you can split your class into different ones with better defined responsibilities. Assuming that you have to perform the following functions in order:
Gather data
Preprocess it
Analyse it
I would create a class for each of them. Here you have some example code that generates some data and performs some basic calculations:
from random import randint
from dataclasses import dataclass
#dataclass
class DataGatherer:
path: str
def get_data(self):
"""Generate fake x, y data"""
return randint(0, 1), randint(0, 1)
#dataclass
class Preprocessing:
x: int
y: int
def prep_x(self):
return self.x + 1
def prep_y(self):
return self.y + 2
#dataclass
class Calculator:
x: int
y: int
def calc(self):
return self.x + self.y
All I have done is to divide your code into blocks, and assigned methods and attributes to fullfill the functions of those blocks. Finally, you can create an Analysis class whose only responsibility is to put the whole process together:
#dataclass
class Analysis:
data_path: str
def process(self, save_path: str):
x, y = DataGatherer(self.data_path).get_data()
print("Data gathered x =", x, "y =", y)
prep = Preprocessing(x, y)
x, y = prep.prep_x(), prep.prep_y()
print("After preprocessing x =", x, "y =", y)
calc = Calculator(x, y)
result = calc.calc()
print("Result =", result)
self.save(result, save_path)
def save(self, result, save_path: str):
print(f"saving result to {save_path}")
In the end this is all you have to do:
>>> Analysis("data_path_here").process("save_path_here")
Data gathered x = 0 y = 1
After preprocessing x = 1 y = 3
Result = 4
saving result to save_path_here
When it comes to testing I use pytest. You can create a test file for each of your classes (eg test_datagatherer.py, test_preprocessing.py ...) and have a unit test function for each of your methods, for example:
from your_module import Preprocessing
def test_prep_x():
prep = Preprocessing(1, 2)
assert type(prep.prep_x()) is int
def test_prep_y():
prep = Preprocessing(1, 2)
assert type(prep.prep_y()) is int
I will leave you to pytest documentation for more details.

How do I get python to read that () is 0?

I'm having issues trying to pass an empty parameter can someone explain to me why my code isn't working. I have a math test file that goes through my math library file but my lib file can't read the () code. When I run the code it says init() missing 1 required positional argument: 'y'
import MathLib as math
math test:
if __name__ == '__main__':
math_obj1 = math.MyMathLib(2.0)
math_obj2 = math.MyMathLib(-0.5)
math_obj3 = math.MyMathLib() # this should give 0.0
print("Math obj1 value = ",math_obj1.get_curr_value() )
print("Math obj2 value = ",math_obj2.get_curr_value() )
print("Math obj3 value = ",math_obj3.get_curr_value() )
import math
class MyMathLib:
def __init__(self, y,):
self.y = y
if self == None:
value == 0.0
As posted, your definition of the __init__() function has y as a required argument.
If you want it to be optional and have a default value of zero, then write it this way:
class MyMathLib:
def __init__(self, y=0.0):
The self variable isn't actually a passable parameter in class methods (I recommend you take another look at python classes). The first (and only) passable parameter in your init function is y. Since y has no default variable, you must pass a value for y, or give it a default value:
def __init__(self, y=0.0):
self.y = y
Also I'm not sure what you're trying to achieve with this line, it makes no sense:
if self == None:
value == 0.0
value is only local to the init function, maybe you meant self.value? Even then, self will never be None (unless you assign self = None within the method), so the statement will never trigger. Ontop of that, you've used a double == instead of =.
You have to set default value in __init__
def __init__(self, y=0.0):
self.y = y
and then you don't have to check None
Or using None
def __init__(self, y=None):
self.y = y
if self.y is None:
self.y = 0.0
It can be useful if you want to recognize if someone used MyMathLib() or MyMathLib(0.0)
That is because your __init__ requires two arguments instead of one. Instead of doing this, you can pass a default variable like #Jay Mody's answer. And also:
self == None will never be true because self always passes in a value y.
Here is another way you can do it:
class MyMathLib:
def __init__(self):
self.y = 0.0
def passNumber(y):
self.y = y
As you can see, if the number is passed using passNumber, that means that the number isn't 0.0. This is another way to do it.

Difference between print(*) and *.print()

I was staring at a very very long code. I am just trying the GUI part. There were these lines :
sh = packing_options[best_index].sheets[idx]
sh.print()
for rect in sh.rect_list:
rect.print()
I wanted to show the value stored in sh and rect for the GUI window.
When I use
b3 = tk.Label(top, text=sh)
b3.grid(row=0, column=2)
b4 = tk.Label(top, text=rect)
b4.grid(row=0, column=2)
It gives this as the result : <main.Sheet object at 0x0000024F5CF4A320>
The code for classes are given below:
class Rect:
def __init__(self, w, l):
self.l = l
self.w = w
def print(self):
print('Rect w:' + str(self.w) + 'l:' + str(self.l))
def area(self):
return self.l * self.w
## class for sheet to save all rectangles' positions
class Sheet:
# initialization
def __init__(self, W, L):
self.W = W # width (=horizontal length)
self.L = L # length (=height, vertical length)
self.rect_list = [] # rectangle list
self.rect_pos = [] # rectangle starting position from left bottom
self.rect_rotate = [] # rectangle rotation indicator list, 0: not rotated, 1: rotated
self.lhl_x = [0,W] # lowest horizontal line boundary point list
self.lhl_y = [0] # lowest horizontal line height list: each element is height
# area of sheet
def area(self):
return self.L * self.W
def print(self):
print('sheet W:' + str(self.W) + ' L:' + str(self.L))
How do I get the real value and store it in a variable to use print(*) (To use to show in GUI ultimately)
from itertools import enumerate
from tkinter import messagebox
def sheet_info(sheet):
str = "Sheet W:{} L:{} Contains {} rectangles:".format(sheet.W, sheet.L, len(sheet.rect_list))
for idx, rect in enumerate(sheet.rect_list):
str += "\n Rectangle W:{} L:{} at position {}".format(rect.w, rect.l, sheet.rect_pos[idx])
return str
messagebox.showinfo("sheet info", sheet_info(sh))
This should do it. May contain mistakes as i can't verify it atm.
Think of it as the behaviour of Python to print ing the object of a regular class , I.e if you provide the __str__ method it will print the object in stringified form with detail you are looking for (ofcourse you decide what is going to be printed) but if there is no __str__ method in the class, then printing an object will give you a queer output including address location of the object (similar to your example ) as standard print method does not know printing an object.
<__main__.Foobar instance at 0x7cf2a22e>
print(sh) is doing the same thing (nothing unexpected).
For this particular question both the print s are different .print is tkinter method defined on sh and print is python's method.
If you want to use the sh in python's built-in print function then you need to have a __str__ (or __repr__) method in the class Sheet , which will be the string representation of your object, because print(sh) finally invokes Sheet.__str__():
And in that __str__ method you can simply put all the details you wish to see on screen using the self keyword.
Very simple example in this direction will be
def __str__(self):
print('sheet W:' + str(self.W) + ' L:' + str(self.L))
__str__ is the string representation of an object and better think of it as toString function in Java (if you are familiar with it)
from tkinter import messagebox
messagebox.showinfo("Title", "your text here")

What is causing an AttributeError with my Python code when I compile it?

I am attempting to write code that allows me to pass in a number that increases or decreases in my Square class. When compiling the code I am given this error:
AttributeError: 'Square' object has no attribute 'change_size'.
Entered code:
class Square():
def __init__(self,s1):
self.s1=s1
def calculate_perimeter(self):
return self.s1*4
def change_size(self,new_size):
self.s1+=new_size
a_square= Square(100)
Interaction:
>>> print(a_square.s1)
100
>>> a_square.change_size(200)
Does the code you posted above have identical indentation to your actual code? If so, the issue is likely caused by the fact that in Python, indentation does actually matter. That is:
class Square():
def init(self, s1):
self.s1 = s1
.
.
.
is not the same as
class Square():
def init(self, s1):
self.s1 = s1
.
.
.
You can see a longer explanation, and more examples, in the PEP8 style guide.
Please change the Indentation,
class Square():
def __init__(self,s1):
self.s1=s1
def calculate_perimeter(self):
return self.s1*4
def change_size(self,new_size):
self.s1+=new_size
return self.s1
a_square= Square(100)
Result:
a_square.s1
100
a_square.change_size(100)
200
Please let me know if you have any questions, i would be very happy to help you.
Few things:
1) use the code formatting blocks to help your make your code more readable
2) remove the += when changing size of the attribute because an augmented assignment operator will add the value to the existing value
3) establish the attribute s1 before you use it in the class
Try something like:
class Square():
s1 = 0
def init(self,s1):
self.s1=s1
def calculate_perimeter(self):
return self.s1*4
def change_size(self,new_size):
self.s1 = new_size
a_square = Square()
print(a_square.calculate_perimeter())
a_square.change_size(5)
print(a_square.calculate_perimeter())

Printing from within properties

I'm trying to make a robotics kit. Its designed to be simple so I'm using properties so when the users change a parameter the property method sends the serial command which controls motors/ servos/whatever.
This is the code at the moment, directly from a previous question I asked on here.
class Servo(object):
def __init__(self, which_servo, angle = 0):
self._angle = angle;
self._servo_no = which_servo
def get_angle(self):
return self._angle
def set_angle(self, value):
self._angle = value
print "replace this print statement with the code to set servo, notice that this method knows the servo number AND the desired value"
def del_angle(self):
del self._angle
angle = property(get_angle, set_angle, del_angle, "I'm the 'angle' property.
this is then initialized as such:
class robot(object):
def __init___(self):
self.servos = [Servo(0), Servo(1), Servo(2), Servo(3)]
Now, this works in the respect that it does change the variable through the getter and setter functions, however the prints in the getter and setter never is printed, thus if I replace it with a serial command I assume it won't do anything either, can anyone shed any light on this?
Thanks
Update: Thanks for the help using the servo file this is whats happened, there are three scenarios the first works and by extension I would have assumed the next two preferable scenarios would work but they don't any ideas?
This works
import servo
class Robot(object):
def __init__(self):
self.servos = [servo.Servo(0, 0), servo.Servo(1,0), servo.Servo(2,0)]
R = Robot()
R.servos[1].angle = 25
This does not:
import servo
class Robot(object):
def __init__(self):
self.servos = [servo.Servo(0, 0), servo.Servo(1,0), servo.Servo(2,0)]
R = Robot()
left_servo = R.servos[1].angle
left_servo = 25
Neither does this
import servo
class Robot(object):
def __init__(self):
self.servos = [servo.Servo(0, 0).angle, servo.Servo(1,0).angle, servo.Servo(2,0).angle]
R = Robot()
R.servo[1] = 25
Using the preferred decorator syntax for properties, this works fine. It'll also help you avoid issues like this in the future
class Servo(object):
def __init__(self, which_servo, angle = 0):
self._angle = angle;
self._servo_no = which_servo
#property
def angle(self):
return self._angle
#angle.setter
def angle(self, value):
self._angle = value
print "replace this print statement with the code to set servo"
#angle.deleter
def angle(self):
del self._angle
Seeing as your indentation is off here, I believe this is likely an issue of indentation in your source. This should work as well if you really want to use the old property function:
class Servo(object):
def __init__(self, which_servo, angle = 0):
self._angle = angle;
self._servo_no = which_servo
def get_angle(self):
return self._angle
def set_angle(self, value):
self._angle = value
print "replace this print statement with the code to set servo"
def del_angle(self):
del self._angle
angle = property(get_angle, set_angle, del_angle,"I'm the 'angle' property.")
Both of these work successfully for me (inside a file called servo.py)
>>> import servo
>>> s = servo.Servo(1, 2)
>>> s.angle
2
>>> s.angle = 3
replace this print statement with the code to set servo
EDIT
To address your new issues. When you assign R.servos[1].angle to left_servo, its not creating a reference to the servos angle, it's just setting left_servo to whatever the angle is. When you reassign 25 to it, you're not assigning to the angle you're assigning to the left_servo.
On the second one, I'm assuming you meant R.servos and not R.servo which should be raising an AttributeError. But the real problem as I see it, is you should be saying R.servos[1].angle = 25 and you're omitting the .angle.
To (attempt to) put it simply: When you use the = operator, you are changing where a name refers to, not what it refers to.
>>> x = 1
>>> x = 2
the second assignment does not overwrite the 1 in memory with a 2, it just changes where x refers to. So if I did something like
>>> x = 1
>>> y = x
>>> y = 2
>>> print x
1
the output is 1 because your are telling y to refer to the same place that x refers. Changing y to 2 changes where y refers to, it does not change the 1 already in memory.

Categories

Resources