88from runner .koan import *
99
1010class 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