tags:

views:

1062

answers:

6

I'm using base class constructor as factory and changing class in this constructor/factory to select appropriate class -- is this approach is good python practice or there are more elegant ways?

I've tried to read help about metaclasses but without big success.

Here example of what I'm doing.

class Project(object):
  "Base class and factory."
  def __init__(self, url):
      if is_url_local(url):
        self.__class__ = ProjectLocal
      else:
        self.__class__ = ProjectRemote
      self.url = url

class ProjectLocal(Project):
  def do_something(self):
    # do the stuff locally in the dir pointed by self.url

class ProjectRemote(Project):
  def do_something(self):
    # do the stuff communicating with remote server pointed by self.url

Having this code I can create the instance of ProjectLocal/ProjectRemote via base class Project:

project = Project('http://example.com')
project.do_something()

I know that alternate way is to using fabric function that will return the class object based on url, then code will looks similar:

def project_factory(url):
      if is_url_local(url):
        return ProjectLocal(url)
      else:
        return ProjectRemote(url)

project = project_factory(url)
project.do_something()

Is my first approach just matter of taste or it has some hidden pitfalls?

A: 

I think the second approach using a factory function is a lot cleaner than making the implementation of your base class depend on its subclasses.

unbeknown
+11  A: 

I would stick with the factory function approach. It's very standard python and easy to read and understand. You could make it more generic to handle more options in several ways such as by passing in the discriminator function and a map of results to classes.

If the first example works it's more by luck than by design. What if you wanted to have an __init__ defined in your subclass?

scooterXL
+1: Don't try to make the base class do anything more than be the base class. Factories are always separate.
S.Lott
Another good option for defining factories is classmethods — they give you class parametrisation for free via `cls` argument.
andreypopp
+1  A: 

I usually have a seperate factory class to do this. This way you don't have to use meta classes or assignments to self.__class__

I also try to avoid to put the knowledge about which classes are available for creation into the factory. Rather, I have all the available classes register themselves withe the factory during module import. The give there class and some information about when to select this class to the factory (this could be a name, a regex or a callable (e.g. a class method of the registering class)).

Works very well for me and also implements such things like encapsulation and information hiding.

Ber
+3  A: 

The following links may be helpful: http://www.suttoncourtenay.org.uk/duncan/accu/pythonpatterns.html#factory http://code.activestate.com/recipes/86900/

In addition, as you are using new style classes, using __new__ as the factory function (and not in a base class, a separate class is better) is what is usually done (as far as I know).

A factory function is generally simpler (as other people have already posted)

In addition, it isn't a good idea to set the __class__ attribute the way you have done.

I hope you find the answer and the links helpful.

All the best.

batbrat
+1, __new__ is the ‘proper’ way to do this, if you really have to. Not recommended for beginners though. Stick with the factory function for clarity, or refactor the classes so that local/remote is determined by something other than the class (eg. composition).
bobince
Thanks for the tip bobince. I am not an expert by any means, so my answers are just based on what I have studied in books. Its great to get some practical advice. Thanks again.
batbrat
I do have practical Python experience though. I love programming in it. However, I haven't much with design patterns in Python.
batbrat
+1 for warning to mess with __class__ attribute
Aaron Digulla
+2  A: 

You shouldn't need metaclasses for this. Take a look at the __new__ method. This will allow you to take control of the creation of the object, rather than just the initialisation, and so return an object of your choosing.

class Project(object):
  "Base class and factory."
  def __new__(cls, url):
    if is_url_local(url):
       return super(Project, cls).__new__(ProjectLocal, url) 
    else:
       return super(Project, cls).__new__(ProjectRemote, url) 

  def __init__(self, url):
    self.url = url
Brian
I guess this is what I've looked for but get lost in metaclasses. Thanks.
bialix
it works, thank you.
bialix
A: 

Yeah, as mentioned by @scooterXL, factory function is the best approach in that case, but I like to note a case for factories as classmethods.

Consider the following class hierarchy:

class Base(object):

    def __init__(self, config):
        """ Initialize Base object with config as dict."""
        self.config = config

    @classmethod
    def from_file(cls, filename):
        config = read_and_parse_file_with_config(filename)
        return cls(filename)

class ExtendedBase(Base):

    def behaviour(self):
        pass # do something specific to ExtendedBase

Now you can create Base objects from config dict and from config file:

>>> Base({"k": "v"})
>>> Base.from_file("/etc/base/base.conf")

But also, you can do the same with ExtendedBase for free:

>>> ExtendedBase({"k": "v"})
>>> ExtendedBase.from_file("/etc/extended/extended.conf")

So, this classmethod factory can be also considered as auxiliary constructor.

andreypopp