object inheritance

A

Anand

I am trying to implement some kind of object inheritance. Just like
one class can extend from another, I want to do the same on objects
dynamically.

I just thought that I can share my excitement here.

Suppose there are classes A and B and their instances a and b.

class A:
def foo(self): self.say('foo')
def say(self, msg):
print 'a.say', msg

class B:
def say(self, msg):
print 'b.say', msg

a = A()
b = B()

I want b to inherit the behavior of a.
b.say foo

I looked around and found that some people talked about similar ideas,
but didn't find any concrete implementation.

I came up with the following implementation using meta-classes.

class ExtendMetaClass(type):
def __init__(cls, *a, **kw):
# take all attributes except special ones
keys = [k for k in cls.__dict__.keys() if not
k.startswith('__')]
d = [(k, getattr(cls, k)) for k in keys]

# remove those attibutes from class
for k in keys:
delattr(cls, k)

# remember then as dict _d
cls._d = dict(d)

def curry(f, arg1):
def g(*a, **kw):
return f(arg1, *a, **kw)
g.__name__ = f.__name__
return g

def _getattr(self, name):
"""Get value of attribute from self or super."""
if name in self.__dict__:
return self.__dict__[name]
elif name in self._d:
value = self._d[name]
if isinstance(value, types.MethodType):
return curry(value, self)
else:
return value
else:
if self._super != None:
return self._super._getattr(name)
else:
raise AttributeError, name

def __getattr__(self, name):
"""Returns value of the attribute from the sub object.
If there is no sub object, self._getattr is called.
"""
if name.startswith('super_'):
return self._super._getattr(name[len('super_'):])

if self._sub is not None:
return getattr(self._sub, name)
else:
return self._getattr(name)

def extend_from(self, super):
"""Makes self extend from super.
"""
self._super = super
super._sub = self

cls.__getattr__ = __getattr__
cls._getattr = _getattr
cls._super = None
cls._sub = None
cls.extend_from = extend_from

class Extend:
__metaclass__ = ExtendMetaClass
def __init__(self, super=None):
if super:
self.extend_from(super)

And the above example becomes:

class A(Extend):
def foo(self): self.say('foo')
def say(self, msg):
print 'a.say', msg

class B(Extend):
def say(self, msg):
print 'b.say', msg
# self.super_foo calls foo method on the super object
self.super_say('super ' + msg)

a = A()
b = B()
b.say foo
a.say super foo

There are one issue with this approach. Once b extends from a,
behavior of a also changes, which probably should not. But that
doesn't hurt me much.

Any comments?
 
P

Pradeep Jindal

Can you tell any specific use case for doing this?

Regards,
Pradeep

I am trying to implement some kind of object inheritance. Just like
one class can extend from another, I want to do the same on objects
dynamically.

I just thought that I can share my excitement here.

Suppose there are classes A and B and their instances a and b.

class A:
def foo(self): self.say('foo')
def say(self, msg):
print 'a.say', msg

class B:
def say(self, msg):
print 'b.say', msg

a = A()
b = B()

I want b to inherit the behavior of a.
b.say foo

I looked around and found that some people talked about similar ideas,
but didn't find any concrete implementation.

I came up with the following implementation using meta-classes.

class ExtendMetaClass(type):
def __init__(cls, *a, **kw):
# take all attributes except special ones
keys = [k for k in cls.__dict__.keys() if not
k.startswith('__')]
d = [(k, getattr(cls, k)) for k in keys]

# remove those attibutes from class
for k in keys:
delattr(cls, k)

# remember then as dict _d
cls._d = dict(d)

def curry(f, arg1):
def g(*a, **kw):
return f(arg1, *a, **kw)
g.__name__ = f.__name__
return g

def _getattr(self, name):
"""Get value of attribute from self or super."""
if name in self.__dict__:
return self.__dict__[name]
elif name in self._d:
value = self._d[name]
if isinstance(value, types.MethodType):
return curry(value, self)
else:
return value
else:
if self._super != None:
return self._super._getattr(name)
else:
raise AttributeError, name

def __getattr__(self, name):
"""Returns value of the attribute from the sub object.
If there is no sub object, self._getattr is called.
"""
if name.startswith('super_'):
return self._super._getattr(name[len('super_'):])

if self._sub is not None:
return getattr(self._sub, name)
else:
return self._getattr(name)

def extend_from(self, super):
"""Makes self extend from super.
"""
self._super = super
super._sub = self

cls.__getattr__ = __getattr__
cls._getattr = _getattr
cls._super = None
cls._sub = None
cls.extend_from = extend_from

class Extend:
__metaclass__ = ExtendMetaClass
def __init__(self, super=None):
if super:
self.extend_from(super)

And the above example becomes:

class A(Extend):
def foo(self): self.say('foo')
def say(self, msg):
print 'a.say', msg

class B(Extend):
def say(self, msg):
print 'b.say', msg
# self.super_foo calls foo method on the super object
self.super_say('super ' + msg)

a = A()
b = B()
b.say foo
a.say super foo

There are one issue with this approach. Once b extends from a,
behavior of a also changes, which probably should not. But that
doesn't hurt me much.

Any comments?
 
A

Anand

Can you tell any specific use case for doing this?

I have many implementaions of a db interface.

SimpleDB - simple implementation
BetterDB - optimized implementation
CachedDB - an implementation with caching of queries
RestrictedDB - implementation with permissions

Now, I want to combine these implementations and use.
Typical use case scenarios are:

db = RestrictedDB(CachedDB(SimpleDB()))
db = RestrictedDB(SimpleDB())
db = RestrictedDB(BetterDB())
db = RestrictedDB(CachedDB(BetterDB())
db = CachedDB(SimpleDB())
etc..
 
D

Duncan Booth

Anand said:
I have many implementaions of a db interface.

SimpleDB - simple implementation
BetterDB - optimized implementation
CachedDB - an implementation with caching of queries
RestrictedDB - implementation with permissions

Now, I want to combine these implementations and use.
Typical use case scenarios are:

db = RestrictedDB(CachedDB(SimpleDB()))
db = RestrictedDB(SimpleDB())
db = RestrictedDB(BetterDB())
db = RestrictedDB(CachedDB(BetterDB())
db = CachedDB(SimpleDB())
etc..
No, that is an argument for multiple-inheritance, mixin classes etc. You
know when constructing the object what behaviour you want it to have. It
isn't an argument for changing the behaviour of an existing object
dynamically.

for an example where it is useful to extend the behaviour of objects have a
look at Zope3 interfaces and adaptors: an object can be defined to
implement one or more interfaces, and you can associate additional
interfaces with objects at runtime. Sometimes an object will implement an
interface directly, but often the implementation is taken out into a
separate 'adaptor' class. Whenever you want to use a specified interface on
an object you simply use the interface class as a constructor on the
object, and that might find the appropriate adapter or it might just return
the original object if the interface is provided directly.

e.g. see http://plone.org/documentation/tutorial/five-zope3-walkthrough

Most of the interface/adapter/object relationships in Zope are static, but
there are interfaces which can be added/removed from objects at runtime:
each web accessible object has a page where you can add or remove
interfaces. For example there is an INavigationRoot interface that you can
add to an object which makes it become the top of the navigation tree for
that part of the site or IPhotoAlbumAble which lets an object behave as a
photo album.

Note that this is very different than the forwarding mechanism which
started this thread: when you use an adaptor you get something which
implements just the specified interface, you don't have to worry about
conflicting names for methods or attributes. Also you can have a common
interface associated with objects which have completely different adapters
implementing that interface.
 
P

Pradeep Jindal

I have many implementaions of a db interface.

SimpleDB - simple implementation
BetterDB - optimized implementation
CachedDB - an implementation with caching of queries
RestrictedDB - implementation with permissions

Now, I want to combine these implementations and use.
Typical use case scenarios are:

db = RestrictedDB(CachedDB(SimpleDB()))
db = RestrictedDB(SimpleDB())
db = RestrictedDB(BetterDB())
db = RestrictedDB(CachedDB(BetterDB())
db = CachedDB(SimpleDB())
etc..

I agree with Duncan. According to me, this should be called Delegation rather
than inheritance. And delegation should work without any conflicts of
identifier names and all that. I think, it should be all about several
objects implementing a protocol (interface) and that should work cleanly.
 
A

Anand

I agree with Duncan. According to me, this should be called Delegation rather
than inheritance. And delegation should work without any conflicts of
identifier names and all that. I think, it should be all about several
objects implementing a protocol (interface) and that should work cleanly.

I don't think so. It is not as simple as delegation.

In the example that I gave previously, call to self.say in A.foo
calls B.say not A.say, which I think is not possible with delegation.
 
A

Anand

No, that is an argument for multiple-inheritance, mixin classes etc. You
know when constructing the object what behaviour you want it to have. It
isn't an argument for changing the behaviour of an existing object
dynamically.


Partially true. I don't want to change the behavior of an exiting
object. I know what behavior I want to have at the time of creating.
This can be achieved by creating classes dynamically by parameterizing
the base class, which I thought is ugly.
for an example where it is useful to extend the behaviour of objects have a
look at Zope3 interfaces and adaptors: an object can be defined to
implement one or more interfaces, and you can associate additional
interfaces with objects at runtime. Sometimes an object will implement an
interface directly, but often the implementation is taken out into a
separate 'adaptor' class. Whenever you want to use a specified interface on
an object you simply use the interface class as a constructor on the
object, and that might find the appropriate adapter or it might just return
the original object if the interface is provided directly.

e.g. seehttp://plone.org/documentation/tutorial/five-zope3-walkthrough
Interesting!

Note that this is very different than the forwarding mechanism which
started this thread: when you use an adaptor you get something which
implements just the specified interface, you don't have to worry about
conflicting names for methods or attributes. Also you can have a common
interface associated with objects which have completely different adapters
implementing that interface.

It is not a simple one-way forwarding mechanism. It is two-way.

An example:

class SimpleDB(Extend):
def withID(self, id):
# do query

def withIDs(self, ids):
return [self.withID(id) for id in ids]

class CachedDB(Extend):
def withID(self, id):
if id not in self.cache:
self.cache[id] = self.super_withID(id)
return self.cache[id]


db = CachedDB(SimpleDB())

When I call db.withIDs, it should make use of the cache, which I think
is not possible with a simple forwarding mechanism.
 
G

Gabriel Genellina

Partially true. I don't want to change the behavior of an exiting
object. I know what behavior I want to have at the time of creating.
This can be achieved by creating classes dynamically by parameterizing
the base class, which I thought is ugly.

If you know at compile time which features you want, you can use multiple
inheritance to define the new class.
If you only know the set of clases to mix at run time, you can dinamically
create new classes with 'type':

newclass = type('newclass', (CachedDB, SimpleDB), {})
db = newclass()
db.withIDs will call CachedDB.withID, as you want.

[In another message]
In the example that I gave previously, call to self.say in A.foo
calls B.say not A.say, which I think is not possible with delegation.

It's easy to create a new type inheriting from your classes:

obj = type('', (B,A), {})()
obj.foo()
should print: b.say "foo"
 
P

praddy

I don't think so. It is not as simple as delegation.

In the example that I gave previously, call to self.say in A.foo
calls B.say not A.say, which I think is not possible with delegation.

Let me explain what I meant, its all about several objects (e.g. "a")
implementing a protocol, in your case the "say" method, and some
object (e.g. "b") extending those objects by adding some more
functionality but requiring that those objects, that it extends from,
follow a specific protocol ("a" must have say method). Also note that,
"B" or "b" is partial implementation of what its meant for, I mean,
calling "b"'s "say" will not work if "b" has not yet extended from "a"
or any other object implementing the same protocol.

Less time, can't get into more details. Bye for now.

Thanks
-Pradeep
 
A

Anand

If you know at compile time which features you want, you can use multiple
inheritance to define the new class.
If you only know the set of clases to mix at run time, you can dinamically
create new classes with 'type':

newclass = type('newclass', (CachedDB, SimpleDB), {})
db = newclass()
db.withIDs will call CachedDB.withID, as you want.

Interesting.

CachedDB.withID should call withID from its super class (here
SimpleDB), when it is not in cache. How to do that?
 
G

Gabriel Genellina

Interesting.

CachedDB.withID should call withID from its super class (here
SimpleDB), when it is not in cache. How to do that?

Using super(). See
<http://docs.python.org/dev/tutorial/classes.html#multiple-inheritance>
and <http://www.python.org/2.3/mro.html> for the details.

This is a running version of your example:

py> class SimpleDB(object):
.... def withID(self, id):
.... print "SimpleDB.withID",id
.... return "Data for %s" % id
.... def withIDs(self, ids):
.... return [self.withID(id) for id in ids]
....
py> class CachedDB(object):
.... def __init__(self):
.... # CachedDB instances must have a cache dictionary
.... super(CachedDB, self).__init__(self)
.... self.cache = {}
.... def withID(self, id):
.... print "CachedDB.withID",id
.... res = self.cache.get(id)
.... if res is None:
.... print "cache miss!"
.... self.cache[id] = res = super(CachedDB, self).wit
hID(id)
.... return res
....
py> newclass = type('newclass', (CachedDB, SimpleDB), {})
py> db = newclass()
py> print db.withIDs([1,2,1,3,2,1])
CachedDB.withID 1
cache miss!
SimpleDB.withID 1
CachedDB.withID 2
cache miss!
SimpleDB.withID 2
CachedDB.withID 1
CachedDB.withID 3
cache miss!
SimpleDB.withID 3
CachedDB.withID 2
CachedDB.withID 1
['Data for 1', 'Data for 2', 'Data for 1', 'Data for 3', 'Data f
or 2', 'Data for 1']

Multiple inheritance is not the only option. You may want to read about
decorators too: instead of overriding the method withID, you can decorate
it with some variant of the "memoize" decorator. Look for the (excellent!)
article on decorators by Michele Simionato.
 

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

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,065
Latest member
OrderGreenAcreCBD

Latest Threads

Top