3

My attempt at answering this question draws on this question: Cast base class to derived class python (or more pythonic way of extending classes)

I'm writing a mixin class that will add some functionality to an object returned by another module. The code in the other module looks like this:

class Foo(Mixin):
    def __new__(cls, *args, **kwargs):
        #...handle a bunch of cases

        if case1:
            return FooTypeA
        elif case2:
            return FooTypeB
        #... etc

class FooTypeA(Mixin):
    #...

class FooTypeB(Mixin):
    #...

I've written MyMixin, which adds a little bit of functionality to the objects returned by Foo. My attempt at solving the problem is this:

from other_module import Foo, FooTypeA, FooTypeB, ...

class MyFoo(Foo):
    def __new__(cls, *args, **kwargs):
        #...handle most of the same cases

        if case1:
            ret = FooTypeA(*args, **kwargs)
            ret.__class__ = MyFooTypeA
        if case2:
            ret = FooTypeB(*args, **kwargs)
            ret.__class__ = MyFooTypeB
        #... etc


class MyFooTypeA(FooTypeA, MyMixin):
    pass

class MyFooTypeB(FooTypeB, MyMixin):
    pass

This looks really, really ugly. Is there really no better solution?

If not, why?

EDIT: I thought it would be easier without going in to speicifics, but the code I'm actually working on is here. The author of this module has written "WebDriverMixin," which mostly provides some nicer syntax for accessing elements on the page that an instance of the selenium webdriver is on. I have "SiteSpecificMixin," which provides some nicer syntax for accessing elements of the particular site that I'm testing.

webdriverplus.WebDriver returns instances of webdriverplus.Firefox, webdriverplus.Chrome, webdriverplus.Ie, etc. webdriverplus.Firefox inherits from webdriverplus.WebDriverMixin and selenium.webdriver.firefox.webdriver.Firefox, webdriverplus.Chrome inherits from webdriverplus.WebDriverMixin and selenium.webdriver.firefox.webdriver.Chrome, etc.

I want to add functionality to the objects that webdriverplus.Webdriver returns, which seems like it requires making a class, mysite.SiteSpecificDriver, copy+pasting the body of webdriverplus.WebDriver.__new__ into mysite.SiteSpecificDriver.__new__, and then writing mysite.Firefox (which needs to inherit from webdriverplus.Firefox and mysite.SiteSpecificMixin), mysite.Chrome (which needs to inherit from webdriverplus.Chrome and mysite.SiteSpecificMixin), etc, and re-handling all of the browsers inside my own module that the original author handles in his.

I'm handling it now with code like in my example above, and it works. I'm new to OOP, but my understanding of OO techniques is that they're supposed to let you avoid code that has long if-elif-...-else clauses that depend on what type of object you're working with, so I think that I must be doing something wrong.

Community
  • 1
  • 1
Patrick Collins
  • 10,306
  • 5
  • 30
  • 69
  • 1
    What are the cases? Your question is a little too vague for an answer right now. – Blender Aug 17 '13 at 12:36
  • Your question is confusing. where's the MyMixin code? what's Mixin? What is the point of MyFoo? – Marcin Aug 17 '13 at 13:50
  • Marcin: I don't really see why the contents of Mixin or MyMixin would matter -- the point is just that I need the objects returned by MyFoo to inherit from the latter, whereas objects returned by Foo inherit from the former -- but I included substantially more context. – Patrick Collins Aug 18 '13 at 11:04

1 Answers1

2

You can rewrite this in a more dynamic manner:

from other_module import Foo, FooTypeA, FooTypeB

bases = [Foo, FooTypeA, FooTypeB]

class MyMixin(object):
    pass

def factory(bases, mixins, name='MyClass'):
    return type(name, bases + mixins, {})

new_classes = [factory((c,), (MyMixin,)) for c in bases]
Gill Bates
  • 14,330
  • 23
  • 70
  • 138
  • This looks nice. I forked the original repo to use this method, and I'm pretty sure that the class_factory function I made will also work in my code. I found that I needed to modify globals() to make it work the way the original module worked, which seems a tiny bit hacky, but it's much more concise than the original. My fork is [here](https://github.com/capitalsigma/webdriverplus/blob/0.2.0/webdriverplus/__init__.py), and the original is [here](https://github.com/tomchristie/webdriverplus/blob/0.2.0/webdriverplus/__init__.py) -- feedback would be great. – Patrick Collins Aug 18 '13 at 07:33