简体   繁体   中英

Pythonic way to assign the parameter into attribute?

The sample codes are like this:

def assign(self, input=None, output=None, param=None, p1=None, p2=None):
    if input:
        self.input = input
    if output:
        self.output = output
    if param:
        self.param = param
    if p1:
        self.p1 = p1
    if p2:
        self.p2 = p2

Though this looks very clear, it suffers if there're 10 parameters for this function. Does anyone have ideas about a more convinient way for this?

you can do something like:

def assign(self,**kwargs):
    for k,v in kwargs.items():
        if v:
           setattr(self,k,v)

This is quite simple and suitable for many situations. If you want to maintain a set of keywords which you'll accept and raise TypeError for the rest:

#python2.7 and newer
def assign(self,allowed_kwargs={'foo','bar','baz'},**kwargs):
    if kwargs.keysview() - allowed_kwargs:
        raise TypeError('useful message here...')
    for k in allowed_kwargs:
        setattr(self,k,kwargs[k])

This is somewhat inspect-able as well since the user will see the set of allowed kwargs.

Explicit is better than implicit

def assign(self, input=None, output=None, param=None, p1=None, p2=None):

has many advantages over

def assign(self, **kwargs)
  • It is self-documenting
  • A helpful TypeError is raised if an invalid parameter is passed to assign .
  • assign can be called with positional as well as keyword arguments

To its credit, the code the OP posted is entirely explicit, though the if -statements are monotonous. To cut down on the monotony, you could use something like this:

class Foo(object):
    def assign(self, input=None, output=None, param=None, p1=None, p2=None):
        for name in 'input output param p1 p2'.split():
            val = vars()[name]
            if val is not None:
                setattr(self, name, val)

foo = Foo()
foo.assign(p1=123, p2='abc')

One of the great things about python is its interactive interpreter. When you write code like this:

>>> def assign(self, input=None, output=None, param=None, p1=None, p2=None):
...     pass
... 

Its quite easy to figure out how you're supposed to use the function:

>>> help(assign)
Python Library Documentation: function assign in module __main__

assign(self, input=None, output=None, param=None, p1=None, p2=None)

By comparison:

>>> def assign2(self, **kwargs):
...     pass
... 

gives:

>>> help(assign2)
Python Library Documentation: function assign2 in module __main__

assign2(self, **kwargs)

I hope you understand why the first form is preferrable. But you still want to avoid writing everything twice (in the arguments and in the body).

The first question is why are you writing code of this nature? I find it a very common case that I want a class with a heap of attributes that it will carry around, but the set of those attributes is essentially fixed. In the very most common case, those attributes never change for the lifetime of the object; in which case python has a built in helper for exactly that!

>>> import collections
>>> Assignment = collections.namedtuple('Assignment', 'input output param p1 p2')
>>> assign = Assignment(None, None, None, None, None)._replace
>>> assign(p1=10)
Assignment(input=None, output=None, param=None, p1=10, p2=None)
>>> help(Assignment)
Python Library Documentation: class Assignment in module __main__

class Assignment(__builtin__.tuple)
 |  Assignment(input, output, param, p1, p2)
 |  
... SNIP

namedtuple 's are regular classes, you can inherit from them to give them special behaviours. they are, unfortunately, immutable, and if you happen to need that, you will need another technique; but you should almost always reach for named tuple first. Otherwise, we can make use of some other magic; we can get all of the local variables, which at function start, includes only the arguments:

>>> class Assignable(object):
...     def assign(self, input=None, output=None, param=None, p1=None, p2=None):
...         _kwargs = vars()
...         _kwargs.pop('self')
...         vars(self).update((attr, value) for attr, value in _kwargs.items() if value is not None)
... 
>>> a = Assignable()
>>> vars(a)
{}
>>> a.assign(p1=6)
>>> vars(a)
{'p1': 6}
>>> a.p1
6

and the help() text is still very helpful!

>>> help(a.assign)
Python Library Documentation: method assign in module __main__

assign(self, input=None, output=None, param=None, p1=None, p2=None) method of __main__.Assignable instance

With mgilson's and unutbu's solutions, the possibility of writing all types of calls is lost.

In my following solution, this possibility is preserved:

class Buu(object):
    def assign(self,
               input=None, output=None,
               param=None,
               p1=None, p2=None,
               **kw):
        for k,v in locals().iteritems():
            if v not in (self,None,kw):
                if v == (None,):
                    setattr(self,k,None)
                else:
                    setattr(self,k,v)
        for k,v in kw.iteritems():
            setattr(self,k,v)

buu = Buu()

buu.assign(None,(None,), p1=[])
print "buu's attributes:",vars(buu)
print

buu.assign(1,p2 = 555, xx = 'extra')
print "buu's attributes:",vars(buu)

result

buu's attributes: {'output': None, 'p1': []}

buu's attributes: {'p2': 555, 'output': None, 'xx': 'extra', 'p1': [], 'input': 1}

By the way, when a coder puts None as default argument for a parameter, it's because he foresees that there will be no case where it will be necessary to pass None as a significant arguments that would really be of influence during the excution.
Hence I think that the point of passing None as real significant argument is a false problem.
However , in the above code, this problem is skirted by using the convention that in case a new attribute with value None must be created, the argument should be (None,) .

If someone wants to pass (None,) ???
Seriously ?
Oh heck!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM