How to print my class and not get memory reference - python

I have a class called Pension, with attributes like a person's name, age, savings and a growth rate.
I have a class method which calculates the person's total savings at retirement year.
Under my main function, I want to print the class to see if my code is working as intended, but I don't know how to do as I only get the memory reference when printing.
How can I print the class instance, so that it goes through all its attributes and runs the function result, and prints the result? Worth to note; to run the function 'result' which calculates the total pension, the growth rate is user inputted and in a function of its own (and is run in main())
For example, if I try to print the 2nd last line: print(pensions) I only get the memory reference. So in this case, if a person (the data for which I read in from a file) has saved up 1000 dollars (using my result method), I would like that fact to be printed into a list.
This is my code:
class Pension:
def __init__(self, name,age,savings,growth):
self.name = name
self.age = age
self.savings = savings
self.growth = growth
def result(self):
amount=self.savings
rate=1+(self.growth/100)
years=65-self.age
return (amount * (1 - pow(rate, years))) / (1 - rate)
def convert(elem: str):
if not elem.isdigit():
return elem
return float(elem)
def convert_row(r: list) -> list:
return [convert(e) for e in r]
def get_growth(msg: str = "Enter growth rate: "):
return float((input(msg).strip()))
def main():
with open('personer.txt') as f:
raw_data = f.readlines()
data = [row.split("/") for row in raw_data]
data = [convert_row(row) for row in data]
pensions = [Pension(*i, get_growth()) for i in data]
main()

From the Pension class object's perspective it doesn't actually matter how is the growth provided. Also in this case maybe it's worth to make the result a property, then there's no need to call it as a function (just access like any other property, but the values will be calculated "dynamically").
You can customize the __str__ method to return any str representation of your object.
class Pension:
def __init__(self, name,age,savings,growth):
self.name = name
self.age = age
self.savings = savings
self.growth = growth
self._result = result
#property
def result(self):
amount=self.savings
rate=1+(self.growth/100)
years=65-self.age
return (amount * (1 - pow(rate, years))) / (1 - rate)
def __str__(self):
return f"Pension:\n{self.amount=}\n{self.age=}\n{self.savings}\n{self.growth=}\n{self.result}"
And then just:
for p in pensions:
print(p)

Related

Python 3.8: TypeError: 'dict' object is not callable when trying to get data from a file

ran into a roadblock during a tutorial. I have no idea on how to solve this. I'm trying to use data from a csv file. When I try accessing the data with the current code commented out (see below), it works by outputting existing data.
However, it immediately errors when I add back in the commented code, with the error mentioned in the title. Could someone look at this and tell me what I've done wrong?
import csv
class item:
pay_rate = 0.8 #the payrate after 20% discount
all = []
def __init__(self, name: str, price: float, quantity: int = 0):
#run validations of received arguments
assert price >= 0, f"price {price} !> 0"
assert quantity >= 0, f"quantity {quantity} !> 0"
#assign to self object
print(f"an instance created: {name}")
self.name = name
self.price = price
self.quantity = quantity
#actions to execute
item.all.append(self)
def calculate_total_price(self):
return self.price * self.quantity
def apply_discount(self):
self.price = self.price * self.pay_rate
#classmethod
def instantiate_from_csv(cls):
with open('items.csv', 'r') as f:
reader = csv.DictReader(f)
items = list(reader)
for item in items:
item(
name=item.get('name'),
price=float(item.get('price')),
quantity=int(item.get('quantity')),
)
def __repr__(self):
return f"item({self.name},{self.price}, {self.quantity})"
item.instantiate_from_csv()
print(item.all)
By naming several things item, you managed to confuse even yourself.
Below is your code with things sensibly renamed and the problem fixed. Take a look at the comments to see what was changed:
import csv
# renamed to AllItems, because that's what you end up using it for
# you're mixing up an instance and a container of instances here and
# it gets you the worst of both worlds
class AllItems:
pay_rate = 0.8
all = []
def __init__(self, name: str, price: float, quantity: int = 0):
assert price >= 0, f"price {price} !> 0"
assert quantity >= 0, f"quantity {quantity} !> 0"
print(f"an instance created: {name}")
self.name = name
self.price = price
self.quantity = quantity
# you can do this, but you in some cases you want to use
# self.all.append instead, which will still access the class
# attribute
AllItems.all.append(self)
def calculate_total_price(self):
return self.price * self.quantity
def apply_discount(self):
# this is dubious - can you apply the same discount many times?
self.price = self.price * self.pay_rate
# renamed to 'load', sure you instantiate, but you don't return the
# instance, you're really just abusing the class as a container
#classmethod
def load_from_csv(cls, fn):
# it seems to make more sense to be able to use different csvs?
with open(fn, 'r') as f:
reader = csv.DictReader(f)
item_dr = list(reader)
for item_dict in item_dr:
# your problem was right here, you referred to `item()` where
# you meant to refer to the class, which is already passed as `cls`
cls(
name=item_dict.get('name'),
price=float(item_dict.get('price')),
quantity=int(item_dict.get('quantity')),
)
def __repr__(self):
# I'm not fixing this, but see remark below
return f"item({self.name},{self.price}, {self.quantity})"
AllItems.load_from_csv('items.csv')
# confusing double `.all` here, didn't bother improving the naming
# considering that it's the result of the design decision you made
print(AllItems.all)
For an items.csv with this:
name,price,quantity
banana,10,12
apple,4,6
pear,7,6
Output:
an instance created: banana
an instance created: apple
an instance created: pear
[item(banana,10.0, 12), item(apple,4.0, 6), item(pear,7.0, 6)]
Note that this fixes your issue but still leaves your code with some serious problems:
You're using the class as a container; that works, but it's not a great way of using a class like this and it certainly won't be what others expect. You're robbing yourself of all of the features a better container like a list would get you, like the it being iterable, or a dict, which would allow easy lookup of products (items). Consider creating an Item class that is just the Item and an Items (or something better) class that contains items and has additional operations.
Note that your representation __repr__ really doesn't give you a representation, that's not how someone could recreate the class at all and it's probably more of a __str__
Here's an example of what I'm suggesting:
import csv
class Item:
pay_rate = 0.8
def __init__(self, name: str, price: float, quantity: int = 0):
assert price >= 0, f"price {price} !> 0"
assert quantity >= 0, f"quantity {quantity} !> 0"
self.name = name
self._price = price
self.quantity = quantity
self.discount = None
#property
def price(self):
if self.discount is None:
return self._price
else:
return self._price * self.discount
#property
def total_price(self):
return self.price * self.quantity
def apply_pay_rate(self):
self.discount = self.pay_rate
#classmethod
def item_list_from_csv(cls, fn):
with open(fn, 'r') as f:
reader = csv.DictReader(f)
item_dr = list(reader)
return [cls(
name=item_dict.get('name'),
price=float(item_dict.get('price')),
quantity=int(item_dict.get('quantity')),
) for item_dict in item_dr]
def __repr__(self):
return f"Item({self.name}, {self.price}, {self.quantity})"
item_list = Item.item_list_from_csv('items.csv')
print(item_list)
print([item.total_price for item in item_list])
Output:
[Item(banana,10.0, 12), Item(apple,4.0, 6), Item(pear,7.0, 6)]
[120.0, 24.0, 42.0]

How to use __str__ function to return a string representation of a class

I have been tasked with creating an inventory manager in python, and one of the required is to define the following function within the class:
__str__ - This function returns a string representation of a
class.
The class refers to a file (inventory.txt) which has the following format:
Country,Code,Product,Cost,Quantity
my code thus far is as follows:
# Creating the class:
class Shoes():
# Constructor:
def __init__(self,country,code,product,cost,quantity):
self.country = country
self.code = code
self.product = product
self.cost = cost
self.quantity = quantity
# Methods:
def get_cost():
inventory = open('inventory.txt','r')
inventory_list = inventory.readlines()
code = input("What is the code of the product:")
for line in inventory_list:
split_lines = line.split(",")
if code == split_lines[1]:
print("This product costs R{}".format(split_lines[3]))
inventory.close()
def get_quantity():
inventory = open('inventory.txt','r')
inventory_list = inventory.readlines()
code = input("What is the code of the product:")
for line in inventory_list:
split_lines = line.split(",")
if code == split_lines[1]:
print("There are {} units in inventory".format(split_lines[4]))
inventory.close()
def __str__(self):
pass
I haven't come across the str so I really am not sure how it works, and how to use it. Any advice would be greatly appreciated.
Here is an example:
def __str__(self):
return f'{self.self.country},{self.self.code},{self.self.product}, {self.cost},{self.quantity })'
This way, when you assign values to a class, you can print its string representation
print(new_shoe)
More info here
https://www.pythontutorial.net/python-oop/python-str/

I dont know where I'm going wrong trying to create an object in a subclass through inputs

I've tried many different things so it's a little all over the place, please help
I've been able to make the first class and then in a different file create some objects for it, but for this subclass I need to use user input and I just can't figure it out.
I have made it so the shift input has to be a 1 or 2 for a day or night shift, I just don't have the knowledge for this.
class Employee:
def __init__(self, name, id, dept, title):
self.__name = name
self.__id = id
self.__dept = dept
self.__title = title
def get_name(self):
return self.__name
def get_id(self):
return self.__id
def get_dept(self):
return self.__dept
def get_title(self):
return self.__title
def __str__(self):
result = ""
result += "Name: " + self.get_name() + "\tID Number: " + str(self.get_id()) + \
"\tDepartment: " + self.get_dept() + "\tJob Title:" + self.get_title()
return result
class ShiftEmployee(Employee):
def __init__(self, name, id, dept, title, shift, pay):
Employee.__init__(self, name, id, dept, title)
self.__shift = shift
self.__pay = pay
#classmethod
def inputs(self):
self.__name = input("Enter name: ")
self.__id = input("Enter ID number: ")
self.__dept = input("Enter department: ")
self.__title = input("Enter Jobe title: ")
self.__shift = input("Enter shift: ")
self.__pay = input("Enter hourly pay: ")
#set_shift(self, shift):
#self.__shift = shift
#def set_pay(self, pay):
#self.__pay = pay
def get_shift(self, shift):
if self.__shift == 1:
return "Day"
elif self.__shift == 0:
return "Night"
else:
return "Invalid entry"
def get_pay(self, pay):
return self.__pay
def __str__(self):
result = ""
#result += Employee.__str__(self)
result += "Name: " + self.get_name(ShiftEmployee) + "\tID Number: " + str(self.get_id(ShiftEmployee)) + \
"\tDepartment: " + self.get_dept(ShiftEmployee) + "\tJob Title:" + self.get_title(ShiftEmployee) + \
"\tShift: " + self.get_shift(ShiftEmployee) + "\tHourly Pay: " + str(self.get_pay(ShiftEmployee))
return result
shift_emp = ShiftEmployee
shift_emp.inputs()
print(shift_emp.__str__(ShiftEmployee))
Don't use a classmethod because
A class method is a method that’s shared among all objects.
Though python itself does not force this behavior, your use of self in the inputs definition indicates that you are not doing what you think. the parameter is traditionally named cls in #classmethod-annotated methods, because the object you're referring to inside the body is not an instance of the class, but the class object itself. This means if you have multiple ShiftEmployee objects, they're going to be writing their data to the same variables. This is not what you want to happen.
you are not instantiating a ShiftEmployee object with shift_emp = ShiftEmployee, but rather assigning the class to the variable shift_emp, which is not what you want to do. so if you remove the #classmethod annotation, I think what you want is
shift_emp = ShiftEmployee() # __init__ gets called when you use this constructor invocation
shift_emp.inputs()
print(shift_emp)
Your __str__ methods don't make a lot of sense. You are passing the class object to each getter, which doesn't seem like it's what you'd want to do. The class object defines the class, what you want are the instances of the class. It's an important, if initially confusing distinction. Posting the error you get would help, but here's what I would expect the methods to look like. I'm not using the getters, because this is internal access, but you can use them instead of directly referring to the state variables if you prefer.
# Employee
def __str__(self):
return f"Name: {self.__name} ID Number: {self.__id} Department: {self.__dept} Job Title: {self.__title}"
# ShiftEmployee
def __str__(self):
return super(ShiftEmployee, self).__str__() + f" Shift: {self.__shift} Hourly Pay: {self.__pay}"
So what's going on here? For one thing, we use format strings because they are easier to work with and exactly the thing you wanted. Then we're using the superclass (Employee) to provide the shared functionality, and using the descendent class to enrich with the ShiftEmployee-only data. I skipped the accessor methods because they're redundant when accessing "private" data from inside the class members. Note that this won't quite do what you expect, either, w.r.t. the shift value that gets printed -- it's going to print the int, not "Night" or "Day". This is where your accessor method comes into play, except that your accessor has an extraneous parameter, shift. So you'd have to remove that value.
Please use the following way to initialize the class and printing the class,
shift_emp = ShiftEmployee() # Added Parenthesis
shift_emp.inputs()
print(str(shift_emp)) # Pass class object to inbuilt str() method to get output from __str__() method from class

Errors in minor details in a class output

I have two files :
class Account:
def __init__(self,id=0,balance=100.0,AIR=0.0):
self.__id = id
self.__balance = balance
self.__AIR = AIR
def getd(self):
return self.__id
def getbalance(self):
return self.__balance
def getAnnualInterest(self):
return self.__AIR
def setid(self,newid):
self.__id = newid
def setbalance(self,newbalance):
self.__balance = newbalance
def setAnnualInterestRate(self,newrate):
self.__AIR = newrate
def getMonthlyInterestRate(self):
return self.__AIR/12
def getMonthlyInterest(self):
return self.__balance*self.getMonthlyInterestRate()
def withdraw(self,amount):
if amount<=self.__balance:
self.__balance -= amount
def deposit(self,amount):
self.__balance += amount
def __str__(self):
return "Account ID : {0.setid} Account Balance : {0.setbalance} Annual Interest Rate : {0.setAnnualInterestRate}".format(self)
and Test:
from Account import Account
def main():
accountA = Account(0,100,0)
accountA.setid = 1234
accountA.setbalance = 20500
accountA.setAnnualInterestRate = 0.375
print(accountA)
accountA.withdraw(500)
accountA.deposit(1500)
print(accountA)
print(accountA.getMonthlyInterest())
main()
My output is mostly correct but there are two minor deatils which I have gotten wrong and I am not sure where in the code the problem is from.
Account ID : 1234 Account Balance : 20500 Annual Interest Rate : 0.375
Account ID : 1234 Account Balance : 20500(This is supposed to be 21500) Annual Interest Rate : 0.375
0.0(And this is supposed to be 671.875 but somehow I got it wrong)
accountA.setbalance = 20500 doesn't call the setbalance method. It changes the value of the setbalance attribute to 20500 (that is, after this line, accountA.setbalance is no longer a method but an int). Instead, you want accountA.setbalance(20500).
However, what you're doing is profoundly un-pythonic in the first place (you're a Java/C#/C++ programmer, aren't you?). Getters and setters are an anti-pattern in Python: just access and change the id, balance et al. attributes, and make them properties if (and only if) you need to perform computations/checks when setting/accessing them.
In addition, __attribute is not a private attribute in Python. The pythonic way to mark an attribute as "private" is a single leading underscore. However, it's just a convention, and the attribute itself will still be public (everything always is in Python -- it has no concept of visibility modifiers).
This:
accountA.setid = 1234
accountA.setbalance = 20500
accountA.setAnnualInterestRate = 0.375
doesn't call the functions. You actually change functions into variables this way. To call the functions use this notation:
accountA.setid(1234)
accountA.setbalance(20500)
accountA.setAnnualInterestRate(0.375)

An exercise from fundamentals of python [duplicate]

This question already exists:
Closed 11 years ago.
Possible Duplicate:
Fundamentals of Python Chapter 8 project 3
Hi I am a newbie programmer who just started to learn about python.
I have recently posted this same question before and I have solved it but my answer is not exactly what the question is asking.
I need help on why I need to implement a new method even though I could do the other way.
thanks
Question:
The __str__ method of the Bank class returns a string containing the
accounts in random order. Design and implement a change that causes
the accounts to be placed in the string by order of name.
[this is the part where I don't understand]
(Hint: You will also have to define a new method in the SavingsAccount class.)
class Bank(object):
def __init__(self):
self._accounts = {}
def __str__(self):
"""Return the string rep of the entire bank."""
pTemp =[]
for i in xrange(len(SavingsAccount.temp)-1):
if self._accounts.get(SavingsAccount.temp[i]).getName() >= self._accounts.get(SavingsAccount.temp[i+1]).getName():
temp = SavingsAccount.temp[i]
SavingsAccount.temp[i] = SavingsAccount.temp[i+1]
SavingsAccount.temp[i+1] = temp
for i in SavingsAccount.temp:
pTemp.append(self._accounts[i])
return '\n'.join(map(str, pTemp))
def add(self, account):
"""Inserts an account using its PIN as a key."""
self._accounts[account.getPin()] = account
def remove(self, pin):
return self._accounts.pop(pin, None)
def get(self, pin):
return self._accounts.get(pin, None)
def computeInterest(self):
"""Computes interest for each account and
returns the total."""
total = 0.0
for account in self._accounts.values():
total += account.computeInterest()
return total
class SavingsAccount(object):
"""This class represents a Savings account
with the owner's name, PIN, and balance."""
RATE = 0.02
temp = []
def __init__(self, name, pin, balance = 0.0):
self._name = name
self._pin = pin
self._balance = balance
SavingsAccount.temp.append(self)
def __str__(self):
result = 'Name: ' + self._name + '\n'
result += 'PIN: ' + self._pin + '\n'
result += 'Balance: ' + str(self._balance)
return result
def getBalance(self):
return self._balance
def getName(self):
return self._name
def getPin(self):
return self._pin
def deposit(self, amount):
"""Deposits the given amount and returns the
new balance."""
self._balance += amount
return self._balance
def withdraw(self, amount):
"""Withdraws the given amount.
Returns None if successful, or an
error message if unsuccessful."""
if amount < 0:
return 'Amount must be >= 0'
elif self._balance < amount:
return 'Insufficient funds'
else:
self._balance -= amount
return None
def computeInterest(self):
"""Computes, deposits, and returns the interest."""
interest = self._balance * SavingsAccount.RATE
self.deposit(interest)
def main():
bank = Bank()
bank.add(SavingsAccount("Zelda","1003",5000.00))
bank.add(SavingsAccount("Wilma","1001",4000.00))
bank.add(SavingsAccount("Fred","1002",1000.00))
print bank
main()
I think the question expects you to define ordering in the SavingsAccount class, that is, be able to determine whether an instance of SavingAccounts comes after or before another instance of SavingAccount. I don't want to write any spoiler here, but tell me if my hint is not enough ;).
UPDATE
Also, a common source of errors in Python with string ordering : a comes before z which comes before A which comes before Z ...
UPDATE2
more hints ;)
What you really want here is to sort a list of instances of SavingAccount according to a given criteria. There are 2 way to do this kind of thing. You can either :
have the one doing the sorting take care of it
or you can have the instances stored in your list taking care of it.
The second option is usually better because "the class to be sorted" should know better than anybody else how to sort itself (it's about encapsulation : not letting people outside control how your class works). Even though the question is not really clear, and the example is not very good (in my opinion), this is the option they would like you to chose.
The idea is that the Bank should just do something like this :
class Bank(object):
def __str__(self):
"""Return the string rep of the entire bank."""
#get a sorted copy of the list
#using default SavingAccount comparison
pTemp =sorted(self._accounts)
return '\n'.join(map(str, pTemp))
And SavingAccount contains information about how to sort.
You may want to have a look at this article from the PythonInfo Wiki.
Also: http://docs.python.org/reference/datamodel.html#object.__lt__

Categories