Skip to content

Commit c98c999

Browse files
committed
Added some extra clarifaction on what to do on the test_setattr_intercepts_attribute_assignments koans
1 parent 3516037 commit c98c999

File tree

2 files changed

+56
-48
lines changed

2 files changed

+56
-48
lines changed

python2/koans/about_attribute_access.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,10 @@ def test_setattr_intercepts_attribute_assignments(self):
185185

186186
self.assertEqual(__, fanboy.a_pie)
187187

188+
#
189+
# NOTE: Change the prefix to make this next assert pass
190+
#
191+
188192
prefix = '__'
189193
self.assertEqual(
190194
"The Laminator, issue #1",

python3/koans/about_attribute_access.py

Lines changed: 52 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -8,113 +8,113 @@
88
from runner.koan import *
99

1010
class AboutAttributeAccess(Koan):
11-
11+
1212
class TypicalObject:
1313
pass
14-
14+
1515
def test_calling_undefined_functions_normally_results_in_errors(self):
1616
typical = self.TypicalObject()
17-
17+
1818
with self.assertRaises(___): typical.foobar()
19-
19+
2020
def test_calling_getattribute_causes_an_attribute_error(self):
2121
typical = self.TypicalObject()
2222

2323
with self.assertRaises(___): typical.__getattribute__('foobar')
24-
24+
2525
# THINK ABOUT IT:
2626
#
2727
# If the method __getattribute__() causes the AttributeError, then
2828
# what would happen if we redefine __getattribute__()?
29-
29+
3030
# ------------------------------------------------------------------
31-
31+
3232
class CatchAllAttributeReads:
3333
def __getattribute__(self, attr_name):
3434
return "Someone called '" + attr_name + "' and it could not be found"
35-
35+
3636
def test_all_attribute_reads_are_caught(self):
3737
catcher = self.CatchAllAttributeReads()
38-
38+
3939
self.assertRegexpMatches(catcher.foobar, __)
4040

4141
def test_intercepting_return_values_can_disrupt_the_call_chain(self):
4242
catcher = self.CatchAllAttributeReads()
4343

4444
self.assertRegexpMatches(catcher.foobaz, __) # This is fine
45-
45+
4646
try:
4747
catcher.foobaz(1)
4848
except TypeError as ex:
4949
err_msg = ex.args[0]
50-
50+
5151
self.assertRegexpMatches(err_msg, __)
52-
52+
5353
# foobaz returns a string. What happens to the '(1)' part?
5454
# Try entering this into a python console to reproduce the issue:
5555
#
5656
# "foobaz"(1)
5757
#
58-
58+
5959
def test_changes_to_the_getattribute_implementation_affects_getattr_function(self):
6060
catcher = self.CatchAllAttributeReads()
61-
61+
6262
self.assertRegexpMatches(getattr(catcher, 'any_attribute'), __)
63-
63+
6464
# ------------------------------------------------------------------
65-
65+
6666
class WellBehavedFooCatcher:
6767
def __getattribute__(self, attr_name):
6868
if attr_name[:3] == "foo":
6969
return "Foo to you too"
7070
else:
7171
return super().__getattribute__(attr_name)
72-
72+
7373
def test_foo_attributes_are_caught(self):
7474
catcher = self.WellBehavedFooCatcher()
75-
75+
7676
self.assertEqual(__, catcher.foo_bar)
7777
self.assertEqual(__, catcher.foo_baz)
78-
78+
7979
def test_non_foo_messages_are_treated_normally(self):
8080
catcher = self.WellBehavedFooCatcher()
81-
81+
8282
with self.assertRaises(___): catcher.normal_undefined_attribute
8383

8484
# ------------------------------------------------------------------
85-
85+
8686
global stack_depth
87-
stack_depth = 0
87+
stack_depth = 0
8888

8989
class RecursiveCatcher:
9090
def __init__(self):
9191
global stack_depth
92-
stack_depth = 0
92+
stack_depth = 0
9393
self.no_of_getattribute_calls = 0
94-
95-
def __getattribute__(self, attr_name):
94+
95+
def __getattribute__(self, attr_name):
9696
global stack_depth # We need something that is outside the scope of this class
9797
stack_depth += 1
9898

9999
if stack_depth<=10: # to prevent a stack overflow
100100
self.no_of_getattribute_calls += 1
101101
# Oops! We just accessed an attribute (no_of_getattribute_calls)
102102
# Guess what happens when self.no_of_getattribute_calls is
103-
# accessed?
103+
# accessed?
104104

105105
# Using 'object' directly because using super() here will also
106106
# trigger a __getattribute__() call.
107-
return object.__getattribute__(self, attr_name)
108-
107+
return object.__getattribute__(self, attr_name)
108+
109109
def my_method(self):
110110
pass
111-
111+
112112
def test_getattribute_is_a_bit_overzealous_sometimes(self):
113113
catcher = self.RecursiveCatcher()
114114
catcher.my_method()
115115
global stack_depth
116116
self.assertEqual(__, stack_depth)
117-
117+
118118
# ------------------------------------------------------------------
119119

120120
class MinimalCatcher:
@@ -126,7 +126,7 @@ def __init__(self):
126126
def __getattr__(self, attr_name):
127127
self.no_of_getattr_calls += 1
128128
return self.DuffObject
129-
129+
130130
def my_method(self):
131131
pass
132132

@@ -135,62 +135,66 @@ def test_getattr_ignores_known_attributes(self):
135135
catcher.my_method()
136136

137137
self.assertEqual(__, catcher.no_of_getattr_calls)
138-
138+
139139
def test_getattr_only_catches_unknown_attributes(self):
140140
catcher = self.MinimalCatcher()
141141
catcher.purple_flamingos()
142142
catcher.free_pie()
143-
143+
144144
self.assertEqual(__,
145145
type(catcher.give_me_duff_or_give_me_death()).__name__)
146-
146+
147147
self.assertEqual(__, catcher.no_of_getattr_calls)
148-
148+
149149
# ------------------------------------------------------------------
150150

151151
class PossessiveSetter(object):
152152
def __setattr__(self, attr_name, value):
153153
new_attr_name = attr_name
154-
154+
155155
if attr_name[-5:] == 'comic':
156156
new_attr_name = "my_" + new_attr_name
157157
elif attr_name[-3:] == 'pie':
158-
new_attr_name = "a_" + new_attr_name
158+
new_attr_name = "a_" + new_attr_name
159159

160-
object.__setattr__(self, new_attr_name, value)
160+
object.__setattr__(self, new_attr_name, value)
161161

162162
def test_setattr_intercepts_attribute_assignments(self):
163163
fanboy = self.PossessiveSetter()
164-
164+
165165
fanboy.comic = 'The Laminator, issue #1'
166166
fanboy.pie = 'blueberry'
167-
168-
self.assertEqual(__, fanboy.a_pie)
167+
168+
self.assertEqual(__, fanboy.a_pie)
169+
170+
#
171+
# NOTE: Change the prefix to make this next assert pass
172+
#
169173

170174
prefix = '__'
171175
self.assertEqual("The Laminator, issue #1", getattr(fanboy, prefix + '_comic'))
172176

173177
# ------------------------------------------------------------------
174178

175-
class ScarySetter:
179+
class ScarySetter:
176180
def __init__(self):
177181
self.num_of_coconuts = 9
178182
self._num_of_private_coconuts = 2
179-
183+
180184
def __setattr__(self, attr_name, value):
181185
new_attr_name = attr_name
182-
186+
183187
if attr_name[0] != '_':
184188
new_attr_name = "altered_" + new_attr_name
185-
189+
186190
object.__setattr__(self, new_attr_name, value)
187-
191+
188192
def test_it_modifies_external_attribute_as_expected(self):
189193
setter = self.ScarySetter()
190194
setter.e = "mc hammer"
191-
195+
192196
self.assertEqual(__, setter.altered_e)
193-
197+
194198
def test_it_mangles_some_internal_attributes(self):
195199
setter = self.ScarySetter()
196200

0 commit comments

Comments
 (0)