summaryrefslogtreecommitdiff
path: root/pypers/prog.txt
blob: ef0cf6dbc1d18112989d21dda9e5b92cb9a56f7a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
THE PROGRAMMABLE PROGRAMMING LANGUAGE
=========================================================================

  *I think that lisp is a better applications language than Python.
  However, Python is close enough, or at least so much better than the
  alternatives, that Python's social and glue language advantages are
  often decisive.*  -- Andy Freeman on c.l.p.

I go in *really* DEEP BLACK MAGIC here.

Lisp has been called the *programmable programming language* [#]_
since its macros allow the  programmer to change the *syntax* of
the language. Python has no macros and the syntax of the language
cannot be changed. Nevertheless, Python metaclasses allows
to change the *semantics* of the language. In this sense, they
are even more powerful and more dangerous than Lisp macros.
Python metaclass allow the user to customize the language (if not
its syntax). This is cool enough, however it can make your programs
unreadable by others. The techniques explained in this
chapter should be used with care. Nevertheless, I trust the judgement
of the programmer who has been able to reach this chapter, and I don't
mind providing him further rope to shoot in his/her foot ;)

.. [#] Paul Graham, 'OnLisp'
       citing 

Enhancing the Python language
--------------------------------------------------------------------------

Let me start with some minor usage of metaclasses. In this section I
will show how the user can implement in few lines features that are
built-in in other languages, through a minimal usage of metaclasses. 

For instance, suppose one wants to define a class which cannot be 
derived: in Java this can be done with the "final" keyword. 
In Python there is no need to add a new keyword to the language: 

 ::

  #<oopp.py>

  class NonDerivableError(Exception): pass

  class Final(type): # better derived from WithCounter,type
      "Instances of Final cannot be derived"
      def __new__(meta,name,bases,dic):
          try:
              meta.already_called is True
          except AttributeError: # not already called
              meta.already_called=True
              return super(Final,meta).__new__(meta,name,bases,dic)
          else: #if already called
              raise NonDerivableError("I cannot derive from %s" % bases)

  #</oopp.py>

Here there is an example of usage:

  >>> from oopp import Final
  >>> class C:
  ...    __metaclass__=Final
  ...
  >>> class D(C): pass #error
  ...
  NonDerivableError: D not created from (<class 'oopp.C'>,)

It is interesting to notice that a similar effect can be reached
with a ``singletonClass`` class factory: a 'MetaSingleton' inherits
from ``Singleton`` and from 'type' (therefore it is a metaclass):

 ::

  #<oopp.py>

  class S(Singleton,type): pass
  singletonClass=ClsFactory[S]

  #</oopp.py>

If we write

  >>> from oopp import singletonClass
  >>> C=singletonClass()
  >>> class D(C):
  ...    pass


we see that actually 'D' is not a new instance of 'Singleton', but
it coincides with 'C', instead:

  >>> id(C),id(D)
  (135622140, 135622140)
  >>> C is D
  True
  >>> type(C)
  <class '__main__._Singleton'>
  >>> type(C).__bases__
  (<class 'oopp.Singleton'>, <type 'type'>)
  >>> c=C(); d=D()
  >>> id(c),id(d)
  (1075378028, 1075378924)

Notice the order: 'SingletonClass' must inherit from 'Singleton' 
first and from ``Class`` second, otherwise the ``Class.__new__`` method would
override the  ``Singleton.__new__``, therefore losing the 'Singleton'
basic property of having only one instance. On the other hand, in
the correct order, 'Singleton' first and 'Class' second, the inheritance
diagram is

 ::


                       object   5
                     (__new__)
                    /          \
                   /            \
        2      WithCounter          type    4
              (__new__)       (__new__)
                  |              |
                  |              |
        1     Singleton         Class    3
              (__new__)       (__new__)
                   \             /
                    \           /
                    SingletonClass    0
                 (Singleton.__new__)


 ::

                        object
                       /     \
                      /       |
                  WithCounter     | 
                      |       |
                  Singleton  type
                       \     /
                        \   /
                     MetaSingleton
                          :
                          :       
                          :   instantiation
                          :
                          :
                        C = D


whereas 'SingletonClass' inherits ``Singleton.__new__`` which, trough
the ``super`` mechanism, calls 'type.__new__' and therefore creates
the class 'C'. Notice that class 'D' is never created, it is simply
an alias for 'C'.

I think it is simpler to write down the class 'Final' explicitely
(explicit is better than implicit) as I did; however a fanatic of code
reuse could derive it from 'SingletonClass':

 ::

  #<final.py>

  from oopp import *

  class Final(Singleton,type):
      "Inherits the 'instance' attribute from Singleton (default None)"
      def __new__(meta,name,bases,dic):
          if meta.counter==0: # first call
              return super(Final,meta).__new__(meta,name,bases,dic)
          else:
              raise NonDerivableError("I cannot derive from %s" % bases)
    
  class C:  __metaclass__=Final

  try:
      class D(C): pass
  except NonDerivableError,e:
      print e
  
  #</final.py>

The reader can check that this script has the correct output 
"I cannot derive from <class 'oopp.C'>". I leave to the reader
to understand the issues with trying to implement 'NonDerivable'
from 'NonInstantiable'. #And why an inner metaclass would not work.

Restricting Python dynamism
-----------------------------------------------------------

 ::

  #<oopp.py>

  def frozen(self,name,value):
      if hasattr(self,name):
          type(self).__bases__[0].__setattr__(self,name,value) 
      else:
          raise AttributeError("You cannot add attributes to %s" % self)

  class Frozen(object):
      """Subclasses of Frozen are frozen, i.e. it is impossibile to add
       new attributes to them and their instances"""
      __setattr__ = frozen
      class __metaclass__(type):
          __setattr__ = frozen

  #</oopp.py>


  #<frozen.py>

  from oopp import *

  class C(Frozen):
      c=1
      def __init__(self): 
          #self.x=5 # won't work anymore, __new__ will be okay
          pass

  class D(C):
      d=2
    
  C.c=2

  print D().d

  #</frozen.py>

Changing the language without changing the language
--------------------------------------------------------------------------

In Lisp the user has the possibility of changing the syntax of the 
language to suit her purposes (or simply to fit her taste). 
In Python, the user cannot change the basic grammar of the language,
nevertheless, to a great extent, metaclasses allows to emulate this effect. 
Notice that using metaclasses to this aim is not necessarely 
a good idea, since once you start
changing the Python standard behaviour, it will become impossible for
others  to understand your programs (which is what happened to Lisp ;).

Let me show how metaclasses can be used to provide notational convenience 
(i.e. syntactic sugar) for Python.

As first example, I will show how we may use metaclasses to provide some
convenient notation for staticmethods and classmethods:

 ::

  class MetaSugar(type):
      def __init__(cls,name,bases,clsdict):
          for key,value in clsdict.iteritems():
              if key.startswith("static_"):
                  setattr(cls,key[7:],staticmethod(value))
              elif key.startwith("class_"):
                  setattr(cls,key[6:],classmethod(value))
        
The same effect can be obtained trough normal inheritance

 ::

  class SyntacticSugar(object):
      def __init__(self):
          for k,v in self.__class__.__dict__.iteritems():
              if k.startswith('static_'):
                  self.__class__.__dict__[k[7:]] = staticmethod(v)
              if k.startswith('static_'):
                  self.__class__.__dict__[k[7:]] = staticmethod(v)

Let me now implement some syntactic sugar for the __metaclass__ hook.

 ::

  #<oopp.py> #rewrite without eval, please

  import re
  squarednames=re.compile('\[([A-Za-z_][\w\., ]*)\]')

  #def inferredfromdocstring(name,bases,dic):
  #    docstring=dic['__doc__']
  #    match=squarednames.match(docstring)
  #    if not match: return ClsFactory[Reflective](name,bases,dic)
  #    metanames=[name.strip() for name in match.group(1).split(',')]
  #    metaname=''.join(metanames)  
  #    if len(metanames)>1: # creates a new metaclass
  #        metaclass=type(metaname,tuple(map(eval,metanames,globals())),{})
  #    else:
  #        metaclass=eval(metaname,globals())
  #    return ClsFactory[metaclass](name,bases,dic)

  #</oopp.py>

  #<sugar.py>

  from oopp import *
  #__metaclass__ = inferredfromdocstring
  class B:
      "Do nothing class"

  class C: 
      "[Reflective]"
      " Do nothing class"

  class D:
      "[WithLogger,Final]"
      "Do nothing class"

  class E(C):
      pass

  #</sugar.py>

With output:

 ::

  *****************************************************************************
  Fri Feb 21 09:35:58 2003
  Creating class Logged_C descending from (),
  instance of <class 'oopp.Logged'>

  Logged_C dictionary:
   __doc__ = Do nothing class
  *****************************************************************************
  Fri Feb 21 09:35:58 2003
  Creating class Logged_Final_D descending from (),
  instance of <class 'oopp.LoggedFinal'>

  Logged_Final_D dictionary:
  __doc__ = Do nothing class
  *****************************************************************************
  Fri Feb 21 09:35:58 2003
  Creating class E descending from (<class 'oopp.Logged_C'>,),
  instance of <class 'oopp.Logged'>

  E dictionary:
  <EMPTY> 

At the end, let me point out few observations:
Metaclasses can be used to provide syntactic sugar, as I have shown
in the previous example. However, I have given the previous
routines as a proof of concept: I do *not* use these routines in
my actual code for many good reasons:

1. At the end a convenient notation will be provided in Python 2.4
2. I don't want to use magic tricks on my code, I want others to
   be able to understand what the code is doing;
3. I want to be able myself to understand my own code in six months
   from today ;)

Anyway, I think it is a good thing to know about this potentiality
of metaclasses, that can turn out to be very convenient in certain
applications: but this does not mean that should be blindly used
and/or abused. In other words: with great powers come 
great responsabilities ;)

Recognizing magic comments
--------------------------------------------------------------------------

In this section, I will begin to unravel the secrets of the black magic art 
of changing Python semantics and I will show that with few lines 
involving metaclasses
and the standard library 'inspect' module, even comments can be made
significant! (let me continue with my series "how to do what should not
be done").

To this aim, I need a brief digression on regular expressions.

 ::

  class RecognizesMagicComments(object):
     form=r'def %s(NAME)(args):#!\s?staticmethod'
     class __metaclass__(type):
         def __new__(meta,name,bases,dic):
             code=[]
             for attr in dic:
                 source=inspect.getsource(dic[attr]).splitlines()
                 for line in source:
                     split=line.split('#!')
                     if len(split)==2:
                         descriptor=split[1]; code.append(split[0])
                     else: code.append(line)
               
  class C(RecognizesMagicComments):
      #!staticmethod
      def f(x): #!staticmethod
          return x

Interpreting Python source code on the fly
---------------------------------------------------------------------------

At this point, I can really go *DEEP* in black magic.

 ::

  import sys, inspect, linecache, re

  def cls_source(name,module):
      lines = linecache.getlines(inspect.getsourcefile(module))
      if not lines: raise IOError, 'could not get source code'
      pat = re.compile(r'^\s*class\s*' + name + r'\b')
      for i in range(len(lines)):
          if pat.match(lines[i]): break
      else: raise IOError, 'could not find class definition'
      lines, lnum = inspect.getblock(lines[i:]), i + 1
      return ''.join(lines)

  class Interpreter(object):
      def __init__(self,CPO): # possible composition of code processing opers
          self.repl=CPO
      def __call__(self,name,bases,dic):
          try:
             modulename=dic['__module__'] # module where the class is defined
          except KeyError: # no __module__ attribute
             raise IOError("Class %s cannot be defined dynamically or in the\n"
             "interpreter and the source code cannot came from a pipe"% name)
          module=sys.modules[modulename] 
          source=self.repl(cls_source(name,module))
          source=re.sub('__metaclass__=.*','__metaclass__=type',source)
          #print source
          loc={}; exec source in vars(module),loc
          return loc[name]

  regexp_expand=Interpreter(regexp)

Implementing lazy evaluation
---------------------------------------------------------------------------

At this point of our knowledge, it becomes trivial to implement lazy 
evaluation and then a ternary operator. (My original, simpler, implementation
is posted on c.l.p.; see the thread 'PEP 312 (and thus 308) implemented 
with a black magic trick')

Implementing a ternary operator
---------------------------------------------------------------------------

 ::

  # module ternary.py

  "PEP 308 and 312 implemented via a metaclass-powered dirty trick"

  import inspect,__main__

  # the ternary operator:

  def if_(cond,f,g):
      "Short circuiting ternary operator implemented via lambdas"
      if cond: return f()
      else: return g()

  # the metaclass black magic:

  class DirtyTrick(type):
      """Cooperative metaclass that looks at the source code of its instances 
      and replaces the string '~' with 'lambda :' before the class creation"""
      def __new__(meta,name,bases,dic):
          for attr in dic.values():
              if inspect.isfunction(attr): 
                  code=inspect.getsource(attr)
                  if code.find('~')==-1: continue # no '~' found, skip
                  code=code.replace('~','lambda :')
                  code=dedent(code)+'\n'
                  exec code in __main__.__dict__,dic # modifies dic
          return super(DirtyTrick,meta).__new__(meta,name,bases,dic)

  # a convenient base class:

  class RecognizesImplicitLambdas:
      "Children of this class do recognize implicit lambdas"
      __metaclass__=DirtyTrick

Here there is an example of usage:

 ::

  from ternary import if_, RecognizesImplicitLambdas
  from math import sqrt

  class C(RecognizesImplicitLambdas):
     def safesqrt(self,x):
          return if_( x>0, ~sqrt(x), ~0) #short-circuiting ternary operator

  c=C()
  print c.safesqrt(4), c.safesqrt(-4)