views:

529

answers:

3

Suppose you have two classes X & Y. You want to decorate those classes by adding attributes to the class to produce new classes X1 and Y1.

For example:

class X1(X):
  new_attribute = 'something'

class Y1(Y):
  new_attribute = 'something'

*new_attribute* will always be the same for both X1 and Y1. X & Y are not related in any meaningful way, except that multiple inheritance is not possible. There are a set of other attributes as well, but this is degenerate to illustrate.

I feel like I'm overcomplicating this, but I had thought to use a decorator, somewhat likeso:

def _xywrap(cls):
  class _xy(cls):
    new_attribute = 'something'
  return _xy

@_xywrap(X)
class X1():
   pass

@_xywrap(Y)
class Y1():
   pass

It feels like I'm missing a fairly common pattern, and I'd be much obliged for thoughts, input and feedback.

Thank you for reading.

Brian

EDIT: Example:

Here is a relevant extract that may illuminate. The common classes are as follows:

from google.appengine.ext import db

# I'm including PermittedUserProperty because it may have pertinent side-effects
# (albeit unlikely), which is documented here: [How can you limit access to a
# GAE instance to the current user][1].

class _AccessBase:
   users_permitted = PermittedUserProperty()
   owner = db.ReferenceProperty(User)

class AccessModel(db.Model, _AccessBase):
    pass

class AccessExpando(db.Expando, _AccessBase):
    pass

# the order of _AccessBase/db.* doesn't seem to resolve the issue
class AccessPolyModel(_AccessBase, polymodel.PolyModel):
    pass

Here's a sub-document:

 class Thing(AccessExpando):
     it = db.StringProperty()

Sometimes Thing will have the following properties:

 Thing { it: ... }

And other times:

 Thing { it: ..., users_permitted:..., owner:... }

I've been unable to figure out why Thing would sometimes have its _AccessParent properties, and other times not.

+2  A: 

Why can't you use multiple inheritance?

class Origin:
  new_attribute = 'something'

class X:
  pass

class Y:
  pass

class X1(Origin, X):
  pass

class Y1(Origin, Y):
  pass
voyager
Brian M. Hunt
... or to clarify ... I don't think it's possible. :o) I'd be delighted if someone could show that it is.
Brian M. Hunt
I'm not sure that I understand why multiple inheritance won't work in this situation. In voyager's example here, it could just as easily be `class X1(Origin, google.appengine.db.Model)` and `class Y1(Origin, django.db.Model)`, couldn't it?
Will McCutchen
You'd think that- but the problem I'm having is that the models sometimes have the properties of the Origin, sometimes they don't. I was quite estatic when Origins' properties started showing up in my models, but then they inexplicably stopped showing up. That sort of nondeterminism is very unnerving! I'm not sure what's going on, but I'm certain it doesn't work. I think someone (maybe me) will have to dig through the Google App Engine code to figure out how it's adding properties to its _entities.
Brian M. Hunt
+2  A: 

Responding to your comments on voyager's answer:

from google.appengine.ext import db

class Mixin(object):
    """Mix in attributes shared by different types of models."""
    foo = 1
    bar = 2
    baz = 3

class Person(db.Model, Mixin):
    name = db.StringProperty()

class Dinosaur(db.polymodel.PolyModel, Mixin):
    height = db.IntegerProperty()

p = Person(name='Buck Armstrong, Dinosaur Hunter')
d = Dinosaur(height=5000)

print p.name, p.foo, p.bar, p.baz
print d.height, d.foo, d.bar, d.baz

Running that results in

Buck Armstrong, Dinosaur Hunter 1 2 3
5000 1 2 3

Is that not what you had in mind?

Will McCutchen
This works - for some reason it didn't work (i.e. pass my unit tests) when I had e.g. class Person(Mixin, db.Model), but I've reversed it (viz. to class Person(db.Model, Mixin)) and it works fine now. This is the behaviour I had expected and desired, so I'm thankful your posted your answer. Thank you.
Brian M. Hunt
I was wrong - this only works sometimes. Why it works sometimes and not others is a mystery to be solved by others, I hope, but suffice to say that sometimes I get subclasses (e.g. Person) with Mixin properties, and sometimes I get subclasses without them. It's inexplicable -- I'll post more info if I figure anything out.
Brian M. Hunt
Can you show some code that exhibits this behavior? Is there a chance that your mixin's properties are shadowing (or being shadowed by) properties of the other superclass?
Will McCutchen
@Will: Thanks for your attention to this. I just added an example.
Brian M. Hunt
@Will: The objective is to have the Mixin object contain db.*Properties (i.e. not just primitives).
Brian M. Hunt
+1  A: 

Use 3-arguments type:

def makeSomeNicelyDecoratedSubclass(someclass):
  return type('MyNiceName', (someclass,), {'new_attribute':'something'})

This is indeed, as you surmised, a reasonably popular idiom.

Edit: in the general case if someclass has a custom metaclass you may need to extract and use it (with a 1-argument type) in lieu of type itself, to preserve it (this may be the case for your Django and App Engine models):

def makeSomeNicelyDecoratedSubclass(someclass):
  mcl = type(someclass)
  return mcl('MyNiceName', (someclass,), {'new_attribute':'something'})

This also works where the simpler version above does (since in simple cases w/no custom metaclasses type(someclass) is type).

Alex Martelli
Hi Alex! Thanks for updating your answer. I had actually started a comment right after I saw this yesterday, but I was waiting to try it out before responding. The mcl was a key point I briefly considered. I'll test this out and let you know sometime this week (I hope). Your thoughts and input are much appreciated! This is an interesting problem. :)
Brian M. Hunt