1

My code base is in Python. Let's say I have a fairly generic class called Report. It takes a large number of parameters

class Report(object):
  def __init__(self, title, data_source, columns, format, ...many more...)

And there are many many instantiation of the Report. These instantiation are not entirely unrelated. Many reports share similar set of parameters, differ only with minor variation, like having the same data_source and columns but with a different title.

Rather than duplicating the parameters, some programming construct is applied to make expression this structure easier. And I'm trying to find some help to sort my head to identify some idiom or design pattern for this.

If a subcategory of report need some extra processing code, subclass seems to be a good choice. Say we have a subcategory of ExpenseReport.

class ExpenseReport(Report):
    def __init__(self, title, ... a small number of parameters ...)

        # some parameters are fixed, while others are specific to this instance
        super(ExpenseReport,self).__init__(
                title,
                EXPENSE_DATA_SOURCE,
                EXPENSE_COLUMNS,
                EXPENSE_FORMAT,
                ... a small number of parameters...)

    def processing(self):
        ... extra processing specific to ExpenseReport ...

But in a lot of cases, the subcategory merely fix some parameters without any extra processing. It could easily be done with partial function.

ExpenseReport = functools.partial(Report,
                        data_source = EXPENSE_DATA_SOURCE,
                        columns = EXPENSE_COLUMNS,
                        format = EXPENSE_FORMAT,
                )

And in some case, there isn't even any difference. We simply need 2 copies of the same object to be used in different environment, like to be embedded in different page.

expense_report = Report("Total Expense", EXPENSE_DATA_SOURCE, ...)
page1.add(expense_report)

...
page2.add(clone(expense_report))

And in my code base, an ugly technique is used. Because we need 2 separate instances for each page, and because we don't want to duplicate the code with long list of parameter that creates report, we just clone (deepcopy in Python) the report for page 2. Not only is the need of cloning not apparent, neglecting to clone the object and instead sharing one instance creates a lot of hidden problem and subtle bugs in our system.

Is there any guidance in this situation? Subclass, partial function or other idiom? My desire is for this construct to be light and transparent. I'm slight wary of subclassing because it is likely to result in a jungle of subclass. And it induces programmer to add special processing code like what I have in ExpenseReport. If there is a need I rather analyze the code to see if it can be generalized and push to the Report layer. So that Report becomes more expressive without needing special processing in lower layers.

Additional Info

We do use keyword parameter. The problem is more in how to manage and organize the instantiation. We have a large number of instantiation with common patterns:

expense_report = Report("Expense", data_source=EXPENSE, ..other common pattern..)
expense_report_usd = Report("USD Expense", data_source=EXPENSE, format=USD, ..other common pattern..)
expense_report_euro = Report("Euro Expense", data_source=EXPENSE, format=EURO, ..other common pattern..)
...
lot more reports 
...
page1.add(expense_report_usd)
page2.add(expense_report_usd)   # oops, page1 and page2 shared the same instance?!
...
lots of pages
...
Wai Yip Tung
  • 18,106
  • 10
  • 43
  • 47
  • About clone and it's problems. Maybe you can use copy of keyword arguments? `report_args = dict(title='Some tile', data_source=..., columns=..., format=...)` then `expense_report1 = Report(**report_args), expense_report2 = Report(**report_args)` – reclosedev Jan 16 '12 at 18:04
  • And it's not clear for me, what type of arguments do you use, but if you have method that takes large number of parameters, I think it's situation to use keyword arguments. Some good examples in [SO question](http://stackoverflow.com/q/1098549/1052325) – reclosedev Jan 16 '12 at 18:14

5 Answers5

2

Why don't you just use keyword arguments and collect them all into a dict:

class Report(object):
  def __init__(self, **params):
      self.params = params
      ...
MRAB
  • 20,356
  • 6
  • 40
  • 33
0

The question is quite old, but this might still help someone who stumbles onto it...

I made a small library called classical to simplify class inheritance cases like this (Python 3 only).

Simple example:

from classical.descriptors import ArgumentedSubclass

class CustomReport(Report):
    Expense = ArgumentedSubclass(data_source=EXPENSE, **OTHER_EXPENSE_KWARGS)
    Usd = ArgumentedSubclass(format=USD)
    Euro = ArgumentedSubclass(format=EURO)
    PatternN = ArgumentedSubclass(**PATTERN_N_KWARGS)
    PatternM = ArgumentedSubclass(**PATTERN_M_KWARGS)

# Now you can chain these in any combination (and with additional arguments):
my_report_1 = CustomReport.Expense.Usd(**kwargs)
my_report_2 = CustomReport.Expense.Euro(**kwargs)
my_report_3 = CustomReport.Expense.PatternM.PatternN(**kwargs)

In this example it's not really necessary to separate Report and CustomReport classes, but might be a good idea to keep the original class "clean".

Hope this helps :)

Grisha S
  • 818
  • 1
  • 6
  • 15
0

I see no reason why you shouldn't just use a partial function.

Marcin
  • 48,559
  • 18
  • 128
  • 201
0

If your main problem is common arguments in class constructos, possible solution is to write something like:

common_arguments = dict(arg=value, another_arg=anoter_value, ...)

expense_report = Report("Expense", data_source=EXPENSE, **common_arguments)
args_for_shared_usd_instance = dict(title="USD Expense", data_source=EXPENSE, format=USD)
args_for_shared_usd_instance.update(common_arguments)
expense_report_usd = Report(**args_for_shared_usd_instance)

page1.add(Report(**args_for_shared_usd_instance))
page2.add(Report(**args_for_shared_usd_instance))

Better naming, can make it convenient. Maybe there is better design solution.

reclosedev
  • 9,352
  • 34
  • 51
0

I found some information myself.

I. curry -- associating parameters with a function « Python recipes « ActiveState Code

http://code.activestate.com/recipes/52549-curry-associating-parameters-with-a-function/

See the entire dicussion. Nick Perkins' comment on 'Lightweight' subclasses is similar to what I've described.

II. PEP 309 -- Partial Function Application

http://www.python.org/dev/peps/pep-0309/

Wai Yip Tung
  • 18,106
  • 10
  • 43
  • 47