Is a Borg rebellion possible? (a metaclass question)

  • Thread starter =?iso-8859-1?B?QW5kcuk=?=
  • Start date
?

=?iso-8859-1?B?QW5kcuk=?=

In my application, I make use of the Borg idiom, invented by Alex
Martelli.

class Borg(object):
'''Borg Idiom, from the Python Cookbook, 2nd Edition, p:273

Derive a class form this; all instances of that class will share
the
same state, provided that they don't override __new__; otherwise,
remember to use Borg.__new__ within the overriden class.
'''
_shared_state = {}
def __new__(cls, *a, **k):
obj = object.__new__(cls, *a, **k)
obj.__dict__ = cls._shared_state
return obj

----
This has worked very well so far, but is starting to impose some
unwanted constraints on my program design.

What I would like to do is, to put it figuratively, create a Borg
rebellion with various splinter groups. In concrete Python terms, I
would like to have

class MyClass(Borg, ...):
...

seven_of_nine = MyClass(...) # part of group "BORG"
two_of_nine = MyClass(...)

splinter1 = MyClass(..., group='splinter')
splinter2 = MyClass(..., group='splinter')

and have splinter 1 and splinter2 share the same state, but a
different state than the one shared by members of the BORG collective.

Any suggestions from the metaclass experts?

André
 
C

Carsten Haese

In my application, I make use of the Borg idiom, invented by Alex
Martelli.

class Borg(object):
'''Borg Idiom, from the Python Cookbook, 2nd Edition, p:273

Derive a class form this; all instances of that class will share
the
same state, provided that they don't override __new__; otherwise,
remember to use Borg.__new__ within the overriden class.
'''
_shared_state = {}
def __new__(cls, *a, **k):
obj = object.__new__(cls, *a, **k)
obj.__dict__ = cls._shared_state
return obj

----
This has worked very well so far, but is starting to impose some
unwanted constraints on my program design.

What I would like to do is, to put it figuratively, create a Borg
rebellion with various splinter groups. In concrete Python terms, I
would like to have

class MyClass(Borg, ...):
...

seven_of_nine = MyClass(...) # part of group "BORG"
two_of_nine = MyClass(...)

splinter1 = MyClass(..., group='splinter')
splinter2 = MyClass(..., group='splinter')

and have splinter 1 and splinter2 share the same state, but a
different state than the one shared by members of the BORG collective.

Any suggestions from the metaclass experts?

You don't need a metaclass. Just turn _shared_state into a dictionary of
shared states, keyed by the group name:

class SplinterBorg(object):
_shared_states = {}
def __new__(cls, *a, **k):
group = k.pop("group","BORG")
obj = object.__new__(cls, *a, **k)
obj.__dict__ = cls._shared_states.setdefault(group,{})
return obj

HTH,
 
?

=?iso-8859-1?B?QW5kcuk=?=

You don't need a metaclass. Just turn _shared_state into a dictionary of
shared states, keyed by the group name:

class SplinterBorg(object):
_shared_states = {}
def __new__(cls, *a, **k):
group = k.pop("group","BORG")
obj = object.__new__(cls, *a, **k)
obj.__dict__ = cls._shared_states.setdefault(group,{})
return obj

HTH,

Unfortunately, it fails. Here's what I tried, followed by the
traceback
class SplinterBorg(object):
_shared_states = {}
def __new__(cls, *a, **k):
group = k.pop("group","BORG")
obj = object.__new__(cls, *a, **k)
obj.__dict__ = cls._shared_states.setdefault(group,{})
return obj

class MyClass(SplinterBorg):
def __init__(self, name):
self.name = name

a1 = MyClass('a')
a2 = MyClass('aa')
b1 = MyClass('b', group="B")


Traceback (most recent call last):
File "test.py", line 15, in <module>
b1 = MyClass('b', group="B")
TypeError: __init__() got an unexpected keyword argument 'group'
 
S

Steve Holden

André said:
Unfortunately, it fails. Here's what I tried, followed by the
traceback
class SplinterBorg(object):
_shared_states = {}
def __new__(cls, *a, **k):
group = k.pop("group","BORG")
obj = object.__new__(cls, *a, **k)
obj.__dict__ = cls._shared_states.setdefault(group,{})
return obj

class MyClass(SplinterBorg):
def __init__(self, name):
self.name = name

a1 = MyClass('a')
a2 = MyClass('aa')
b1 = MyClass('b', group="B")


Traceback (most recent call last):
File "test.py", line 15, in <module>
b1 = MyClass('b', group="B")
TypeError: __init__() got an unexpected keyword argument 'group'
Because your subclass signature is completely wrong. Why did you feel
you had to add an __init__() method to your subclass? This has two bvad
effects:

1. It stops the super-class's __init__() method from being called, and

2. It breaks the calling syntax specified in the superclass.

All you really need is to create your SplinterBorgs with appropriate
group names, you don't neef subclasses at all:

a1 = SplinterBorg(group="one")
a2 = SplinterBorg(group="two")

and so on.

If you want to create subclasses then they should work like this:

class MyBorg1(SplinterBorg):
def __init__(self):
SplinterBorg.__init__(self, group='borg1')

and so on, with a different group for each. But this seems over complicated.

regards
Steve
--
Steve Holden +1 571 484 6266 +1 800 494 3119
Holden Web LLC/Ltd http://www.holdenweb.com
Skype: holdenweb http://del.icio.us/steve.holden
--------------- Asciimercial ------------------
Get on the web: Blog, lens and tag the Internet
Many services currently offer free registration
----------- Thank You for Reading -------------
 
C

Carsten Haese

Unfortunately, it fails. Here's what I tried, followed by the
traceback
class SplinterBorg(object):
_shared_states = {}
def __new__(cls, *a, **k):
group = k.pop("group","BORG")
obj = object.__new__(cls, *a, **k)
obj.__dict__ = cls._shared_states.setdefault(group,{})
return obj

class MyClass(SplinterBorg):
def __init__(self, name):
self.name = name
[...]
Traceback (most recent call last):
File "test.py", line 15, in <module>
b1 = MyClass('b', group="B")
TypeError: __init__() got an unexpected keyword argument 'group'

Indeed, if you have an __init__ method that shouldn't see the "group"
argument, you need a metaclass after all so you can yank the "group"
argument between __new__ and __init__. The following code seems to work,
but it's making my brain hurt:

class SplinterBorgMeta(type):
def __call__(cls, *args, **kwargs):
inst = cls.__new__(cls, *args, **kwargs)
kwargs.pop("group",None)
inst.__init__(*args,**kwargs)
return inst

class SplinterBorg(object):
__metaclass__ = SplinterBorgMeta
_shared_states = {}
def __new__(cls, *a, **k):
group = k.pop("group","BORG")
obj = object.__new__(cls, *a, **k)
obj.__dict__ = cls._shared_states.setdefault(group,{})
return obj

class MyClass(SplinterBorg):
def __init__(self, name):
self.name = name

The alternatives, as Steve just pointed out, would be not to subclass
SplinterBorg or to provide an __init__ method that expects a "group"
argument.

HTH,
 
?

=?iso-8859-1?B?QW5kcuk=?=

Because your subclass signature is completely wrong. Why did you feel
you had to add an __init__() method to your subclass? This has two bvad
effects:


Because this is what I need to do currently with using the standard
Borg class, and is something I would like to be able to do. Here's a
simplified version of the current usage I have
=====
class Borg(object):
_shared_state = {}
def __new__(cls, *a, **k):
obj = object.__new__(cls, *a, **k)
obj.__dict__ = cls._shared_state
return obj

class BorgConsole(Borg, SingleConsole):
'''Every BorgConsole share a common state'''
def __init__(self, locals={}, filename="Crunchy console"):
SingleConsole.__init__(self, locals, filename=filename)

=====
and it is called via something like
a_console = BorgConsole(locals)

where locals is a previously defined dict. Note that a number of
such instances are created dynamically as the program is used.

What I would like to do is to be able to add a "page_id" (the Borg
group referred to my previous message), so that I could call

a_console = BorgConsole(locals, page_id)
where page_id would be an optional argument, defaulting to the BORG
group, as mentioned in my original post.

I must admit I did not try with keywords arguments ... and will not be
for a couple of hours while I am at work.

1. It stops the super-class's __init__() method from being called, and
I don't understand why this is so here, and not with the usual Borg
case as I used it...
2. It breaks the calling syntax specified in the superclass.

All you really need is to create your SplinterBorgs with appropriate
group names, you don't neef subclasses at all:

a1 = SplinterBorg(group="one")
a2 = SplinterBorg(group="two")

and so on.

If you want to create subclasses then they should work like this:

class MyBorg1(SplinterBorg):
def __init__(self):
SplinterBorg.__init__(self, group='borg1')

and so on, with a different group for each. But this seems over complicated.

I do need subclasses (I think) as my classes inherit from more than
the SplinterBorg class... I'll see what I can do following some of
your suggestions.

Thanks for the reply,

André
 
?

=?iso-8859-1?B?QW5kcuk=?=

Unfortunately, it fails. Here's what I tried, followed by the
traceback
class SplinterBorg(object):
_shared_states = {}
def __new__(cls, *a, **k):
group = k.pop("group","BORG")
obj = object.__new__(cls, *a, **k)
obj.__dict__ = cls._shared_states.setdefault(group,{})
return obj
class MyClass(SplinterBorg):
def __init__(self, name):
self.name = name
[...]
Traceback (most recent call last):
File "test.py", line 15, in <module>
b1 = MyClass('b', group="B")
TypeError: __init__() got an unexpected keyword argument 'group'

Indeed, if you have an __init__ method that shouldn't see the "group"
argument, you need a metaclass after all so you can yank the "group"
argument between __new__ and __init__. The following code seems to work,
but it's making my brain hurt:

class SplinterBorgMeta(type):
def __call__(cls, *args, **kwargs):
inst = cls.__new__(cls, *args, **kwargs)
kwargs.pop("group",None)
inst.__init__(*args,**kwargs)
return inst

class SplinterBorg(object):
__metaclass__ = SplinterBorgMeta
_shared_states = {}
def __new__(cls, *a, **k):
group = k.pop("group","BORG")
obj = object.__new__(cls, *a, **k)
obj.__dict__ = cls._shared_states.setdefault(group,{})
return obj

class MyClass(SplinterBorg):
def __init__(self, name):
self.name = name

The alternatives, as Steve just pointed out, would be not to subclass
SplinterBorg or to provide an __init__ method that expects a "group"
argument.

HTH,

Thanks for your reply. I will try to adapt it and see if it works in
my case.

André
 
S

Steven Bethard

Carsten said:
Indeed, if you have an __init__ method that shouldn't see the "group"
argument, you need a metaclass after all so you can yank the "group"
argument between __new__ and __init__. The following code seems to work,
but it's making my brain hurt:

class SplinterBorgMeta(type):
def __call__(cls, *args, **kwargs):
inst = cls.__new__(cls, *args, **kwargs)
kwargs.pop("group",None)
inst.__init__(*args,**kwargs)
return inst

class SplinterBorg(object):
__metaclass__ = SplinterBorgMeta
_shared_states = {}
def __new__(cls, *a, **k):
group = k.pop("group","BORG")
obj = object.__new__(cls, *a, **k)
obj.__dict__ = cls._shared_states.setdefault(group,{})
return obj

class MyClass(SplinterBorg):
def __init__(self, name):
self.name = name

I think I would probably write that as::
... def __init__(cls, name, bases, bodydict):
... cls._shared_states = {}
... def __call__(cls, *args, **kwargs):
... group = kwargs.pop('group', None)
... inst = cls.__new__(cls, *args, **kwargs)
... inst.__dict__ = cls._shared_states.setdefault(group, {})
... inst.__init__(*args, **kwargs)
... return inst
... ... __metaclass__ = SplinterBorgMeta
... def __init__(self, name):
... self.name = name
... ('aa', 'aa', 'bb', 'bb')

That is, I don't think there's really a need for __new__ if you're using
a metaclass. Just set the instance's __dict__ in the __call__ method of
the metaclass.

STeVe
 
C

Carsten Haese

Carsten said:
[slightly convoluted example...]

I think I would probably write that as::
[concise example...]

That is, I don't think there's really a need for __new__ if you're using
a metaclass. Just set the instance's __dict__ in the __call__ method of
the metaclass.

Good point. I suppose it shows that this is the first metaclass I've
ever written, but it works, and it didn't make my brain explode ;)
 
B

BartlebyScrivener

All you really need is to create your SplinterBorgs with appropriate
group names, you don't neef subclasses at all:

oops, I tried this once and the link broke. I'll try tinyurl.

Dang. With that subject heading I thought this was about some post-
Singularity, Python-programmed cyborgs rising up to take over the
universe.

See, e.g.
How To Survive A Robot Uprising
http://tinyurl.com/yrk5pw

I am officially misled!

rd
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,236
Members
46,822
Latest member
israfaceZa

Latest Threads

Top