Talks: dynclasses.py

File dynclasses.py, 8.1 kB (added by campug, 1 year ago)

Description of dynamic class creation and Python's object model

Line 
1import types
2
3# One of the nicest things about Python is that you can put pieces together.
4# You only have to learn each piece once.
5
63                # an integer
73, 4, 5          # a tuple of integers, also written (3, 4, 5)
8x = 3, 4, 5      # assignment
9
10def f():         # a function
11    x = 3, 4, 5
12    return x
13
14class Foo(object):   # a class
15    def f(self):     # ok, we had to add self
16        x = 3, 4, 5
17        return x
18
19foo = Foo()
20assert foo.f() == (3, 4, 5)  # ok, sometimes you need the ()s
21
22
23# Another nice thing is that you can just type things in, line-by-line.
24
25# So the question is, how do I do what I just did line-by-line (dynamically)?
26
27
28class Foo(object):  # a class
29    pass  # ok, we need this so we can unindent.  Any value would work too.
30
31def f(self):        # same again, but it's not part of Foo yet
32    x = 3, 4, 5
33    return x
34
35Foo.f = f     # is it really that easy?
36
37foo = Foo()
38assert foo.f() == (3, 4, 5)  # yes!
39
40
41# But what about this?
42
43Foo.g = f
44
45assert foo.g() == (3, 4, 5)  # yes!  And I didn't even change foo!
46
47
48# There's no difference between bound and unbound method types:
49
50assert type(f) is types.FunctionType
51assert type(Foo.f) is types.MethodType  # (how did that happen?)
52assert type(foo.f) is types.MethodType
53assert type(Foo.g) is types.MethodType
54assert type(foo.g) is types.MethodType
55
56
57# Ok, but what about sticking a function on a particular instance?
58
59foo.h = f
60
61try:
62    foo.h()
63except TypeError, e:
64    assert str(e) == 'f() takes exactly 1 argument (0 given)'
65    # oops, it has the wrong name too!
66
67
68# So what went wrong?
69
70assert type(foo.f) == types.MethodType
71assert type(foo.h) == types.FunctionType
72
73# Or in terms of what you'd see if you entered "type(foo.f)" at the console:
74assert repr(type(foo.f)) == "<type 'instancemethod'>"  # types.MethodType
75assert repr(type(foo.h)) == "<type 'function'>"        # types.FunctionType
76
77# foo.f is a method, but foo.h is still a function.
78# Only methods get magically bound to foo when accessed as foo.name.
79
80# Except actually, foo.f isn't really a method.
81assert repr(type(Foo.__dict__['f'])) == "<type 'function'>"  # FunctionType
82
83# "a class attribute reference [which] would yield a user-defined function
84#  ... is transformed into an unbound user-defined method object whose
85# im_class attribute is [the class]."
86#    -- http://docs.python.org/reference/datamodel.html
87
88# i.e. f is still sitting there as a function in Foo.__dict__,
89# but it gets converted to a method when accessed as Foo.f (unbound)
90# or foo.f (bound to foo).
91
92# So how do we turn foo.h into a method?  With any of these:
93
94foo.h = types.MethodType(f, foo)       # Bind f to foo and assign to foo.h
95foo.h = types.MethodType(f, foo, Foo)  # Require a Foo instance for "self"
96
97foo.h = f.__get__(foo)                 # Bind f to foo and assign to foo.h
98foo.h = f.__get__(foo, Foo)            # Require a Foo instance for "self"
99
100
101# Is that all it takes?
102
103assert repr(type(foo.h)) == "<type 'instancemethod'>"  # types.MethodType
104assert foo.h() == (3, 4, 5)  # yes!
105
106assert foo.h.__name__ == 'f'
107
108# It still has the wrong name though... and you can't change a method's name.
109
110
111# Ok, how do I get the function back?
112
113h = foo.h.im_func            # im is short for instance method I presume
114
115assert h(foo) == (3, 4, 5)
116
117h.__name__ = 'h'  # Better.  Let's put it back to being a method...
118
119foo.h = h.__get__(foo)
120assert foo.h.__name__ == 'h'
121assert foo.f.__name__ == 'h'  # Oops.  (Exercise for the reader.)
122
123
124# Does ".im_func" work on methods that didn't start off as functions?
125
126class Foo(object):   # a class
127    def f(self):     # ok, we had to add self
128        x = 3, 4, 5
129        return x
130
131f = foo.f.im_func
132
133assert f(foo) == (3, 4, 5)  # yes!  Except...
134
135
136# ... that was a trick question.
137
138# Let's watch the original definition of Foo again.
139
140class Foo(object):
141    def f(self):
142        x = 3, 4, 5
143        return x
144    # Stop!
145    # What is f *right here*?  A function or a method?  Does it exist at all?
146
147
148
149
150    # Of course it does.  It's a function.
151    assert type(f) == types.FunctionType
152
153
154
155# But *right here* ...
156assert type(Foo.f) == types.MethodType  # ... it's a method.  Huh?
157
158
159
160# All methods start off as functions.
161# (Except the ones implemented in C, such as built-ins and extensions.)
162
163
164# Ok, this is what actually happens as we define class Foo.
165
166# class Foo(object):
167#     ...
168
169# "class Foo(object):" is delayed until its body has been processed...
170
171body = '''
172
173def f(self):
174    x = 3, 4, 5
175    return x
176
177'''
178
179tempdict = {}
180exec body in tempdict
181
182# Minor detail: classes have a __module__ attribute.
183# sys.modules[SomeClass.__module__] is the module SomeClass was defined in.
184
185tempdict['__module__'] = __name__
186
187
188# At the end of the class body, create the class itself:
189
190Foo = type('Foo', (object,), tempdict)  # "class Foo(object):" <= tempdict
191foo = Foo()
192assert foo.f() == (3, 4, 5)
193
194# The "type()" built-in doesn't just query the class of objects,
195# it can make new classes!
196
197
198
199# Ok, here's a challenge.  Is it possible to define a VanishingClass
200# so that:
201#     x = VanishingClass()
202#     x is None
203# ?
204
205
206
207class VanishingClass(object):
208    def __new__(newclass, *args, **kwargs):
209        return None
210
211x = VanishingClass()
212assert x is None  # yes!
213
214# "MyClass(1, 2, x=3)" means "MyClass.__new__(MyClass, 1, 2, x=3)".
215# It's object.__new__ that calls __init__ when you create a class (I think).
216
217
218
219# Ok, another challenge.  Is it possible to define a SingletonClass
220# so that "SingletonClass() is SingletonClass()"?
221
222
223
224class SingletonClass(object):
225    def __new__(newclass, *args, **kwargs):
226        try:
227            return SingletonClass.instance
228        except AttributeError:
229            singleton = object.__new__(newclass, *args, **kwargs)
230            SingletonClass.instance = singleton
231            return singleton
232
233
234assert SingletonClass() is SingletonClass()  # yes!
235
236
237
238# And if we're being pedantic...
239assert VanishingClass() is VanishingClass()  # VanishingClass is a singleton!
240# ... or a zerogleton.
241
242
243# Ok, but is any of this *useful*?
244
245# Well, I've used SingletonClass for a DatabaseObject class, where
246# DatabaseObject('Fred') would create or retrieve Fred's record.
247
248# I've even used VanishingClass in a type-checking hack:
249
250# /*
251#  * You are not expected to understand this.
252#  */
253class Integer(object):  # a variant on VanishingClass
254    def __new__(newclass, *args, **kwargs):
255        if args and args[0] is newclass:
256            return None  # Integer(Integer) => vanish!
257        # Just create it normally.  (I may have this wrong...)
258        self = newclass.__bases__[0].__new__(
259                    newclass.__bases__[0], *args, **kwargs)
260        self.__class__ = newclass
261        return self
262
263
264def public_api_function(arg=Integer):
265    return implementation(arg=Integer(arg))
266
267def implementation(arg=None):
268    pass  # ...
269
270
271
272
273# The main reason all this is worth describing is to explain Python classes,
274# but also because it lets you create classes completely on-the-fly, or
275# really friendly-looking declarative APIs:
276
277'''
278
279class MyForm(object):
280    name = String()
281    age = Integer()
282    ...
283
284'''
285# If you need an order on fields, you're in trouble - remember tempdict?
286# There's a clever hack though: give the field constructors a global counter.
287
288
289# A final example:
290
291class Word(object):  # a basic class that prints out as "Word()"
292    def __repr__(self):
293        return '%s()' % self.__class__.__name__
294
295input = '''No-one expects the Spanish Inquisition!'''
296
297words = []
298for word in input.split():
299    newclass = type(word, (Word,), {})  # create a new class
300    words.append(newclass())            # create an instance
301    globals()[word] = newclass          # publish the class under its name
302
303assert repr(words)=='[No-one(), expects(), the(), Spanish(), Inquisition!()]'
304# Yes, those are some very strange class names!
305spanish = Spanish()
306assert repr(spanish)=='Spanish()'
307inquisition = globals()['Inquisition!']()
308assert repr(inquisition)=='Inquisition!()'