Welcome, guest | Sign In | My Account | Store | Cart

The ObjectMerger class dynamically merges two given objects, making one a subclass of the other. The input elements could either be class instances or simple types, making this class particularly useful to derive native classes (for instance, cStringIO). The resulting ObjectMerger instance acts as a proxy to the new type, allowing callers to work with it in the same way they would work with any other statically derived type.

Python, 64 lines
 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
#
# version: 1  (2004-03-05)
#

class ObjectMerger:
    """
    Object 'Merger'.
    
    Returns an instance that takes methods from two given instances/objects. 
    In order, as if one have the subclass methods of the other.
    """
    def __init__(self, super, _self):
        self.___super = super
        self.___self = _self
        
    def __getattr__ ( self, name ):
        if not name.startswith('___'):
            super = self.___super
            _self = self.___self
            if hasattr(_self, name):
                return getattr(_self, name)
            elif hasattr(super, name):
                return getattr(super, name)
            
        return getattr(ObjectMerger, name)
    
    
if __name__=='__main__':
    import unittest
    
    class test1:
        def a(self): pass
        def b(self): pass
        
    class test2:
        def a(self): pass
        def c(self): pass
    
    class testObjectMerger(unittest.TestCase):
        def test_merged_instances_method_priority (self):
            """Test priority of methods"""
            c1 = test1()
            c2 = test2()
            m = ObjectMerger(c1, c2)
            self.assertEqual(m.a, c2.a)
            self.assertEqual(m.b, c1.b)
            self.assertEqual(m.c, c2.c)
            
        def test_merged_objects_with_one_noninstance (self):
            """Test mix of instance and object(string)"""
            c1 = test1()
            c2 = 'hola carlos'
            m = ObjectMerger(c1, c2)
            self.assertEqual(m.a, c1.a)
            #can't compare functions directly because:
            #   1. it seems that after first get function is copied..
            #       or something
            #   2. don't know why python fails comparing the function..
            self.assertEqual(m.__len__(), c2.__len__())
            self.assertEqual(m.b, c1.b)

    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(testObjectMerger))
    unittest.TextTestRunner(verbosity=2).run(suite)

I used it to merge aSocket with ssl(aSocket).. giving an ssl_socket with close(). :-)

I choose this, because didn't want to make classes in runtime.

ToDo / Bugs / Improvements:

  1. __str__ and __repr__ functions.

  2. I think this code is not scalable, i.e.:

    m = ObjectMerger(ObjectMerger(a,b),c),
    

    but I didn't try that.. in any case, may be possible to setup internal instances with a name like id(self)_super ..

  3. Memorization of attributes, for eliminate one indirection. Something like:

    def __getattr__(self, name): .. attr = getattr(..) eval('A.%s = B'%, {'A':self, 'B':attr})

    but i'm not sure if this works for nonfunction parameters.. and, also, could have unexpected behaviour.

  4. Maybe usefull some method name overwrite, when you want to keep one interface and the subclass changes the names. I didn't wrote that. But could be usefull. For example, to do: ObjectMerger(aSocket, ssledSocket, {'recv':'read', 'send':'write'} )

  5. This prevents getting any attribute of its instances whose name starts with ___ ( 3 x '_' )

  6. From the instance returned you could write methods calling the 'superclass' using the instance variable self.___super.

5 comments

Javier Burroni 20 years, 1 month ago  # | flag

scalling fix. To make scalling works, this patch should be added:

class ObjectMerger:
    def __hasattr__( self, attr ):
        return reduce( lambda x, y: x or hasattr( y, attr ), [self.___super, self.__self], 0 )
David Beach 20 years, 1 month ago  # | flag

What about more than two objects??? Maybe you could change it to:

class ObjectMerger:
    """
    Object 'Merger'.

    Returns an instance that takes methods from an arbitrary sequence
    of instances/objects, in order.  The initial sequnce of objects
    is duplicated on construction, so future alterations will not
    affect the behavior of this class.  Each object is tested for the
    required attribute in order, until a match is found.
    """
    def __init__(self, classes):
        self.___classes = tuple(classes)

    def __getattr__ ( self, name ):
        if not name.startswith('___'):
            for cls in self.___classes:
                try:
                    return getattr(cls, name)
                except AttributeError:
                    pass
        return getattr(ObjectMerger, name)

I believe this will allow you to fuse the attributes of any number of classes, without the explicit need for functional composition.

Here's a different version which is a bit more minimal, but may also be more efficent:

class ObjectMerger(tuple):
    def __getattr__(self, name):
        for obj in self:
            try:
                return getattr(obj, name)
            except AttributeError:
                pass
        return getattr(ObjectMerger, name)

Dave

David Beach 20 years, 1 month ago  # | flag

Oops... Should have used __getattribute__ on this last example (for new style classes).

Alejandro David Weil (author) 20 years, 1 month ago  # | flag

Thanks. I'll try that with Javier's line too.

I prefer using hasattr() instead of catch the exception, but don't know what is better of both ways.

Thanks!

David Beach 20 years, 1 month ago  # | flag

Try/Except vs. using hasattr(). I believe that using try/except is superior to testing with hasattr. Because it involves only one test (instead of using two), the code should execute faster.

There's nothing wrong with using hasattr() in principle, but I chose to use the try/except mechanism as a way of improving the performance of the algorithm, especially if it is searching through an arbitrary number of classes/instances.

Created by Alejandro David Weil on Thu, 4 Mar 2004 (PSF)
Python recipes (4591)
Alejandro David Weil's recipes (1)

Required Modules

Other Information and Tasks