autoborg &c (was )e: Unified Type/class (Singleton)

Alex Martelli aleax at aleax.it
Mon Jan 28 07:08:34 EST 2002


"Pedro Rodriguez" <pedro_rodriguez at club-internet.fr> wrote in message
news:pan.2002.01.28.11.53.09.55635.1770 at club-internet.fr...
    ...
> Borg vs. Singleton : I was just kiding, sort of... I wonder if it would be
> good to provide a diagram when dealing with new concepts. Example :

For people who "thing visually", maybe.  I prefer to think verbally
(sharing state vs sharing identity).

Back to the mechanics (metaclasses or otherwise) that one can use
to implement creational design patterns (and other kinds).  When
I have a class object C, "instantiation" is a call on C, say:
    instance = C(*args, **kwds)
so it involves C.__call__, which typically comes from C.__class__.

type.__call__ can be summarized as:

    def __call__(self, *args, **kwds):
        result = self.__new__(self, *args, **kwds)
        if result: result.__init__(*args, **kwds)
        return result

where 'self', the instance of type, is C, the class object.


It's easy to check this (Python's GREAT this way...):

class X(object):
  def __new__(self, *args, **kwds):
      result = object.__new__(self)
      print 'X.__new__(%r,%s,%s)->%s'%(self, args, kwds, result)
      return result
  def __init__(self, *args, **kwds):
      print 'X.__init__(%r,%s,%s)'%(self, args, kwds)

x=X('ba','be',bi='bo')


D:\>python ni.py
X.__new__(<class '__main__.X'>,('ba', 'be'),{'bi': 'bo'})-><__main__.X
object at
 0x00789C50>
X.__init__(<__main__.X object at 0x00789C50>,('ba', 'be'),{'bi': 'bo'})


Nothing mysterious here, right?  Just notice the difference
between 'self' in the two cases.  Now, clearly, if you want to
influence what happens when C is called, your options include
overriding __new__ and/or __init__ in C, and overriding
__call__ in C's type (metaclass).

We have examples of the former two kinds in the above, here
is an example of the third kind showing an interaction of
all three options (only "influence", here too, is just the
printing of some trace information at key spots):

class T(type):
  def __call__(self, *args, **kwds):
      print 'T.__call__(%r,%s,%s)->'%(self, args, kwds)
      result = self.__new__(self, *args, **kwds)
      print 'T.__call__(%r,%s,%s)->%s'%(self, args, kwds, result)
      if result: result.__init__(*args, **kwds)
      return result

class TT:
  __metaclass__ = T

class X(TT):
  def __new__(self, *args, **kwds):
      result = super(X, self).__new__(self)
      print 'X.__new__(%r,%s,%s)->%s'%(self, args, kwds, result)
      return result
  def __init__(self, *args, **kwds):
      print 'X.__init__(%r,%s,%s)'%(self, args, kwds)

x=X('ba','be',bi='bo')


D:\>python bo.py
T.__call__(<class '__main__.X'>,('ba', 'be'),{'bi': 'bo'})->
X.__new__(<class '__main__.X'>,('ba', 'be'),{'bi': 'bo'})-><__main__.X
object at
 0x007B3E40>
T.__call__(<class '__main__.X'>,('ba', 'be'),{'bi': 'bo'})-><__main__.X
object a
t 0x007B3E40>
X.__init__(<__main__.X object at 0x007B3E40>,('ba', 'be'),{'bi': 'bo'})


If you work in C.__init__, then programmers who subclass C must
call C.__init__ explicitly (if they define their own __init__,
as most often happens).  Similarly for __new__, although I guess
it's much rarer for __new__ to be overridden in subclasses.  In
any case, your control is limited, since the "template method"
type.__call__ remains untouched.

If you choose to work by subclassing type (having C use your
own metaclass), you can get at the "template method" itself and
insert or tweak whatever you need to.  For example:

class autoborg(type):
  metastate = {}
  def __call__(self, *args, **kwds):
      result = self.__new__(self, *args, **kwds)
      result.__dict__ = self.metastate
      if result: result.__init__(*args, **kwds)
      return result

class Borg:
  __metaclass__ = autoborg

class anyborg(Borg):
  def __init__(self, *args, **kwds):
      self.__dict__.update(kwds)

a = anyborg(a='b',c='d')
b = anyborg(e='f',g='h')

print a.a, a.c, a.e, a.g


D:\>python mebo.py
b d f h


Note that client-code programmers, i.e. the authors of classes
such as anyborg, do NOT need to remember to call a base class's
__init__ (and to call it FIRST, before any state alteration).

Borg's typical __dict__-rebinding "just happens magically" in
this case, *before* the start of execution of the __init__ of
any class whose metaclass is autoborg.  This is the "full" kind
of Borg, where _all_ instances of _all_ borged classes share
just one state, by the way; it's not too difficult to arrange
things otherwise, e.g. by having each baseless class with
metaclass autoborg provide a separate metastate (left as an
exercise to the reader).  It *IS* difficult to make Borg work
with classes whose instances keep part of their state *elsewhere*
than in __dict__, of course... even just supporting __slots__
may (I'm still mulling about it) require some C programming.


By the way, I have a new counter for those who are horrified
by the idea of naming Borg after a TV show -- "TV show, what
TV show?  It's named in honor of Jorge Luis Borges, master of
identity and separateness, just shortened for ease of use and
uniformity of pronunciation across linguistic boundaries...".
[And if you buy THIS, I have a nice bridge to sell you, and
some very promising land in Florida, and...]


Alex






More information about the Python-list mailing list