How can we modify data that is in a shelve? - python

I have opened a shelve using the following code:
#!/usr/bin/python
import shelve #Module:Shelve is imported to achieve persistence
Accounts = 0
Victor = {'Name':'Victor Hughes','Email':'victor#yahoo.com','Deposit':65000,'Accno':'SA456178','Acctype':'Savings'}
Beverly = {'Name':'Beverly Dsilva','Email':'bevd#hotmail.com','Deposit':23000,'Accno':'CA432178','Acctype':'Current'}
def open_shelf(name='shelfile.shl'):
global Accounts
Accounts = shelve.open(name) #Accounts = {}
Accounts['Beverly']= Beverly
Accounts['Victor']= Victor
def close_shelf():
Accounts.close()
I am able to append values to the shelve but unable to modify the values.
I have defined a function Deposit() from which I would like to modify the data present in the shelve.But it gives me the following error:
Traceback (most recent call last):
File "./functest.py", line 16, in <module>
Deposit()
File "/home/pavitar/Software-Development/Python/Banking/Snippets/Depositfunc.py", line 18, in Deposit
for key in Accounts:
TypeError: 'int' object is not iterable
Here is my Function:
#!/usr/bin/python
import os #This module is imported so as to use clear function in the while-loop
from DB import * #Imports the data from database DB.py
def Deposit():
while True:
os.system("clear") #Clears the screen once the loop is re-invoked
input = raw_input('\nEnter the A/c type: ')
flag=0
for key in Accounts:
if Accounts[key]['Acctype'].find(input) != -1:
amt = input('\nAmount of Deposit: ')
flag+=1
Accounts[key]['Deposit'] += amt
if flag == 0:
print "NO such Account!"
if __name__ == '__main__':
open_shelf()
Deposit()
close_shelf()
I'm new to Python.Please help.Correct me if I'm wrong.I need someone to give a little bit of explanation as to the functioning of this code.I'm confused.

Firstly, don't use a global for Accounts, rather pass it back and forth. Using the global caused your error. Like this:
def open_shelf(name='shelfile.shl'):
Accounts = shelve.open(name) #Accounts = {}
...
return Accounts
def close_shelf(Accounts):
Accounts.close()
def Deposit(Accounts):
...
if __name__ == '__main__':
Accounts = open_shelf()
Deposit(Accounts)
close_shelf(Accounts)
Secondly, don't redefine built-in functions. In Deposit(), you assign the result of raw_input to a variable named input:
input = raw_input('\nEnter the A/c type: ')
Four lines later, you try to use the built-in input function:
amt = input('\nAmount of Deposit: ')
But that won't work because input has been redefined!
Thirdly, when iterating over shelved items, follow the pattern of 1) grab shelved item, 2) mutate item, 3) write mutated item back to shelf. Like so:
for key, acct in Accounts.iteritems(): # grab a shelved item
if val['Acctype'].find(input) != -1:
amt = input('\nAmount of Deposit: ')
flag+=1
acct['Deposit'] += amt # mutate the item
Accounts[key] = acct # write item back to shelf
(This third bit of advice was tweaked from hughdbrown's answer.)

I think you'd have more luck like this:
for key, val in Accounts.iteritems():
if val['Acctype'].find(input) != -1:
amt = input('\nAmount of Deposit: ')
flag+=1
val['Deposit'] += amt
Accounts[key] = val

Related

Why does replace not work for this substring?

I'm completely new to Python so I apologize in advance for the bad code. I am trying to replace the sub-string of strings in a list, but the list returned is the same. In the code, there is a list of accounts and another list of transactions given by the user, I am supposed to check whether the command is add or sub to either add to balance or subtract from it. Here is the format for both lists:
F1:
ACCOUNT NUMBER | PIN CODE | BALANCE
F2:
COMMAND | AMOUNT | ACCOUNT NUMBER | PIN CODE
And here is the code I'm working with:
import sys
F1 = sys.argv[1]
F2 = sys.argv[2]
def file_reader(filePath):
file = open(filePath, 'r')
data = file.read()
file.close()
return data
def splitter(str):
return str.split()
def joiner(list):
return '\n'.join(list)
def file_writer(filePath, str):
file = open(filePath, 'w')
file.write(str)
def make_transaction(accounts, transactions):
for i in range(len(transactions)):
# Check account number & pin
if accounts[i][0:4] == transactions[i][9:13]:
if accounts[i][5:9] == transactions[i][-4:]:
# Check whether the user wants to add or subtract from balance
if 'add' in transactions[i]:
balance = int(accounts[i][10:])
transaction = int(transactions[i][4:8])
balance += transaction
balanceStr = str(balance)
accounts[i].replace(accounts[i][10:], balanceStr)
elif 'sub' in transactions[i]:
balance = int(accounts[i][10:])
transaction = int(transactions[i][4:8])
if balance > transaction:
balance -= transaction
balanceStr = str(balance)
accounts[i].replace(accounts[i][10:], balanceStr)
return accounts
file1 = fileReader(F1)
file1Data = splitter(file1)
file2 = fileReader(F2)
file2Data = splitter(file2)
print(file1Data)
print(file2Data)
print(makeTransaction(file1Data, file2Data))
This is the output:
Program Failed for Input: /tmp/a1 /tmp/tx
Expected Output:
Your Program Output: ['1000|1234|10000', '1020|2222|0', '3000|3344|1000', '2020|1234|90000']
['add|1000|1000|1234', 'sub|1000|1020|2222', 'sub|1000|3000|3344']
['1000|1234|10000', '1020|2222|0', '3000|3344|1000', '2020|1234|90000']
Any help would be appreciated. Thank you.
.replace is risky use case for what you are trying to solve. It is using pattern matching to do the change and in case your substring, i.e. balance[10:] would accidentally match with balance[0:5] you would silently get very unpredictable results.
Instead, I would suggest you replace
accounts[i].replace(accounts[i][10:], balanceStr)
with
accounts[i] = accounts[i][0:9] + balanceStr
On an extra note, assuming that your input data is split by |, you should create data holders for your input data instead of working with the strings as the strings are volatile and your code could start acting unpredictably if your customer performed a very large transaction.
I would instead do something along the lines of account_number, pin_code, balance = '1000|1234|10000'.split('|') and load the data into a list where each row would represent one account or one transaction.
This should be completely rewritten:
def make_transaction(accounts, transactions):
for i in range(len(transactions)):
# Check account number & pin
if accounts[i][0:4] == transactions[i][9:13]:
if accounts[i][5:9] == transactions[i][-4:]:
# Check whether the user wants to add or subtract from balance
if 'add' in transactions[i]:
balance = int(accounts[i][10:])
transaction = int(transactions[i][4:8])
balance += transaction
balanceStr = str(balance)
accounts[i].replace(accounts[i][10:], balanceStr)
elif 'sub' in transactions[i]:
balance = int(accounts[i][10:])
transaction = int(transactions[i][4:8])
if balance > transaction:
balance -= transaction
balanceStr = str(balance)
accounts[i].replace(accounts[i][10:], balanceStr)
return accounts
for example, I'd rather do this:
def make_transaction(accounts, transactions) :
for a,t in zip(accounts, transactions) :
a_num, a_pin, a_balance = a.split('|')
t_command, t_amount, t_num, t_pin = t.split('|')
if a_num == t_num and a_pin == t_pin :
if t_command == 'add' :
# do your stuff
elif t_command == 'sub' :
# do something else
and so on... working on string with indices is very very wrong.

How to remove an object stored in a Pickle file with Python

I am working on a database program that stores objects in a pickle file. The objects have three attributes: Name, Grade, and Average. I have been able to save each Student object to a pickle file, and I can read the file back and display it's contents. However, I cannot figure out how to delete an object from the file using an index number. I want the user to be able to type in a number, and then delete that numbered object (so if I type in 2, the second object is deleted.) Right now my code is able to delete an item from a pickle list, but I cannot get it to work with an object. When I try to delete a student, I get the following error: TypeError: 'Student' object does not support item deletion. Does anyone know how I could delete an object from my pickle file?
My code is below:
import pickle
class Student():
def __init__(self,nam,grd,avg):
self.name = nam
self.grade = grd
self.average = avg
def get_details(self):
print(self.name, self.grade, self.average)
def create_item():
new_student = Student(input("Enter name: "),input("Enter grade: "), input("Enter average: "))
save_object(new_student, 'student_data.pkl')
def clear_database():
file = open('student_data.pkl', 'w')
file.close()
def save_object(obj, filename):
with open(filename, 'ab') as output:
pickle.dump(obj, output, pickle.HIGHEST_PROTOCOL)
def unpickle_database(filename):
with open(filename, 'rb') as f:
while True:
try:
yield pickle.load(f)
except EOFError:
break
def display_database():
print("\nName: Grade: Average: ")
for student in unpickle_database('student_data.pkl'):
property_list = [student.name, student.grade, student.average]
print(''.join([v.ljust(20,' ') for v in property_list]))
def delete_student(student_to_delete):
with open('student_data.pkl', 'rb') as f:
db = pickle.load(f)
try:
del db[student_to_delete]
except KeyError:
print("{user} doesn't exist in db".format(user=user_to_delete))
while True:
user_input = input("\nType \"Clear\" to clear the database. Type \"Add\" to add a student. Type \"Display\" to display the database contents. Type \"Quit\" to quit the program. Type \"Remove\" to remove a student.\n")
if user_input == "Quit":
break
if user_input == "Clear":
Student.clear_database()
print("\nThe database has been cleared.")
elif user_input == "Add":
Student.create_item()
display_database()
elif user_input == "Display":
display_database()
elif user_input == "Remove":
student_to_delete = input("Type the student number that you would like to delete: ")
delete_student(student_to_delete)
You can't delete objects directly from the Pickle file. I mean, you could do that, but it'd require you to parse its structure to determine where individual objects' data begins and ends. That's tedious and not very rewarding.
What you can do, though, is read all of the pickled students as a list:
students = list(unpickle_database(filename))
Then, delete one of the students from the list with del students[index] and finally re-pickle all the remaining students using said list:
for student in students:
save_object(student, filename)

Issues with pickling in python - items not saving to file once program exits

I am trying to create a python program to save my friends' birthdays and access them easily and check for birthdays each day(I am not great at remembering dates and I never use facebook), but when I add a new birthday it is only accessible until I end the program - it then disappears again. I have been struggling with this for a while now and would really appreciate help fixing the error. Thanks!
import time
import pickle
def main():
birthday_file = open('birthdays_dict.dat','ab')
birthday_doc = open('birthdays_dict.dat','rb')
birthdays = pickle.load(birthday_doc)
date = time.strftime("%m/%d")
again = 'y'
while again.lower() == 'y' or again.lower() == 'yes':
choice = menu_choice()
if choice == 1:
name = add_name()
birthday = add_birthday()
birthdays[name] = birthday
print(name)
print(birthday)
pickle.dump(birthdays,birthday_file)
elif choice == 2:
print('Birthdays today(' + date + '):')
birth_today = {}
for key, value in birthdays.items():
if value == date:
print(key)
elif choice == 3:
search_name = input('Enter name to search: ')
print()
if search_name in birthdays:
print(birthdays[search_name])
if birthdays[search_name] == date:
print('Their birthday is today!')
else:
print('Not found')
else:
print('Not a valid selection!')
print()
again = go_again()
birthday_file.close()
birthday_doc.close()
Your problem is that you keep appending new dicts onto the file instead of replacing the old one, but then at startup you only load the very first one instead of all of them.
To fix this, you need to change this:
birthday_file = open('birthdays_dict.dat','ab')
… to this:
birthday_file = open('birthdays_dict.dat','wb')
But don't do that change on its own, because that will erase the file before you've read the old version!
You probably want to do something like this at the top of the function:
with open('birthdays_dict.dat', 'rb') as birthday_doc:
birthdays = pickle.load(birthday_doc)
I used a with statement so the file will automatically get closed right after the load, so it's definitely safe for us to overwrite it later.
Then later, when you want to write to the file, that's when you open it in w mode to erase the file and overwrite it with the new version—at which point you might as well close it immediately, because if you ever do write to it again, you're going to want to erase it again first, so let's use with again:
with open('birthdays_dict.dat', 'wb') as birthday_doc:
pickle.dump(birthdays, birthday_doc)

Python 3 Unit tests with user input

I'm absolutely brand new to Python unit test. I need to use it for a project I have to submit. I sort of have an idea of where to begin, it looks like we basically put in test parameters to functions we have defined in our program and we enter the expected result. If the expected result is output, we get OK, otherwise we will get Failure, or an error.
So my problem is that I have multiple user inputs stored into variables that are within for loops or while loops. I don't know where to even begin with this to set test values in for them.
Here is all of my code:
studentTripExpenses = {}
def dictCreate(studentAmount):
for i in range(0, studentAmount):
studentName = input("What is the name of the student? ")
expenseList = []
print("Enter 'done' to move to the next student.")
while True:
expense = input("What is the cost of this expense? ")
if expense.lower() == 'done':
break
elif (float(expense) >= 0) or (float(expense) < 0):
expenseList.append(float(expense))
elif not expense.isdigit():
print("Please enter a number or enter 'done' to move on.")
studentTripExpenses[studentName] = expenseList
return studentTripExpenses
def studentCost(dct):
for i in dct:
#Variable for individual costs of student
personalCost = 0
#Determines the total cost for each student
for x in dct[i]:
personalCost = personalCost + x
#Sets each students value to their total cost to two decimal places
dct[i] = float("%.2f" % personalCost)
return dct
def amountsDue(expenseLst, studentAvgPrice):
#Runs through the dictionary of students and individual total trip costs
for key in expenseLst:
maxPerson = max(expenseLst, key=expenseLst.get)
costDifference = 0
#Determines who owes who how much money
if max(expenseLst.values()) > expenseLst[key]:
costDifference = studentAvgPrice-expenseLst[key]
if (costDifference < 0):
costDifference = costDifference * -1
print("%s owes %s $%.2f" % (key, maxPerson, costDifference))
def main():
numOfStudents = int(input("How many students are going on the trip? "))
studentCostDict = dictCreate(numOfStudents)
studentTripExpenses = studentCost(studentCostDict)
totalCost = 0
#Gets the total cost for all students
for key in (studentTripExpenses):
totalCost = totalCost + studentTripExpenses[key]
#Changes the total cost to 2 decimal places
totalCost = float("%.2f" % totalCost)
#Determines the average amount spent per student
avgCost = float("%.2f" % (totalCost/len(studentTripExpenses)))
amountsDue(studentTripExpenses, avgCost)
main()
You can use mocking, where you replace a function or class with a test-supplied version. You can do this with the unittest.mock() module.
In this case, you can patch the input() name in your module; instead of the built-in function, the mock object will be called:
from unittest import mock
from unittest import TestCase
import module_under_test
class DictCreateTests(TestCase):
#mock.patch('module_under_test.input', create=True)
def testdictCreateSimple(self, mocked_input):
mocked_input.side_effect = ['Albert Einstein', '42.81', 'done']
result = dictCreate(1)
self.assertEqual(result, {'Albert Einstein': [42.81]})
Because input doesn't exist in your module (it is a built-in function), I told the mock.patch() decorator to create the name; now this input will be used instead of the built-in function.
The side_effect attribute lets you state multiple results; each time the mock is called, it'll return the next value in that list. So the first time 'Albert Einstein' is returned, the next time '42.81', etc.
Together, this lets you simulate actual user inputs.
If you do your test right, you'll notice that there is a bug in your function; the float() call will throw a ValueError exception when anything other than done or a valid numeric value is entered. You need to rework your code to account for that. Try with mocked_input.side_effect = ['Albert Einstein', 'Not an expense', '42.81', 'done'] to trigger the bug.
In case we do not have classes.
In the names.py file, we have the get_names function.
def get_names() -> list:
names = [str(input("Enter name: "))]
while str(input("Do you want to add another name")) == "Y":
names.append(str(input("Enter name: ")))
return categories
In the test_names.py file, we can write test like the following
import numpy as np
from unittest import mock
from src.main.names import get_names
#mock.patch('src.main.names.input', create=True)
def test_should_get_names_from_users(mocked_input):
mocked_input.side_effect = ["John", "Y", "Robert", "N"]
actual_names = get_names()
expected_names = ['John', "Robert"]
assert actual_names == expected_names

finding item in text file during 2nd read

In the beginning of my program, i opened a file with f = open("foods.txt", "r+"). Later I called this method i created
def findFood(food):
foodRegex = re.compile(r'(?P<food>\S+)\s+\-.*')
for line in f.readlines():
print line
duplicateFound = re.search(foodRegex, line)
if duplicateFound.group('food') == food:
return duplicateFound
else:
return False
However I run the method again. But my program doesn't work the way I want it to. Specifically
def build_meal_plan():
number_of_items = int(raw_input("How many items would you like to add to your meal plan? "))
count = 0
while number_of_items > 0:
print count
food = raw_input("Enter in food name: ")
print food
if findFood(food):
servings = int(raw_input("Number of servings: "))
else:
print "Food not found! Try again? (y/n): ",
choice = raw_input()
if choice == 'y' or choice == "yes":
number_of_items += 1
else:
return
However during the 2nd run of my findFood method I cannot locate an item i know exists within the .txt file. I am not sure why I cannot find the same item I found in the text file during the first run. My assumption is that you can only go through a txt file once.
Once you call f.readlines(), you are at the end of the file. To return to the start, so you can go through it again, call f.seek(0):
def findFood(food):
foodRegex = re.compile(r'(?P<food>\S+)\s+\-.*')
for line in f.readlines():
...
f.seek(0)
Alternatively, you can import the contents of the file to a list:
def import_file(filename):
with open(filename) as f:
content = [line.strip() for line in f]
return content
And use that instead of referring back to the file.
def findFood(food, data):
foodRegex = re.compile(r'(?P<food>\S+)\s+\-.*')
for line in data:
...
Then you don't need to worry about returning to the start.

Categories

Resources