8
8
from runner .koan import *
9
9
10
10
class AboutAttributeAccess (Koan ):
11
-
11
+
12
12
class TypicalObject :
13
13
pass
14
-
14
+
15
15
def test_calling_undefined_functions_normally_results_in_errors (self ):
16
16
typical = self .TypicalObject ()
17
-
17
+
18
18
with self .assertRaises (___ ): typical .foobar ()
19
-
19
+
20
20
def test_calling_getattribute_causes_an_attribute_error (self ):
21
21
typical = self .TypicalObject ()
22
22
23
23
with self .assertRaises (___ ): typical .__getattribute__ ('foobar' )
24
-
24
+
25
25
# THINK ABOUT IT:
26
26
#
27
27
# If the method __getattribute__() causes the AttributeError, then
28
28
# what would happen if we redefine __getattribute__()?
29
-
29
+
30
30
# ------------------------------------------------------------------
31
-
31
+
32
32
class CatchAllAttributeReads :
33
33
def __getattribute__ (self , attr_name ):
34
34
return "Someone called '" + attr_name + "' and it could not be found"
35
-
35
+
36
36
def test_all_attribute_reads_are_caught (self ):
37
37
catcher = self .CatchAllAttributeReads ()
38
-
38
+
39
39
self .assertRegexpMatches (catcher .foobar , __ )
40
40
41
41
def test_intercepting_return_values_can_disrupt_the_call_chain (self ):
42
42
catcher = self .CatchAllAttributeReads ()
43
43
44
44
self .assertRegexpMatches (catcher .foobaz , __ ) # This is fine
45
-
45
+
46
46
try :
47
47
catcher .foobaz (1 )
48
48
except TypeError as ex :
49
49
err_msg = ex .args [0 ]
50
-
50
+
51
51
self .assertRegexpMatches (err_msg , __ )
52
-
52
+
53
53
# foobaz returns a string. What happens to the '(1)' part?
54
54
# Try entering this into a python console to reproduce the issue:
55
55
#
56
56
# "foobaz"(1)
57
57
#
58
-
58
+
59
59
def test_changes_to_the_getattribute_implementation_affects_getattr_function (self ):
60
60
catcher = self .CatchAllAttributeReads ()
61
-
61
+
62
62
self .assertRegexpMatches (getattr (catcher , 'any_attribute' ), __ )
63
-
63
+
64
64
# ------------------------------------------------------------------
65
-
65
+
66
66
class WellBehavedFooCatcher :
67
67
def __getattribute__ (self , attr_name ):
68
68
if attr_name [:3 ] == "foo" :
69
69
return "Foo to you too"
70
70
else :
71
71
return super ().__getattribute__ (attr_name )
72
-
72
+
73
73
def test_foo_attributes_are_caught (self ):
74
74
catcher = self .WellBehavedFooCatcher ()
75
-
75
+
76
76
self .assertEqual (__ , catcher .foo_bar )
77
77
self .assertEqual (__ , catcher .foo_baz )
78
-
78
+
79
79
def test_non_foo_messages_are_treated_normally (self ):
80
80
catcher = self .WellBehavedFooCatcher ()
81
-
81
+
82
82
with self .assertRaises (___ ): catcher .normal_undefined_attribute
83
83
84
84
# ------------------------------------------------------------------
85
-
85
+
86
86
global stack_depth
87
- stack_depth = 0
87
+ stack_depth = 0
88
88
89
89
class RecursiveCatcher :
90
90
def __init__ (self ):
91
91
global stack_depth
92
- stack_depth = 0
92
+ stack_depth = 0
93
93
self .no_of_getattribute_calls = 0
94
-
95
- def __getattribute__ (self , attr_name ):
94
+
95
+ def __getattribute__ (self , attr_name ):
96
96
global stack_depth # We need something that is outside the scope of this class
97
97
stack_depth += 1
98
98
99
99
if stack_depth <= 10 : # to prevent a stack overflow
100
100
self .no_of_getattribute_calls += 1
101
101
# Oops! We just accessed an attribute (no_of_getattribute_calls)
102
102
# Guess what happens when self.no_of_getattribute_calls is
103
- # accessed?
103
+ # accessed?
104
104
105
105
# Using 'object' directly because using super() here will also
106
106
# trigger a __getattribute__() call.
107
- return object .__getattribute__ (self , attr_name )
108
-
107
+ return object .__getattribute__ (self , attr_name )
108
+
109
109
def my_method (self ):
110
110
pass
111
-
111
+
112
112
def test_getattribute_is_a_bit_overzealous_sometimes (self ):
113
113
catcher = self .RecursiveCatcher ()
114
114
catcher .my_method ()
115
115
global stack_depth
116
116
self .assertEqual (__ , stack_depth )
117
-
117
+
118
118
# ------------------------------------------------------------------
119
119
120
120
class MinimalCatcher :
@@ -126,7 +126,7 @@ def __init__(self):
126
126
def __getattr__ (self , attr_name ):
127
127
self .no_of_getattr_calls += 1
128
128
return self .DuffObject
129
-
129
+
130
130
def my_method (self ):
131
131
pass
132
132
@@ -135,62 +135,66 @@ def test_getattr_ignores_known_attributes(self):
135
135
catcher .my_method ()
136
136
137
137
self .assertEqual (__ , catcher .no_of_getattr_calls )
138
-
138
+
139
139
def test_getattr_only_catches_unknown_attributes (self ):
140
140
catcher = self .MinimalCatcher ()
141
141
catcher .purple_flamingos ()
142
142
catcher .free_pie ()
143
-
143
+
144
144
self .assertEqual (__ ,
145
145
type (catcher .give_me_duff_or_give_me_death ()).__name__ )
146
-
146
+
147
147
self .assertEqual (__ , catcher .no_of_getattr_calls )
148
-
148
+
149
149
# ------------------------------------------------------------------
150
150
151
151
class PossessiveSetter (object ):
152
152
def __setattr__ (self , attr_name , value ):
153
153
new_attr_name = attr_name
154
-
154
+
155
155
if attr_name [- 5 :] == 'comic' :
156
156
new_attr_name = "my_" + new_attr_name
157
157
elif attr_name [- 3 :] == 'pie' :
158
- new_attr_name = "a_" + new_attr_name
158
+ new_attr_name = "a_" + new_attr_name
159
159
160
- object .__setattr__ (self , new_attr_name , value )
160
+ object .__setattr__ (self , new_attr_name , value )
161
161
162
162
def test_setattr_intercepts_attribute_assignments (self ):
163
163
fanboy = self .PossessiveSetter ()
164
-
164
+
165
165
fanboy .comic = 'The Laminator, issue #1'
166
166
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
+ #
169
173
170
174
prefix = '__'
171
175
self .assertEqual ("The Laminator, issue #1" , getattr (fanboy , prefix + '_comic' ))
172
176
173
177
# ------------------------------------------------------------------
174
178
175
- class ScarySetter :
179
+ class ScarySetter :
176
180
def __init__ (self ):
177
181
self .num_of_coconuts = 9
178
182
self ._num_of_private_coconuts = 2
179
-
183
+
180
184
def __setattr__ (self , attr_name , value ):
181
185
new_attr_name = attr_name
182
-
186
+
183
187
if attr_name [0 ] != '_' :
184
188
new_attr_name = "altered_" + new_attr_name
185
-
189
+
186
190
object .__setattr__ (self , new_attr_name , value )
187
-
191
+
188
192
def test_it_modifies_external_attribute_as_expected (self ):
189
193
setter = self .ScarySetter ()
190
194
setter .e = "mc hammer"
191
-
195
+
192
196
self .assertEqual (__ , setter .altered_e )
193
-
197
+
194
198
def test_it_mangles_some_internal_attributes (self ):
195
199
setter = self .ScarySetter ()
196
200
0 commit comments