|
1 | 1 | # Dunder methods with `__getattribute__` |
2 | 2 |
|
| 3 | + |
3 | 4 | ```python |
4 | 5 | OPS = { |
5 | 6 | "plus": "__add__", |
@@ -33,70 +34,61 @@ def answer(question): |
33 | 34 |
|
34 | 35 | ``` |
35 | 36 |
|
36 | | -This approach begins by defining a [dictionary][dictionaries] of the word keys with their related [dunder][dunder] methods. |
| 37 | +This approach begins by defining a [dictionary][dictionaries] of the word keys with their related [`dunder-methods`][dunder] methods. |
| 38 | +Since only whole numbers are involved, the available `dunder-methods` are those for the [`int`][int] class/namespace. |
| 39 | +The supported methods for the `int()` namespace can be found by using `print(dir(int))` or `print(int.__dict__)` in a Python terminal. |
| 40 | +See [SO: Difference between dir() and __dict__][dir-vs-__dict__] for differences between the two. |
37 | 41 |
|
38 | 42 | ~~~~exercism/note |
39 | | -They are called "dunder" methods because they have **d**ouble **under**scores at the beginning and end of the method name. |
40 | | -They are also called magic methods. |
| 43 | +The built-in [`dir`](https://docs.python.org/3/library/functions.html?#dir) function returns a list of all valid attributes for an object. |
| 44 | +The `dunder-method` [`<object>.__dict__`](https://docs.python.org/3/reference/datamodel.html#object.__dict__) is a mapping of an objects writable attributes. |
41 | 45 | ~~~~ |
42 | 46 |
|
43 | | -Since only whole numbers are involved, the dunder methods are those for [`int`][int]. |
44 | | -The supported methods for `int` can be found by using `print(dir(int))`. |
45 | | - |
46 | | -~~~~exercism/note |
47 | | -The built-in [`dir`](https://docs.python.org/3/library/functions.html?#dir) function returns a list of valid attributes for an object. |
48 | | -~~~~ |
| 47 | +The `OPS` dictionary is defined with all uppercase letters, which is the naming convention for a Python [constant][const]. |
| 48 | +It indicates that the value should not be changed. |
49 | 49 |
|
50 | | -Python doesn't _enforce_ having real constant values, |
51 | | -but the `OPS` dictionary is defined with all uppercase letters, which is the naming convention for a Python [constant][const]. |
52 | | -It indicates that the value is not intended to be changed. |
53 | | - |
54 | | -The input question to the `answer` function is cleaned using the [`removeprefix`][removeprefix], [`removesuffix`][removesuffix], and [`strip`][strip] methods. |
| 50 | +The input question to the `answer()` function is cleaned using the [`removeprefix`][removeprefix], [`removesuffix`][removesuffix], and [`strip`][strip] string methods. |
55 | 51 | The method calls are [chained][method-chaining], so that the output from one call is the input for the next call. |
56 | 52 | If the input has no characters left, |
57 | | -it uses the [falsiness][falsiness] of an empty string with the [`not`][not] operator to return the [`ValueError`][value-error] for having a syntax error. |
| 53 | +it uses the [falsiness][falsiness] of an empty string with the [`not`][not] operator to return a `ValueError("syntax error")`. |
58 | 54 |
|
59 | | -Next, the [`isdigit`][isdigit] method is used to see if all of the remaining characters in the input are digits. |
| 55 | +Next, the [`isdigit`][isdigit] method is used to see if the remaining characters in the input are digits. |
60 | 56 | If so, it uses the [`int()`][int-constructor] constructor to return the string as an integer. |
61 | 57 |
|
62 | | -Next, the elements in the `OPS` dictionary are iterated. |
63 | | -If the key name is in the input, then the [`replace()`][replace] method is used to replace the name in the input with the dunder method value. |
64 | | -If none of the key names are found in the input, then a `ValueError` is returned for having an unknown operation. |
65 | | - |
66 | | -At this point the input question is [`split()`][split] into a list of its words, which is then iterated while its [`len()`][len] is greater than 1. |
| 58 | +Next, the elements in the `OPS` dictionary are iterated over. |
| 59 | +If the key name is in the input, then the [`str.replace`][replace] method is used to replace the name in the input with the `dunder-method` value. |
| 60 | +If none of the key names are found in the input, a `ValueError("unknown operation")` is returned. |
67 | 61 |
|
68 | | -Within a [try][exception-handling], the list is [destructured][destructure] into `x, op, y, *tail`. |
69 | | -If `op` is not in the supported dunder methods, it raises `ValueError("syntax error")`. |
70 | | -If there are any other exceptions raised in the try, `except` raises `ValueError("syntax error")` |
| 62 | +At this point the input question is [`split()`][split] into a `list` of its words, which is then iterated over while its [`len()`][len] is greater than 1. |
71 | 63 |
|
72 | | -Next, it converts `x` to an `int` and calls the [`__getattribute__`][getattribute] for its dunder method and calls it, |
73 | | -passing it `y` converted to an `int`. |
| 64 | +Within a [try-except][exception-handling] block, the list is [unpacked][unpacking] (_see also [Concept: unpacking][unpacking-and-multiple-assignment]_) into the variables `x, op, y, and *tail`. |
| 65 | +If `op` is not in the supported `dunder-methods` dictionary, a `ValueError("syntax error")` is raised. |
| 66 | +If there are any other exceptions raised within the `try` block, they are "caught"/ handled in the `except` clause by raising a `ValueError("syntax error")`. |
74 | 67 |
|
75 | | -It sets the list to the result of the dunder method plus the remaining elements in `*tail`. |
| 68 | +Next, `x` is converted to an `int` and [`__getattribute__`][getattribute] is called for the `dunder-method` (`op`) to apply to `x`. |
| 69 | +`y` is then converted to an `int` and passed as the second arguemnt to `op`. |
76 | 70 |
|
77 | | -~~~~exercism/note |
78 | | -The `*` prefix in `*tail` [unpacks](https://treyhunner.com/2018/10/asterisks-in-python-what-they-are-and-how-to-use-them/) the `tail` list back into its elements. |
79 | | -This concept is also a part of [unpacking-and-multiple-assignment](https://exercism.org/tracks/python/concepts/unpacking-and-multiple-assignment) concept in the syllabus. |
80 | | -~~~~ |
| 71 | +Then `ret` is redefined to a `list` containing the result of the dunder method plus the remaining elements in `*tail`. |
81 | 72 |
|
82 | 73 | When the loop exhausts, the first element of the list is selected as the function return value. |
83 | 74 |
|
| 75 | +[const]: https://realpython.com/python-constants/ |
84 | 76 | [dictionaries]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries |
| 77 | +[dir-vs-__dict__]: https://stackoverflow.com/a/14361362 |
85 | 78 | [dunder]: https://www.tutorialsteacher.com/python/magic-methods-in-python |
| 79 | +[exception-handling]: https://docs.python.org/3/tutorial/errors.html#handling-exceptions |
| 80 | +[falsiness]: https://www.pythontutorial.net/python-basics/python-boolean/ |
| 81 | +[getattribute]: https://docs.python.org/3/reference/datamodel.html?#object.__getattribute__ |
| 82 | +[int-constructor]: https://docs.python.org/3/library/functions.html?#int |
86 | 83 | [int]: https://docs.python.org/3/library/stdtypes.html#typesnumeric |
87 | | -[const]: https://realpython.com/python-constants/ |
88 | | -[removeprefix]: https://docs.python.org/3/library/stdtypes.html#str.removeprefix |
89 | | -[removesuffix]: https://docs.python.org/3/library/stdtypes.html#str.removesuffix |
90 | | -[strip]: https://docs.python.org/3/library/stdtypes.html#str.strip |
| 84 | +[isdigit]: https://docs.python.org/3/library/stdtypes.html?#str.isdigit |
| 85 | +[len]: https://docs.python.org/3/library/functions.html?#len |
91 | 86 | [method-chaining]: https://www.tutorialspoint.com/Explain-Python-class-method-chaining |
92 | 87 | [not]: https://docs.python.org/3/library/operator.html?#operator.__not__ |
93 | | -[falsiness]: https://www.pythontutorial.net/python-basics/python-boolean/ |
94 | | -[value-error]: https://docs.python.org/3/library/exceptions.html?#ValueError |
95 | | -[isdigit]: https://docs.python.org/3/library/stdtypes.html?#str.isdigit |
96 | | -[int-constructor]: https://docs.python.org/3/library/functions.html?#int |
| 88 | +[removeprefix]: https://docs.python.org/3/library/stdtypes.html#str.removeprefix |
| 89 | +[removesuffix]: https://docs.python.org/3/library/stdtypes.html#str.removesuffix |
97 | 90 | [replace]: https://docs.python.org/3/library/stdtypes.html?#str.replace |
98 | 91 | [split]: https://docs.python.org/3/library/stdtypes.html?#str.split |
99 | | -[len]: https://docs.python.org/3/library/functions.html?#len |
100 | | -[exception-handling]: https://docs.python.org/3/tutorial/errors.html#handling-exceptions |
101 | | -[destructure]: https://riptutorial.com/python/example/14981/destructuring-assignment |
102 | | -[getattribute]: https://docs.python.org/3/reference/datamodel.html?#object.__getattribute__ |
| 92 | +[strip]: https://docs.python.org/3/library/stdtypes.html#str.strip |
| 93 | +[unpacking]: https://treyhunner.com/2018/10/asterisks-in-python-what-they-are-and-how-to-use-them/ |
| 94 | +[unpacking-and-multiple-assignment]: https://exercism.org/tracks/python/concepts/unpacking-and-multiple-assignment |
0 commit comments