1
+ #!/usr/bin/env python
2
+ # -*- encoding: utf-8 -*-
3
+ """
4
+ Topic: 类型检查系统
5
+ Desc :
6
+ """
7
+
8
+
9
+ # Base class. Uses a descriptor to set a value
10
+ class Descriptor :
11
+ def __init__ (self , name = None , ** opts ):
12
+ self .name = name
13
+ for key , value in opts .items ():
14
+ setattr (self , key , value )
15
+
16
+ def __set__ (self , instance , value ):
17
+ instance .__dict__ [self .name ] = value
18
+
19
+
20
+ # Descriptor for enforcing types
21
+ class Typed (Descriptor ):
22
+ expected_type = type (None )
23
+
24
+ def __set__ (self , instance , value ):
25
+ if not isinstance (value , self .expected_type ):
26
+ raise TypeError ('expected ' + str (self .expected_type ))
27
+ super ().__set__ (instance , value )
28
+
29
+
30
+ # Descriptor for enforcing values
31
+ class Unsigned (Descriptor ):
32
+ def __set__ (self , instance , value ):
33
+ if value < 0 :
34
+ raise ValueError ('Expected >= 0' )
35
+ super ().__set__ (instance , value )
36
+
37
+
38
+ class MaxSized (Descriptor ):
39
+ def __init__ (self , name = None , ** opts ):
40
+ if 'size' not in opts :
41
+ raise TypeError ('missing size option' )
42
+ super ().__init__ (name , ** opts )
43
+
44
+ def __set__ (self , instance , value ):
45
+ if len (value ) >= self .size :
46
+ raise ValueError ('size must be < ' + str (self .size ))
47
+ super ().__set__ (instance , value )
48
+
49
+
50
+ if __name__ == '__main__' :
51
+ class Integer (Typed ):
52
+ expected_type = int
53
+
54
+ class UnsignedInteger (Integer , Unsigned ):
55
+ pass
56
+
57
+ class Float (Typed ):
58
+ expected_type = float
59
+
60
+ class UnsignedFloat (Float , Unsigned ):
61
+ pass
62
+
63
+ class String (Typed ):
64
+ expected_type = str
65
+
66
+ class SizedString (String , MaxSized ):
67
+ pass
68
+
69
+ class Stock :
70
+ # Specify constraints
71
+ name = SizedString ('name' , size = 8 )
72
+ shares = UnsignedInteger ('shares' )
73
+ price = UnsignedFloat ('price' )
74
+
75
+ def __init__ (self , name , shares , price ):
76
+ self .name = name
77
+ self .shares = shares
78
+ self .price = price
79
+
80
+ s = Stock ('ACME' , 50 , 91.1 )
81
+
82
+ # Class decorator to apply constraints
83
+ def check_attributes (** kwargs ):
84
+ def decorate (cls ):
85
+ for key , value in kwargs .items ():
86
+ if isinstance (value , Descriptor ):
87
+ value .name = key
88
+ setattr (cls , key , value )
89
+ else :
90
+ setattr (cls , key , value (key ))
91
+ return cls
92
+
93
+ return decorate
94
+
95
+ # Example
96
+ @check_attributes (name = SizedString (size = 8 ),
97
+ shares = UnsignedInteger ,
98
+ price = UnsignedFloat )
99
+ class Stock :
100
+ def __init__ (self , name , shares , price ):
101
+ self .name = name
102
+ self .shares = shares
103
+ self .price = price
104
+
105
+
106
+
107
+ # A metaclass that applies checking
108
+ class checkedmeta (type ):
109
+ def __new__ (cls , clsname , bases , methods ):
110
+ # Attach attribute names to the descriptors
111
+ for key , value in methods .items ():
112
+ if isinstance (value , Descriptor ):
113
+ value .name = key
114
+ return type .__new__ (cls , clsname , bases , methods )
115
+
116
+
117
+ # Example
118
+ class Stock2 (metaclass = checkedmeta ):
119
+ name = SizedString (size = 8 )
120
+ shares = UnsignedInteger ()
121
+ price = UnsignedFloat ()
122
+
123
+ def __init__ (self , name , shares , price ):
124
+ self .name = name
125
+ self .shares = shares
126
+ self .price = price
127
+
128
+
129
+ # Base class. Uses a descriptor to set a value
130
+ class Descriptor :
131
+ def __init__ (self , name = None , ** opts ):
132
+ self .name = name
133
+ for key , value in opts .items ():
134
+ setattr (self , key , value )
135
+
136
+ def __set__ (self , instance , value ):
137
+ instance .__dict__ [self .name ] = value
138
+
139
+
140
+ # Decorator for applying type checking
141
+ def Typed (expected_type , cls = None ):
142
+ if cls is None :
143
+ return lambda cls : Typed (expected_type , cls )
144
+ super_set = cls .__set__
145
+
146
+ def __set__ (self , instance , value ):
147
+ if not isinstance (value , expected_type ):
148
+ raise TypeError ('expected ' + str (expected_type ))
149
+ super_set (self , instance , value )
150
+
151
+ cls .__set__ = __set__
152
+ return cls
153
+
154
+
155
+ # Decorator for unsigned values
156
+ def Unsigned (cls ):
157
+ super_set = cls .__set__
158
+
159
+ def __set__ (self , instance , value ):
160
+ if value < 0 :
161
+ raise ValueError ('Expected >= 0' )
162
+ super_set (self , instance , value )
163
+
164
+ cls .__set__ = __set__
165
+ return cls
166
+
167
+
168
+ # Decorator for allowing sized values
169
+ def MaxSized (cls ):
170
+ super_init = cls .__init__
171
+
172
+ def __init__ (self , name = None , ** opts ):
173
+ if 'size' not in opts :
174
+ raise TypeError ('missing size option' )
175
+ super_init (self , name , ** opts )
176
+
177
+ cls .__init__ = __init__
178
+
179
+ super_set = cls .__set__
180
+
181
+ def __set__ (self , instance , value ):
182
+ if len (value ) >= self .size :
183
+ raise ValueError ('size must be < ' + str (self .size ))
184
+ super_set (self , instance , value )
185
+
186
+ cls .__set__ = __set__
187
+ return cls
188
+
189
+
190
+ # Specialized descriptors
191
+ @Typed (int )
192
+ class Integer (Descriptor ):
193
+ pass
194
+
195
+
196
+ @Unsigned
197
+ class UnsignedInteger (Integer ):
198
+ pass
199
+
200
+
201
+ @Typed (float )
202
+ class Float (Descriptor ):
203
+ pass
204
+
205
+
206
+ @Unsigned
207
+ class UnsignedFloat (Float ):
208
+ pass
209
+
210
+
211
+ @Typed (str )
212
+ class String (Descriptor ):
213
+ pass
214
+
215
+
216
+ @MaxSized
217
+ class SizedString (String ):
218
+ pass
0 commit comments