Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions exercises/practice/forth/.docs/instructions.append.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Instructions append

## Customizing and Raising Exceptions

Sometimes it is necessary to both [customize](https://docs.python.org/3/tutorial/errors.html#user-defined-exceptions) and [`raise`](https://docs.python.org/3/tutorial/errors.html#raising-exceptions) exceptions in your code. When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging.

Custom exceptions can be created through new exception classes (see [`classes`](https://docs.python.org/3/tutorial/classes.html#tut-classes) for more detail.) that are typically subclasses of [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception).

For situations where you know the error source will be a derivative of a certain exception type, you can choose to inherit from one of the [`built in error types`](https://docs.python.org/3/library/exceptions.html#base-classes) under the _Exception_ class. When raising the error, you should still include a meaningful message.

This particular exercise requires that you create a _custom exception_ to be [raised](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement)/"thrown" when the stack is not sufficiently filled. The tests will only pass if you customize an appropriate exception, `raise` that exception, and include appropriate error messages.


```python
# subclassing the Exception to create a StackUnderflowError
class StackUnderflowError(Exception):
"""Exception raised when Stack is not full.
message: explanation of the error.
"""
def __init__(self, message):
self.message = message


# raising a StackUnderflowError
raise StackUnderflowError("Insufficient number of items in stack")
```

Additionally, this exercise requires that you raise several `built-in exceptions` with error messages.
To raise a `built-in exception` with a message, write the message as an argument to the `exception` type:

```python
# an example when division by zero is attempted.
raise ZeroDivisionError("divide by zero")

#an example when the operation is undefined.
raise ValueError("undefined operation")
```
18 changes: 11 additions & 7 deletions exercises/practice/forth/.meta/example.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
class StackUnderflowError(Exception):
pass
"""Exception raised when Stack is not full.
message: explanation of the error.
"""
def __init__(self, message):
self.message = message


def is_integer(string):
Expand All @@ -20,11 +24,11 @@ def evaluate(input_data):
values.pop(0)
key = values.pop(0).lower()
if is_integer(key):
raise ValueError("Integers cannot be redefined")
raise ValueError("illegal operation")
defines[key] = [
x
for v in values
for x in defines.get(v, [v])
x
for v in values
for x in defines.get(v, [v])
]
stack = []
input_data = input_data[-1].split()
Expand All @@ -44,7 +48,7 @@ def evaluate(input_data):
elif word == '/':
divisor = stack.pop()
if divisor == 0:
raise ZeroDivisionError("Attempted to divide by zero")
raise ZeroDivisionError("divide by zero")
stack.append(int(stack.pop() / divisor))
elif word == 'dup':
stack.append(stack[-1])
Expand All @@ -56,7 +60,7 @@ def evaluate(input_data):
elif word == 'over':
stack.append(stack[-2])
else:
raise ValueError("{} has not been defined".format(word))
raise ValueError("undefined operation")
except IndexError:
raise StackUnderflowError("Insufficient number of items in stack")
return stack
18 changes: 12 additions & 6 deletions exercises/practice/forth/.meta/template.j2
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,23 @@
{%- if case is error_case %}
{%- if case["expected"]["error"] == "divide by zero" %}
# {{ case["expected"]["error"] }}
with self.assertRaisesWithMessage(ZeroDivisionError):
with self.assertRaises(ZeroDivisionError) as err:
{{ case["property"] }}({{ case["input"]["instructions"] }})
self.assertEqual(type(err.exception), ZeroDivisionError)
self.assertEqual(str(err.exception.args[0]), "{{ case["expected"]["error"] }}")
{%- else %}
{%- if "stack" in case["expected"]["error"] %}
with self.assertRaisesWithMessage(StackUnderflowError):
with self.assertRaises(StackUnderflowError) as err:
{{ case["property"] }}({{ case["input"]["instructions"] }})
self.assertEqual(type(err.exception), StackUnderflowError)
self.assertEqual(str(err.exception.args[0]), "Insufficient number of items in stack")
{%- else %}
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
{{ case["property"] }}({{ case["input"]["instructions"] }})
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(str(err.exception.args[0]), "{{ case["expected"]["error"] }}")
{%- endif %}
{%- endif %}
{{ case["property"] }}({{ case["input"]["instructions"] }})
{%- else %}
self.assertEqual({{ case["property"] }}({{ case["input"]["instructions"] }}), {{ case["expected"] }})
{%- endif %}
Expand All @@ -27,5 +35,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
{{ test_case(case["description"], subcase) }}
{% endfor %}
{% endfor %}

{{ macros.footer() }}
104 changes: 79 additions & 25 deletions exercises/practice/forth/forth_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,58 @@ def test_addition_can_add_two_numbers(self):
self.assertEqual(evaluate(["1 2 +"]), [3])

def test_addition_errors_if_there_is_nothing_on_the_stack(self):
with self.assertRaisesWithMessage(StackUnderflowError):
with self.assertRaises(StackUnderflowError) as err:
evaluate(["+"])
self.assertEqual(type(err.exception), StackUnderflowError)
self.assertEqual(
str(err.exception.args[0]), "Insufficient number of items in stack"
)

def test_addition_errors_if_there_is_only_one_value_on_the_stack(self):
with self.assertRaisesWithMessage(StackUnderflowError):
with self.assertRaises(StackUnderflowError) as err:
evaluate(["1 +"])
self.assertEqual(type(err.exception), StackUnderflowError)
self.assertEqual(
str(err.exception.args[0]), "Insufficient number of items in stack"
)

def test_subtraction_can_subtract_two_numbers(self):
self.assertEqual(evaluate(["3 4 -"]), [-1])

def test_subtraction_errors_if_there_is_nothing_on_the_stack(self):
with self.assertRaisesWithMessage(StackUnderflowError):
with self.assertRaises(StackUnderflowError) as err:
evaluate(["-"])
self.assertEqual(type(err.exception), StackUnderflowError)
self.assertEqual(
str(err.exception.args[0]), "Insufficient number of items in stack"
)

def test_subtraction_errors_if_there_is_only_one_value_on_the_stack(self):
with self.assertRaisesWithMessage(StackUnderflowError):
with self.assertRaises(StackUnderflowError) as err:
evaluate(["1 -"])
self.assertEqual(type(err.exception), StackUnderflowError)
self.assertEqual(
str(err.exception.args[0]), "Insufficient number of items in stack"
)

def test_multiplication_can_multiply_two_numbers(self):
self.assertEqual(evaluate(["2 4 *"]), [8])

def test_multiplication_errors_if_there_is_nothing_on_the_stack(self):
with self.assertRaisesWithMessage(StackUnderflowError):
with self.assertRaises(StackUnderflowError) as err:
evaluate(["*"])
self.assertEqual(type(err.exception), StackUnderflowError)
self.assertEqual(
str(err.exception.args[0]), "Insufficient number of items in stack"
)

def test_multiplication_errors_if_there_is_only_one_value_on_the_stack(self):
with self.assertRaisesWithMessage(StackUnderflowError):
with self.assertRaises(StackUnderflowError) as err:
evaluate(["1 *"])
self.assertEqual(type(err.exception), StackUnderflowError)
self.assertEqual(
str(err.exception.args[0]), "Insufficient number of items in stack"
)

def test_division_can_divide_two_numbers(self):
self.assertEqual(evaluate(["12 3 /"]), [4])
Expand All @@ -53,16 +77,26 @@ def test_division_performs_integer_division(self):

def test_division_errors_if_dividing_by_zero(self):
# divide by zero
with self.assertRaisesWithMessage(ZeroDivisionError):
with self.assertRaises(ZeroDivisionError) as err:
evaluate(["4 0 /"])
self.assertEqual(type(err.exception), ZeroDivisionError)
self.assertEqual(str(err.exception.args[0]), "divide by zero")

def test_division_errors_if_there_is_nothing_on_the_stack(self):
with self.assertRaisesWithMessage(StackUnderflowError):
with self.assertRaises(StackUnderflowError) as err:
evaluate(["/"])
self.assertEqual(type(err.exception), StackUnderflowError)
self.assertEqual(
str(err.exception.args[0]), "Insufficient number of items in stack"
)

def test_division_errors_if_there_is_only_one_value_on_the_stack(self):
with self.assertRaisesWithMessage(StackUnderflowError):
with self.assertRaises(StackUnderflowError) as err:
evaluate(["1 /"])
self.assertEqual(type(err.exception), StackUnderflowError)
self.assertEqual(
str(err.exception.args[0]), "Insufficient number of items in stack"
)

def test_combined_arithmetic_addition_and_subtraction(self):
self.assertEqual(evaluate(["1 2 + 4 -"]), [-1])
Expand All @@ -77,8 +111,12 @@ def test_dup_copies_the_top_value_on_the_stack(self):
self.assertEqual(evaluate(["1 2 dup"]), [1, 2, 2])

def test_dup_errors_if_there_is_nothing_on_the_stack(self):
with self.assertRaisesWithMessage(StackUnderflowError):
with self.assertRaises(StackUnderflowError) as err:
evaluate(["dup"])
self.assertEqual(type(err.exception), StackUnderflowError)
self.assertEqual(
str(err.exception.args[0]), "Insufficient number of items in stack"
)

def test_drop_removes_the_top_value_on_the_stack_if_it_is_the_only_one(self):
self.assertEqual(evaluate(["1 drop"]), [])
Expand All @@ -87,8 +125,12 @@ def test_drop_removes_the_top_value_on_the_stack_if_it_is_not_the_only_one(self)
self.assertEqual(evaluate(["1 2 drop"]), [1])

def test_drop_errors_if_there_is_nothing_on_the_stack(self):
with self.assertRaisesWithMessage(StackUnderflowError):
with self.assertRaises(StackUnderflowError) as err:
evaluate(["drop"])
self.assertEqual(type(err.exception), StackUnderflowError)
self.assertEqual(
str(err.exception.args[0]), "Insufficient number of items in stack"
)

def test_swap_swaps_the_top_two_values_on_the_stack_if_they_are_the_only_ones(self):
self.assertEqual(evaluate(["1 2 swap"]), [2, 1])
Expand All @@ -99,12 +141,20 @@ def test_swap_swaps_the_top_two_values_on_the_stack_if_they_are_not_the_only_one
self.assertEqual(evaluate(["1 2 3 swap"]), [1, 3, 2])

def test_swap_errors_if_there_is_nothing_on_the_stack(self):
with self.assertRaisesWithMessage(StackUnderflowError):
with self.assertRaises(StackUnderflowError) as err:
evaluate(["swap"])
self.assertEqual(type(err.exception), StackUnderflowError)
self.assertEqual(
str(err.exception.args[0]), "Insufficient number of items in stack"
)

def test_swap_errors_if_there_is_only_one_value_on_the_stack(self):
with self.assertRaisesWithMessage(StackUnderflowError):
with self.assertRaises(StackUnderflowError) as err:
evaluate(["1 swap"])
self.assertEqual(type(err.exception), StackUnderflowError)
self.assertEqual(
str(err.exception.args[0]), "Insufficient number of items in stack"
)

def test_over_copies_the_second_element_if_there_are_only_two(self):
self.assertEqual(evaluate(["1 2 over"]), [1, 2, 1])
Expand All @@ -113,12 +163,20 @@ def test_over_copies_the_second_element_if_there_are_more_than_two(self):
self.assertEqual(evaluate(["1 2 3 over"]), [1, 2, 3, 2])

def test_over_errors_if_there_is_nothing_on_the_stack(self):
with self.assertRaisesWithMessage(StackUnderflowError):
with self.assertRaises(StackUnderflowError) as err:
evaluate(["over"])
self.assertEqual(type(err.exception), StackUnderflowError)
self.assertEqual(
str(err.exception.args[0]), "Insufficient number of items in stack"
)

def test_over_errors_if_there_is_only_one_value_on_the_stack(self):
with self.assertRaisesWithMessage(StackUnderflowError):
with self.assertRaises(StackUnderflowError) as err:
evaluate(["1 over"])
self.assertEqual(type(err.exception), StackUnderflowError)
self.assertEqual(
str(err.exception.args[0]), "Insufficient number of items in stack"
)

def test_user_defined_words_can_consist_of_built_in_words(self):
self.assertEqual(evaluate([": dup-twice dup dup ;", "1 dup-twice"]), [1, 1, 1])
Expand Down Expand Up @@ -146,12 +204,16 @@ def test_user_defined_words_can_define_word_that_uses_word_with_the_same_name(se
self.assertEqual(evaluate([": foo 10 ;", ": foo foo 1 + ;", "foo"]), [11])

def test_user_defined_words_cannot_redefine_non_negative_numbers(self):
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
evaluate([": 1 2 ;"])
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(str(err.exception.args[0]), "illegal operation")

def test_user_defined_words_errors_if_executing_a_non_existent_word(self):
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
evaluate(["foo"])
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(str(err.exception.args[0]), "undefined operation")

def test_case_insensitivity_dup_is_case_insensitive(self):
self.assertEqual(evaluate(["1 DUP Dup dup"]), [1, 1, 1, 1])
Expand All @@ -170,11 +232,3 @@ def test_case_insensitivity_user_defined_words_are_case_insensitive(self):

def test_case_insensitivity_definitions_are_case_insensitive(self):
self.assertEqual(evaluate([": SWAP DUP Dup dup ;", "1 swap"]), [1, 1, 1, 1])

# Utility functions
def assertRaisesWithMessage(self, exception):
return self.assertRaisesRegex(exception, r".+")


if __name__ == "__main__":
unittest.main()
14 changes: 14 additions & 0 deletions exercises/practice/go-counting/.docs/instructions.append.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Instructions append

## Exception messages

Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message.

This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" a `ValueError` when given invalid coordinates. The tests will only pass if you both `raise` the `exception` and include a message with it.

To raise a `ValueError` with a message, write the message as an argument to the `exception` type:

```python
# when the coordinates for the piece are invalid
raise ValueError('Invalid coordinate')
```
2 changes: 1 addition & 1 deletion exercises/practice/go-counting/.meta/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def walk(self, x, y,

def territory(self, x, y):
if not self.valid(x, y):
raise ValueError('invalid coordinate')
raise ValueError('Invalid coordinate')
if self.board[y][x] in STONES:
return (NONE, set())

Expand Down
6 changes: 5 additions & 1 deletion exercises/practice/go-counting/.meta/template.j2
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@


{% macro test_case(case) -%}
{%- set expected = case["expected"] %}
{%- set exp_error = expected["error"] %}
def test_{{ case["description"] | to_snake }}(self):
board = Board({{ case["input"]["board"] }})
{%- if case is error_case %}
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
board.territory(x={{ case["input"]["x"] }}, y={{ case["input"]["y"] }})
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(err.exception.args[0], "{{ exp_error }}")
{%- else %}
{%- if "owner" in case["expected"] %}
stone, territory = board.territory(x={{ case["input"]["x"] }}, y={{ case["input"]["y"] }})
Expand Down
16 changes: 12 additions & 4 deletions exercises/practice/go-counting/go_counting_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,23 +37,31 @@ def test_a_stone_and_not_a_territory_on_5x5_board(self):

def test_invalid_because_x_is_too_low_for_5x5_board(self):
board = Board([" B ", " B B ", "B W B", " W W ", " W "])
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
board.territory(x=-1, y=1)
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(err.exception.args[0], "Invalid coordinate")

def test_invalid_because_x_is_too_high_for_5x5_board(self):
board = Board([" B ", " B B ", "B W B", " W W ", " W "])
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
board.territory(x=5, y=1)
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(err.exception.args[0], "Invalid coordinate")

def test_invalid_because_y_is_too_low_for_5x5_board(self):
board = Board([" B ", " B B ", "B W B", " W W ", " W "])
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
board.territory(x=1, y=-1)
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(err.exception.args[0], "Invalid coordinate")

def test_invalid_because_y_is_too_high_for_5x5_board(self):
board = Board([" B ", " B B ", "B W B", " W W ", " W "])
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
board.territory(x=1, y=5)
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(err.exception.args[0], "Invalid coordinate")

def test_one_territory_is_the_whole_board(self):
board = Board([" "])
Expand Down
Loading