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

The goal of this recipe is to help the use of classes that need a great number of options in constructor for configuration.

Python, 39 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
class OptionError (AttributeError):	
    pass
	
class OptionsUser:	
	""" This class is intended to be used as a base class for class that	need to use Options"""
	def initOptions (self,option, kw):
		"""Method intended to be called from the derived class constructor.
		   Put the options into object scope."""
		for k,v in option.__dict__.items()+kw.items():
			if not hasattr(self.__class__,k):
				raise OptionError,"invalid option "+k
			setattr(self,k,v)
	
	def reconfigure(self,option=Options(), **kw):
		""" Public member that should be used to change options during object life"""
		self.InitOptions(option,kw)
		self.onReconfigure(self)
		
	def onReconfigure(self):
		""" Public member intended to be overloaded by derived class. Called by
		    reconfigure method but can also be called from the output in case of
		    direct access to options attributs"""
		pass
		

class Options:
	def __init__(self, **kw):
		self.__dict__.update(kw)
		
	def __lshift__(self,other):
		"""overloading operator """
		s = self.__copy__()
		s.__dict__.update(other.__dict__)
		return s
		
	def __copy__(self):
		s = self.__class__()
		s.__dict__ = self.__dict__.copy()
		return s
			

Motivation Let's start with a sample: class TextBlock: def __init__ (self, font='Times', size=14, color=(0,0,0), height=0, width=0, align='LEFT', lmargin=1, rmargin=1) ....

If you have to instanciate several objects with the sames values for parameter, your first idea could be to repeat each time this values: block1 = TextBlock(font='Arial', size=10, color=(1,0,0), height=20, width=200) block2 = TextBlock(font='Arial', size=10, color=(1,0,0), height=80, width=100) block3 = TextBlock(font='Courrier',size=12, height=80,width=100)

But this isn't really good because you duplicate code. So you can create an inherited class wich fix (font, size and color) or (height and width) but this become really hard if you want to mix the two: For instance block2 have same height and same width than block3 but same font,size and color than block1, how to avoid repetition?

With Options recipe you can do that: stdBlockOptions = Options(font='Arial', size=10, color=(1,0,0), height=80, width=100) block1 = TextBlock(stdBlockOptions, height=20, width=200) block2 = TextBlock(stdBlockOptions) block3 = TextBlock(stdBlockOptions, font='Courrier', size=12)

This is quite like stylesheet in text processors: you can change one caracteristic for yours objects whithout having to copy this change in all yours declarations.

The module allow you to specialize options, for sample if you have many TextBlock to instanciate in Courier size 12 you can create: courrierBlockOptions = stdBlockOptions << Options(font='Courrier', size=12) Then any changes to the definition of stdBlockOptions will change courrierBlockOptions except for size and font which are specialized in courrierBlockOptions.

For creating a class that accept Options objects - First, your class must inherit from the OptionsUser class. - Second, you should define default values of options as static members. - Third, the contructor of your class should call the initOptions method

sample: class MyClass (OptionsUser): #options specification (default values) length = 10 width = 20 color = (0,0,0) xmargin = 1 ymargin = 1

 def __init__ (self, opt=Options(), **kw):
     """ class constructor """
     self.initOptions(opt,kw)

The constructor idioms is intended to provide backward compatibiltie and ease of use for your class: - The specification of an Options object is optionnal - The user can specify options in the constructor even if a Options object is specified. They will overload the content of the Options object.

You should of course adapt it, if your constructor need parameters wich can't be sent as options. For a class related to Tkinter GUI, you probably would have: def __init__ (self,parentFrame, opt=Options(), **kw):

If you have many classes whith the same default options, you should use derivation:

class MyDefaultOptions(OptionUser): #options specification (default values) length=10 width=20 color=(0,0,0)

class MyClass(MyDefaultOptions): #options specification (specific options or other default values) color=(1,0,0) xmargin = 1 ymargin = 1

def __init__ (self, opt=Options(), **kw):
   """ class constructor """
   self.initOptions(opt,kw)

To change object options at runtime, you have two possibility: - direct access to options : object.option = value - use the reconfigure method: object.reconfigure(option=value) The reconfigure method is defined in OptionsUser class and accept both an Options object and/or nammed parameters.

To detect change of an option at runtime: -The reconfigure method call the onReconfigure method. You can overload it in your classes. -Direct access can't be directy handled. You can ask for your user to call the onReconfigure method to signal option change.

Design discussion:

Why to use << operator for options overloading? First of all I wanted escape the problems caused by collision between a method name and an option in the class Options. So standards methods name was forbiden. Then I had two solutions: using an operator or using an external function. I decided to use operator. My first goal was to use the + operator. But when I started to deal with it, I discovered that it was a mistake because option overloading isn't a commutative operation. That's why I decided to use the << operator: - It's quite unused in Python - It's standard meaning isn't commutative - I found that it's picture fit quite well the option overloading notion

Why to put options in class scope? This practice had the great benefice to improve default options specification. I haven't found an non ugly implementations for that whithout putting option in class scope. And this permite direct access to option.

Why to use setattr and not copying directly __dict__ in the initOptions methode? This choice would of course greatly improve performances. But a class would emulate an option whith __getattr__ and __setattr__ methods. And I have the secret hope that we will have magic accessors like __get_data__ and __set_data__ for x.data in a future versions of Python. All of this couldn't work with a __dict__.update().

Why raising an exception when an option hasn't a default value? When I first started to test an Options module, I used 'size' instead of 'length' as label for an option. Of course everything worked well, except that my length option wasn't initialized. It taken to me a quite long time to found the mistake and I wonder what could happen if the same mistake was done in a hight hierarchie of options with many overloading. So I think that it is important to check for this.

Created by Sébastien Keim on Thu, 20 Sep 2001 (PSF)
Python recipes (4591)
Sébastien Keim's recipes (24)

Required Modules

  • (none specified)

Other Information and Tasks