How to set object parameters nicely?

A

allen.fowler

Hello,

I've got a bunch of code that looks something like:

class MyOb(object):
def __init__(self, p1=None, p2=None, p3=None, ...):
self.p1 = p1
self.p2 = p2
self.p3 = p3
self.pN = ...


ob1 = MyOb(p1="Tom", p3="New York")
ob2 = MyOb(p1="Joe", p2="joe@host", p3="New Jersey")

.... and so on.

This is fine for only a few parameters, but it's very ugly and a lot
of duplicate typing once I've got 10+ parameters and 5 kinds of
objects.

Is there a better way to do this?

Thanks,
:)
 
D

Diez B. Roggisch

allen.fowler said:
Hello,

I've got a bunch of code that looks something like:

class MyOb(object):
def __init__(self, p1=None, p2=None, p3=None, ...):
self.p1 = p1
self.p2 = p2
self.p3 = p3
self.pN = ...


ob1 = MyOb(p1="Tom", p3="New York")
ob2 = MyOb(p1="Joe", p2="joe@host", p3="New Jersey")

... and so on.

This is fine for only a few parameters, but it's very ugly and a lot
of duplicate typing once I've got 10+ parameters and 5 kinds of
objects.

Is there a better way to do this?

There are some tricks. Like this


def __init__(self, p1=None, ...):
d = locals()
del d["self"]
self.__dict__.update(d)


However, it looks like a code-smell for me if you have 10+ paramters.

Diez
 
M

MRAB

allen.fowler said:
Hello,

I've got a bunch of code that looks something like:

class MyOb(object):
def __init__(self, p1=None, p2=None, p3=None, ...):
self.p1 = p1
self.p2 = p2
self.p3 = p3
self.pN = ...


ob1 = MyOb(p1="Tom", p3="New York")
ob2 = MyOb(p1="Joe", p2="joe@host", p3="New Jersey")

... and so on.

This is fine for only a few parameters, but it's very ugly and a lot
of duplicate typing once I've got 10+ parameters and 5 kinds of
objects.

Is there a better way to do this?

class MyOb(object):
def __init__(self, **kwargs):
self.__dict__.update(kwargs)

ob1 = MyOb(p1="Tom", p3="New York")
ob2 = MyOb(p1="Joe", p2="joe@host", p3="New Jersey")
 
A

allen.fowler

class MyOb(object):
     def __init__(self, **kwargs):
         self.__dict__.update(kwargs)

ob1 = MyOb(p1="Tom", p3="New York")
ob2 = MyOb(p1="Joe", p2="joe@host", p3="New Jersey")

I've tried this, but have found two issues:

1) I can't set default values.
2) I can't set required values.

In both of the above cases, if the object is created without the
"exact" dict() I expect, all the assumption my methods make about what
is available in "self" fall apart.

Perhaps, as Diez mentioned, my approach is wrong. What would be the
right thing to do in this situation?

-- AF
 
D

Diez B. Roggisch

allen.fowler said:
I've tried this, but have found two issues:

1) I can't set default values.
2) I can't set required values.

In both of the above cases, if the object is created without the
"exact" dict() I expect, all the assumption my methods make about what
is available in "self" fall apart.

Perhaps, as Diez mentioned, my approach is wrong. What would be the
right thing to do in this situation?

There is no general answer to this. It depends on your actual problem.

Diez
 
A

allen.fowler

Is there a better way to do this?
There is no general answer to this. It depends on your actual problem.

Diez

What are some of the patterns that tend to be used?

-- AF
 
C

Carl Banks

What are some of the patterns that tend to be used?

For the record, I don't really agree that a lot of parameters is code
smell. It's maybe a red flag that you are doing too much in one
function and/or class, but nothing inherently shady.

One thing to ask yourself: are there a lot of combinations of
parameters that don't make sense? For example, do you have a lot of
cases where, say, if one parameter is set to x, then parameters a, b,
c, and d do nothing? That would indicate that you should break your
function/class up into smaller, more targeted parts.

However, if all your parameters are orthogonal, that is, if all or
most combinations make sense, then there's no reason ten or twenty
parameters isn't perfectly reasonable.

Whenever I have ten parameters in an __init__, I ususally just write
out the assignments, although more often than not the object's
attributes don't correspond to the parameters one-to-one, so I'd have
to write them out anyway.


Carl Banks
 
A

allen.fowler

For the record, I don't really agree that a lot of parameters is code
smell.  It's maybe a red flag that you are doing too much in one
function and/or class, but nothing inherently shady.

One thing to ask yourself: are there a lot of combinations of
parameters that don't make sense?  For example, do you have a lot of
cases where, say, if one parameter is set to x, then parameters a, b,
c, and d do nothing?  That would indicate that you should break your
function/class up into smaller, more targeted parts.

However, if all your parameters are orthogonal, that is, if all or
most combinations make sense, then there's no reason ten or twenty
parameters isn't perfectly reasonable.

Whenever I have ten parameters in an __init__, I ususally just write
out the assignments, although more often than not the object's
attributes don't correspond to the parameters one-to-one, so I'd have
to write them out anyway.

Thank you for the thoughtful insight.

In this case, and I am trying to create a number of ORM-like objects.
(Though, there is no database involved.)

So, instances of these classes are acting as records that are shuttled
around in the system, and the object's properties are acting as
values. The parameters are (mostly) orthogonal, but do need defaults,
and some must be required.
 
L

Lie Ryan

I've tried this, but have found two issues:

1) I can't set default values.
2) I can't set required values.

In both of the above cases, if the object is created without the
"exact" dict() I expect, all the assumption my methods make about what
is available in "self" fall apart.

You can inspect the dict, something like:

def __init__(self, **kwargs):
required = ['p1', 'p2', 'p3']
optional = {'p4': 'foo', 'p5': 'bar'}
if any(par not in kwargs for par in required):
raise TypeError('required parameter not set')
for par in optional:
if par not in kwargs:
kwargs[par] = optional[par]
# ... do work ...

Tips: turn this parameter checker into a @decorator

But generally, as Diez pointed out your class may be doing too much. Try
splitting it into smaller classes. Passing too much parameter is an
indication of a God Object (or demi-god)
http://en.wikipedia.org/wiki/God_object "He" just knows too much.



===

Unpythonic warning: Java-style code would use several separate
initializers that would be called in order before the object is in a
ready state (but never in an invalid state [!]). The first initializer
merely set all the attributes into some valid state. This is an
*anti-pattern* in python. Avoid it.

def __init__(self):
self.p1 = ''
self.p2 = 0
self.p3 = []
def init_name(self, name):
self.p1 = name

etc, etc...

===
 
I

inhahe

def __init__(self, required1, required2, default1='d1', default2='d2',
**kwargs):
   for par in kwargs:
     self.setattr(par, kwargs[par])
self.required1 = required1
self.required2 = required2
self.default1 = default1
self.default2 = default2

or

def __init__(self, required1, required2, default1='d1', default2='d2',
**kwargs):
   for par in kwargs:
     self.setattr(par, kwargs[par])
self.required1 = required1
self.required2 = required2
self.default1 = default1
self.default2 = default2
 
I

inhahe

def __init__(self, required1, required2, default1='d1', default2='d2',
**kwargs):
   for par in kwargs:
      self.setattr(par, kwargs[par])
  self.required1 = required1
  self.required2 = required2
  self.default1 = default1
  self.default2 = default2

or

def __init__(self, required1, required2, default1='d1', default2='d2',
**kwargs):
   for par in kwargs:
      self.setattr(par, kwargs[par])
  self.required1 = required1
  self.required2 = required2
  self.default1 = default1
  self.default2 = default2

(sorry, sent the same code twice. i was going to do it a different way
and realized it would be too magically)
 
B

Bruno Desthuilliers

allen.fowler a écrit :
(snip)
In this case, and I am trying to create a number of ORM-like objects.
(Though, there is no database involved.)

So, instances of these classes are acting as records that are shuttled
around in the system, and the object's properties are acting as
values. The parameters are (mostly) orthogonal, but do need defaults,
and some must be required.

You could specify the names, defaults and validations required at an
upper level then automate the whole thing, ie (thinking out loud):


class Field(object):
def __init__(self, name,required=False,
default=None,target=None,validate=None):
self._name = name
self._required = required
self._default = default
self._target = target or name
self._validate = validate or lambda x: x

def validate(self, value):
""" _validate is supposed to raise a ValueError if not ok.
it can also do any required conversion, formatting etc
"""
return self._validate(value)

def set(self, instance, **kw):
value = kw.get(self._name, None)
if value is None
if self.required:
raise ValueError("argument %s is required" % self._name)
else:
value = self._default
value = self.validate(value)
setattr(instance, self._target, value)


class Record(object):
def __init__(self, **kw):
if not hasattr(self, "_fields"):
raise AttributeError("Record subclasses must define _fields")
for field in self._fields:
field.set(self, **kw)

class Foo(Record):
_fields = (
Field("bar", True, validate=lambda v : v > 1),
Field("baaz", default=42)
)



NB : totally untested code, so it will of course contains at least one
major and obvious bug / typo / whatever !-)

You could go further using an even more declarative API based on a fancy
custom metaclass and descriptors (for Fields) etc etc - and that even
might be the RightThing(tm) to do if you have more than moderatly
complex needs, but for simple cases the above should work fine without
going over the top with black magic.

HTH
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,733
Messages
2,569,439
Members
44,829
Latest member
PIXThurman

Latest Threads

Top