What's the best way to extend the functionality of factory-produced classes outside of the module in python?

StackOverflow https://stackoverflow.com/questions/18897864

  •  29-06-2022
  •  | 
  •  

Question

I've been reading lots of previous SO discussions of factory functions, etc. and still don't know what the best (pythonic) approach is to this particular situation. I'll admit up front that i am imposing a somewhat artificial constraint on the problem in that i want my solution to work without modifying the module i am trying to extend: i could make modifications to it, but let's assume that it must remain as-is because i'm trying to understand best practice in this situation.

I'm working with the http://pypi.python.org/pypi/icalendar module, which handles parsing from and serializing to the Icalendar spec (hereafter ical). It parses the text into a hierarchy of dictionary-like "component" objects, where every "component" is an instance of a trivial derived class implementing the different valid ical types (VCALENDAR, VEVENT, etc.) and they are all spit out by a recursive factory from the common parent class:

class Component(...):
  @classmethod
  def from_ical(cls, ...)

I have created a 'CalendarFile' class that extends the ical 'Calendar' class, including in it generator function of its own:

class CalendarFile(Calendar):
  @classmethod
  def from_file(cls, ics):

which opens a file (ics) and passes it on:

     instance = cls.from_ical(f.read())

It initializes and modifies some other things in instance and then returns it. The problem is that instance ends up being a Calendar object instead of a CalendarFile object, in spite of cls being CalendarFile. Short of going into the factory function of the ical module and fiddling around in there, is there any way to essentially "recast" that object as a 'CalendarFile'?

The alternatives (again without modifying the original module) that I have considered are:

  • make the CalendarFile class a has-a Calendar class (each instance creates its own internal instance of a Calendar object), but that seems methodically stilted.
  • fiddle with the returned object to give it the methods it needs (i know there's a term for creating a customized object but it escapes me).
  • make the additional methods into functions and just have them work with instances of Calendar.
  • or perhaps the answer is that i shouldn't be trying to subclass from a module in the first place, and this type of code belongs in the module itself.

Again i'm trying to understand what the "best" approach is and also learn if i'm missing any alternatives. Thanks.

Was it helpful?

Solution

Normally, I would expect an alternative constructor defined as a classmethod to simply call the class's standard constructor, transforming the arguments that it receives into valid arguments to the standard constructor.

>>> class Toy(object):
...     def __init__(self, x):
...         self.x = abs(x)
...     def __repr__(self):
...         return 'Toy({})'.format(self.x)
...     @classmethod
...     def from_string(cls, s):
...         return cls(int(s))
... 
>>> Toy.from_string('5')
Toy(5)

In most cases, I would strongly recommend something like this approach; this is the gold standard for alternative constructors.

But this is a special case.

I've now looked over the source, and I think the best way to add a new class is to edit the module directly; otherwise, scrap inheritance and take option one (your "has-a" option). The different classes are all slightly differentiated versions of the same container class -- they shouldn't really even be separate classes. But if you want to add a new class in the idiom of the code as it it is written, you have to add a new class to the module itself. Furthermore, from_iter is deceptively named; it's not really a constructor at all. I think it should be a standalone function. It builds a whole tree of components linked together, and the code that builds the individual components is buried in a chain of calls to various factory functions that also should be standalone functions but aren't. IMO much of that code ought to live in __init__ where it would be useful to you for subclassing, but it doesn't.

Indeed, none of the subclasses of Component even add any methods. By adding methods to your subclass of Calendar, you're completely disregarding the actual idiom of the code. I don't like its idiom very much but by disregarding that idiom, you're making it even worse. If you don't want to modify the original module, then forget about inheritance here and give your object a has-a relationship to Calendar objects. Don't modify __class__; establish your own OO structure that follows standard OO practices.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top