How to set object parameters nicely?

Discussion in 'Python' started by allen.fowler, Dec 1, 2009.

  1. allen.fowler

    allen.fowler Guest

    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,
    :)
    allen.fowler, Dec 1, 2009
    #1
    1. Advertising

  2. allen.fowler schrieb:
    > 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
    Diez B. Roggisch, Dec 1, 2009
    #2
    1. Advertising

  3. allen.fowler

    MRAB Guest

    allen.fowler wrote:
    > 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")
    MRAB, Dec 1, 2009
    #3
  4. allen.fowler

    allen.fowler Guest


    > > 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")


    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
    allen.fowler, Dec 1, 2009
    #4
  5. allen.fowler schrieb:
    >>> 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")

    >
    > 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
    Diez B. Roggisch, Dec 1, 2009
    #5
  6. allen.fowler

    allen.fowler Guest

    > >>> 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")

    >
    > > 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 theobjectis 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


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

    -- AF
    allen.fowler, Dec 2, 2009
    #6
  7. allen.fowler

    Carl Banks Guest

    On Dec 2, 12:13 pm, "allen.fowler" <> wrote:
    > > >>> 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")

    >
    > > > 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 theobjectis 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.

    >
    > 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
    Carl Banks, Dec 2, 2009
    #7
  8. allen.fowler

    allen.fowler Guest

    On Dec 2, 6:36 pm, Carl Banks <> wrote:

    > 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.
    allen.fowler, Dec 3, 2009
    #8
  9. allen.fowler

    Lie Ryan Guest

    On 12/2/2009 10:26 AM, allen.fowler wrote:
    > 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...

    ===
    Lie Ryan, Dec 3, 2009
    #9
  10. allen.fowler

    inhahe Guest

    On Thu, Dec 3, 2009 at 9:16 AM, Lie Ryan <> wrote:
    > On 12/2/2009 10:26 AM, allen.fowler wrote:
    >>
    >> 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.
    >>

    >


    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
    inhahe, Dec 3, 2009
    #10
  11. allen.fowler

    inhahe Guest

    On Thu, Dec 3, 2009 at 9:32 AM, inhahe <> wrote:
    > On Thu, Dec 3, 2009 at 9:16 AM, Lie Ryan <> wrote:
    >> On 12/2/2009 10:26 AM, allen.fowler wrote:
    >>>
    >>> 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.
    >>>

    >>

    >
    > 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)
    inhahe, Dec 3, 2009
    #11
  12. 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
    Bruno Desthuilliers, Dec 3, 2009
    #12
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Rob Meade

    Expire page, and display nicely

    Rob Meade, Jan 26, 2004, in forum: ASP .Net
    Replies:
    0
    Views:
    360
    Rob Meade
    Jan 26, 2004
  2. Daniel

    print out graph nicely?

    Daniel, Jun 9, 2004, in forum: ASP .Net
    Replies:
    3
    Views:
    415
    Steven Cheng[MSFT]
    Jun 15, 2004
  3. =?Utf-8?B?QXNoYQ==?=

    how to dispose and object nicely.

    =?Utf-8?B?QXNoYQ==?=, Mar 1, 2005, in forum: ASP .Net
    Replies:
    1
    Views:
    361
    Mark Fitzpatrick
    Mar 1, 2005
  4. =?Utf-8?B?TWlrZQ==?=
    Replies:
    0
    Views:
    956
    =?Utf-8?B?TWlrZQ==?=
    Aug 31, 2005
  5. Simon Andrews

    Waiting (nicely!) for a thread

    Simon Andrews, Aug 15, 2005, in forum: Java
    Replies:
    7
    Views:
    720
    Thomas G. Marshall
    Aug 16, 2005
Loading...

Share This Page