Configuring an object via a dictionary

dn PythonList at DancesWithMice.info
Fri Mar 15 18:23:38 EDT 2024


On 15/03/24 22:30, Loris Bennett via Python-list wrote:
> Hi,
> 
> I am initialising an object via the following:
> 
>      def __init__(self, config):
> 
>          self.connection = None
> 
>          self.source_name = config['source_name']
>          self.server_host = config['server_host']
>          self.server_port = config['server_port']
>          self.user_base = config['user_base']
>          self.user_identifier = config['user_identifier']
>          self.group_base = config['group_base']
>          self.group_identifier = config['group_identifier']
>          self.owner_base = config['owner_base']
> 
> However, some entries in the configuration might be missing.  What is
> the best way of dealing with this?

How do you define "missing"?

Thus, @Thomas' suggestion may/not apply. It is neat and easy.

I usually plump for:

self.source_name = config[ "source_name" ] or default_value

but @Grant's warning applies!
(which is why the pythonic way is to use (and test for) None as a 
definition of "missing" (see also impacts of using default values and 
mutable data-structures)


LBYL cf EAFP:
When setting-up an environment like this, elements are often set to a 
default-value, and then user-settings, command-line arguments, and the 
like, applied in a priority-order sequence, amend as-appropriate. In 
this way, such problems will never arise.

This is the better course 90% of the time.


Which raises the next question:

What is the impact if some attribute is "missing"? ie if the 'whatever' 
must be identified by source_name (for example), then there is little 
point in assigning any values to the new class. Considerations which 
apply *after* this question, the __init__(), are at least 
equally-important considerations (see below)!


> I could of course simply test each element of the dictionary before
> trying to use.  I could also just write
> 
>         self.config = config
> 
> but then addressing the elements will add more clutter to the code.

By which you mean that such "clutter" should only appear in the 
__init__() - which is not such a bad idea (but see below).

OTOH it is often helpful to one's comprehension to be given a prompt as 
to the source of the data. Thus, in later processing *config[ 
"source_name" ] may add a small amount of extra information to the 
reader, over self.source_name.

* maybe prepended "self.", or not...


Am assuming that passing all eight elements as individual arguments is 
off-the-table, embodying far too many 'negatives' (see below).


> However, with a view to asking forgiveness rather than
> permission, is there some simple way just to assign the dictionary
> elements which do in fact exist to self-variables?

Assuming config is a dict:

	self.__dict__.update( config )

will work, but attracts similar criticism - if not "clutter" then an 
unnecessary view (and understanding) of the workings of classes 
under-the-hood.


Another question:
When these values are used, are they all used at the same time, and 
never again? It may only be worth 'breaking-out' and making attributes 
from those which are used in multiple situations within the class's 
methods. If, the other extreme, they are only (effectively) passed from 
the __init__() to some sort of open() method, then pass the 
data-structure as an whole and delegate/remove the "clutter" to there. 
In that scenario, such detail would *only* has meaning *and* purpose in 
the open() method and thus no point in cluttering-up the __init__() with 
detail that is only required elsewhere!


> Or should I be doing this completely differently?

YMMV, but I prefer the idea of transferring the environment/config as a 
whole (as above).

If it were a class (cf the supposed dict) then "clutter" is reduced by 
eschewing brackets and quotation-marks, eg "config.source_name".


If those eight-elements are not the entirety of that data-structure, 
then consider creating an interface-class, which is extracted (or built 
that way) from some wider 'environment', and used to set-up access to 
data-source(s) or whatever. Again, this aids understanding in knowing 
where data has been created/gathered, and improves confidence when 
utilising it down-the-line.

The other principle in only providing the required (eight) items of 
data, is that the 'receiving-class' needs no understanding of the 
structure or workings of the 'sending-class' = separation of concerns, 
and each class has single purpose/reason to change.


A variation on that might be to use a method/function as the interface:

	access = Access( config.access_data )

Thus, the config class (instance) will need an access_data method to 
collate the data-items. The complimentary code in this Access.__init__( 
self, config, ) might be something like:

	(
		self.source_name,
	        self.server_host,
         	self.server_port,
	        self.user_base,
         	self.user_identifier,
	        self.group_base,
         	self.group_identifier,
	        self.owner_base = config_access()
	)

If you know my style/preferences, notice that I'm breaking my own 'rule' 
of using named-parameters in preference to positional-parameters when 
there are three or more. However, this *may* be one of those exceptions 
(cf hobgoblins). That said, this is the third and least-preferred idea!

-- 
Regards,
=dn


More information about the Python-list mailing list