Been studding python for a while now, did not ask anything since AI was useful for clarifying most of the simple stuff. In advancing Python programming course, in chapter 10, one of the last exercise was phonebook exercise as a final OOP exercise. My code has passed all of the test cases but on one I got stuck. I tried debugging for about 4h and could not figure out so I'm in doubt weather a test might be wrong since I'm not familiar with tests. Here is my entire OOP code followed by a test on which code fails with an output of a failed test. I would really apreciate if anyone could clarify my mistake.
class PhoneBook:
def __init__(self):
self.__persons = {}
def add_number(self, name: str, number: str):
if name in self.__persons:
self.__persons[name].add_number(number)
else:
self.__persons[name] = Person(name=name, numbers=[number])
def get_entry(self, name: str):
# IMPORTANT this part bellof (2-3 lines) is problematic for the given test case...
if name in self.__persons and not self.__persons[name].address():
print(self.__persons[name].numbers()[0])
print("address unknown")
elif name in self.__persons and not self.__persons[name].numbers():
print("number unknown")
print(self.__persons[name].address())
elif name not in self.__persons:
print("address unknown")
print("number unknown")
else:
for num in self.__persons[name].numbers():
print(num)
print(self.__persons[name].address())
def add_address(self, name:str, address:str):
if name in self.__persons:
self.__persons[name].add_address(address)
else:
self.__persons[name] = Person(name = name, address=address)
def all_entries(self):
return self.__persons
class PhoneBookApplication:
def __init__(self):
self.__phonebook = PhoneBook()
def help(self):
print("commands: ")
print("0 exit")
print("1 add number")
print("2 search")
print("3 add address")
def add_number(self):
name = input("name: ")
number = input("number: ")
self.__phonebook.add_number(name, number)
def search(self):
name = input("name: ")
self.__phonebook.get_entry(name)
def add_address(self):
name = input("name: ")
address = input("address: ")
if address:
self.__phonebook.add_address(name, address)
else:
raise ValueError("Enter a valid address.")
def execute(self):
self.help()
while True:
print("")
command = input("command: ")
if command == "0":
break
elif command == "1":
self.add_number()
elif command == "2":
self.search()
elif command == "3":
self.add_address()
else:
self.help()
class Person:
def __init__(self, name:str, address:str = None, numbers:list = None):
self.__name = name
self.__address = address if address is not None else ""
self.__numbers = numbers if numbers is not None else []
def name(self):
return self.__name
def numbers(self):
return self.__numbers
def address(self):
if self.__address:
return self.__address
else:
return None
def add_number(self, number:str):
if number:
self.__numbers.append(number)
else:
raise ValueError("Enter a valid number.")
def add_address(self, address:str):
if address:
self.__address = address
else:
raise ValueError("Enter a valid address.")
def __str__(self):
return f"{self.numbers()}\n{self.address()}"
phonebook = PhoneBookApplication()
phonebook.execute()
TEST FAIL OUTPUT:
#IMPORTANT TEST FAIL OUTPUT:
# Test failed
# PhoneBook2_Part2Test: test_3_works_many_numbers
# The output of your program should be
# 040-999999
# with input
# 1
# Emilia
# 09-123456
# 1
# Emilia
# 040-999999
# 2
# Emilia
# 0
# Now the output was
# commands:
# 0 exit
# 1 add number
# 2 search
# 3 add address
#
# 09-123456
# address unknown
TEST ON WHICH CODE FAILS:
import unittest
from unittest.mock import patch
from tmc import points, reflect
from tmc.utils import load, load_module, reload_module, get_stdout, check_source
from functools import reduce
import os
import os.path
import textwrap
from random import choice, randint
from datetime import date, datetime, timedelta
def test_3_works_many_numbers(self):
syote = ["1", "Emilia", "09-123456", "1", "Emilia", "040-999999", "2", "Emilia", "0"]
with patch('builtins.input', side_effect=syote):
try:
reload_module(self.module)
except:
self.fail(f"Check that the program works with input\n{s(syote)}")
output = get_stdout()
expected = "09-123456"
self.assertTrue(expected in output,
f"The output of your program should be\n{expected}\nwith input\n{s(syote)}\nNow the output was\n{output}")
expected = "040-999999"
self.assertTrue(expected in output,
f"The output of your program should be\n{expected}\nwith input\n{s(syote)}\nNow the output was\n{output}")
Been studding python for a while now, did not ask anything since AI was useful for clarifying most of the simple stuff. In advancing Python programming course, in chapter 10, one of the last exercise was phonebook exercise as a final OOP exercise. My code has passed all of the test cases but on one I got stuck. I tried debugging for about 4h and could not figure out so I'm in doubt weather a test might be wrong since I'm not familiar with tests. Here is my entire OOP code followed by a test on which code fails with an output of a failed test. I would really apreciate if anyone could clarify my mistake.
class PhoneBook:
def __init__(self):
self.__persons = {}
def add_number(self, name: str, number: str):
if name in self.__persons:
self.__persons[name].add_number(number)
else:
self.__persons[name] = Person(name=name, numbers=[number])
def get_entry(self, name: str):
# IMPORTANT this part bellof (2-3 lines) is problematic for the given test case...
if name in self.__persons and not self.__persons[name].address():
print(self.__persons[name].numbers()[0])
print("address unknown")
elif name in self.__persons and not self.__persons[name].numbers():
print("number unknown")
print(self.__persons[name].address())
elif name not in self.__persons:
print("address unknown")
print("number unknown")
else:
for num in self.__persons[name].numbers():
print(num)
print(self.__persons[name].address())
def add_address(self, name:str, address:str):
if name in self.__persons:
self.__persons[name].add_address(address)
else:
self.__persons[name] = Person(name = name, address=address)
def all_entries(self):
return self.__persons
class PhoneBookApplication:
def __init__(self):
self.__phonebook = PhoneBook()
def help(self):
print("commands: ")
print("0 exit")
print("1 add number")
print("2 search")
print("3 add address")
def add_number(self):
name = input("name: ")
number = input("number: ")
self.__phonebook.add_number(name, number)
def search(self):
name = input("name: ")
self.__phonebook.get_entry(name)
def add_address(self):
name = input("name: ")
address = input("address: ")
if address:
self.__phonebook.add_address(name, address)
else:
raise ValueError("Enter a valid address.")
def execute(self):
self.help()
while True:
print("")
command = input("command: ")
if command == "0":
break
elif command == "1":
self.add_number()
elif command == "2":
self.search()
elif command == "3":
self.add_address()
else:
self.help()
class Person:
def __init__(self, name:str, address:str = None, numbers:list = None):
self.__name = name
self.__address = address if address is not None else ""
self.__numbers = numbers if numbers is not None else []
def name(self):
return self.__name
def numbers(self):
return self.__numbers
def address(self):
if self.__address:
return self.__address
else:
return None
def add_number(self, number:str):
if number:
self.__numbers.append(number)
else:
raise ValueError("Enter a valid number.")
def add_address(self, address:str):
if address:
self.__address = address
else:
raise ValueError("Enter a valid address.")
def __str__(self):
return f"{self.numbers()}\n{self.address()}"
phonebook = PhoneBookApplication()
phonebook.execute()
TEST FAIL OUTPUT:
#IMPORTANT TEST FAIL OUTPUT:
# Test failed
# PhoneBook2_Part2Test: test_3_works_many_numbers
# The output of your program should be
# 040-999999
# with input
# 1
# Emilia
# 09-123456
# 1
# Emilia
# 040-999999
# 2
# Emilia
# 0
# Now the output was
# commands:
# 0 exit
# 1 add number
# 2 search
# 3 add address
#
# 09-123456
# address unknown
TEST ON WHICH CODE FAILS:
import unittest
from unittest.mock import patch
from tmc import points, reflect
from tmc.utils import load, load_module, reload_module, get_stdout, check_source
from functools import reduce
import os
import os.path
import textwrap
from random import choice, randint
from datetime import date, datetime, timedelta
def test_3_works_many_numbers(self):
syote = ["1", "Emilia", "09-123456", "1", "Emilia", "040-999999", "2", "Emilia", "0"]
with patch('builtins.input', side_effect=syote):
try:
reload_module(self.module)
except:
self.fail(f"Check that the program works with input\n{s(syote)}")
output = get_stdout()
expected = "09-123456"
self.assertTrue(expected in output,
f"The output of your program should be\n{expected}\nwith input\n{s(syote)}\nNow the output was\n{output}")
expected = "040-999999"
self.assertTrue(expected in output,
f"The output of your program should be\n{expected}\nwith input\n{s(syote)}\nNow the output was\n{output}")
Share
Improve this question
edited Mar 15 at 20:44
Carcigenicate
45.9k11 gold badges77 silver badges129 bronze badges
asked Mar 15 at 20:36
Miloš GrujićMiloš Grujić
193 bronze badges
1
- 1 This is not a very helpful question for the community, so I think you should just check your code more thoroughly.. But if you are stuck, check that you print all the numbers that the person has, if the address is missing - I think the requirement is that and currently you are printing just numbers[0] which is just the first number. IF you get it right, I think you should consider deleting the question – Luihis Commented Mar 15 at 21:09
1 Answer
Reset to default 0name mangling
Please don't do this.
class PhoneBook:
def __init__(self):
self.__persons = {}
Much better to assign self._persons
, with a single _
underscore.
Similarly for the Person attributes {name, address, numbers}.
use the "batteries included"
The add_number() method is tediously long. Prefer to rely on defaultdict(Person).
dataclass
The Person
class really wants to be a
@dataclass.
This aspect of the signature is obviously incorrect, as flagged by mypy:
address: str = None,
How do we know? Because a string is not None.
Similarly for numbers
, as None is clearly not a list.
property
This call-site access, and the implementation, feel a bit off.
if ... and not self.__persons[name].address():
...
def address(self):
if self.__address:
return self.__address
else:
return None
I mean, sure, it works.
But it wants to be an @property
,
without the ()
method call notation.
Similarly for a numbers
property.
Also, it's unclear how returning None
is somehow an improvement on returning
the empty container []
.
evaluate for side effects
def get_entry(self, name: str):
Thank you for the nice annotation.
But it doesn't go far enough;
it should point out that we return ... -> None:
Which highlights that this is the Wrong Name.
Do we "get" an entry and return it? No!
We "print" an entry.
Clearly this should be def print_entry
or def display_entry
, or something like that.
Names really matter; they affect how we reason about code.
mapping
Using {if, elif, elif} is fine as far as it goes.
You might prefer to use a dict
for dispatching to a function:
dispatch = {
"0": sys.exit,
"1": self.add_number,
"2": self.search,
"3": self.add_address,
}
obtaining input param via stdin
This seems ill advised.
def test_3_works_many_numbers(self):
syote = ["1", "Emilia", "09-123456", ... ]
with patch('builtins.input', side_effect=syote):
try:
reload_module(self.module)
I have no idea what "syote" means.
But it looks like you're trying to override sys.stdin
to present certain inputs to the target code.
A more appropriate way to "instrument the target code!"
with automated tests would be to refactor the function
into a pair of functions, one of which accepts simple
input parameters and the other which calls input()
.
Obviously consuming characters from stdin is a side effect,
so one will be a "pure" function, while the other will
have side effects.
The upside for your code base is you will find that some
kinds of functions are easier to test than others.
You might also consider defining a function which accepts input params from a file descriptor or a Path.