Python - Iterating over Arguments passed to a Function
Asked Answered
K

3

6

Suppose I have the following example:

class foo:
   ...
   def bar(self, w, x, y, z, ...):
      self.w = w
      self.x = x
      self.y = y
      self.z = z
      ...

I wish to reduce the n-number of attribute assignment lines in bar() to one assignment line set using a setattr() cycle through the arguments. Is there a good way to cycle through said arguments for this purpose?

I wish to retain the defined parameter names so as to limit the number of parameters passed to the function as well as the order in which they are passed. I also understand that functions can be handled like objects; so is it possible to obtain a list of the defined parameters as an attribute of the function and iterate through that?

Kegan answered 9/9, 2016 at 15:28 Comment(3)
The information you're looking for would be defined in the Python Language Reference - Data Model - Objects, values and types reference (version 3 reference). It does not appear that the parameter list is defined as part of a function object.Glyph
Just found this after I posted the question. Getting List of Parameter Names inside Python Function This might answer my question. Feel free to mark this question as a duplicate.Kegan
@JEarls The parameter list is not defined, but at the top of the function or method, locals() will contain only the parameters (including self if it's a method).Prolong
P
1

Use locals() and you can get all the arguments (and any other local variables):

class foo:
    def bar(self, w, x, y, z):
        argdict = {arg: locals()[arg] for arg in ('w', 'x', 'y', 'z')}
        for key, value in argdict.iteritems():
            setattr(self, key, value)
        ...

Might be possible to do it more efficiently, and you could inline argdict if you prefer less lines to readability or find it more readable that way.

Prolong answered 9/9, 2016 at 16:0 Comment(10)
So locals(), in this case would return a dictionary of the arguments passed to function bar() with the values keyed to the names? Is locals() operating only in the scale of function bar()?Kegan
Yes. More specifically, locals() returns all local variables - if you inserted the line q = 73 before calling locals(), the dict would include that pair. The dict does include self, That's why I copied it to a new dict instead of using it directly - it allows limiting to only the intended variables, even if you later need to move it down, and also means you don't try to set self.self = self which would be unhelpful at best.Prolong
Alright. So if I called argdict = locals() at the very beginning of function bar() it would only contain the elements passed to bar as arguments? That's what I'm looking for.Kegan
Almost. It would still include self if bar() is a method rather than a function (as in your example), though - self is still an argument, just not one you pass explicitly. You could more efficiently pare it down by argdict = dict(locals()); del argdict['self']. Can't directly do argdict = locals() if you want to remove something from argdict without actually removing it entirely.Prolong
It's not a proper way to hard code the name of arguments in function.Pekin
@Kasramvd How so? You have to hard-code that in any function that uses normal arguments - def echo(input): return input, for the simplest example I can think of. How would you use anything but *args (and some cases of **kwargs) conveniently without being able to assume the names you gave things?Prolong
@David Heyman OK, so here's the final result: if locals() will include self as an element, then I can translate locals() over to argdict and remove self from argdict while retaining it in locals(). Then use setattr() on argdict to finish the job.Kegan
@DavidHeyman That's exactly what you should do. If you are aware, the importance of such communities is that they are being use by another people and will be use in future, so you can't just give an improper answer because of the OP's request. Also it might not work well for OP too.Pekin
@Kasramvd : I'm sorry, are you trying to say that the use of named arguments to functions/methods is poor practice? If not, please explain, because I seem to be misunderstanding your intent.Prolong
First off you are doing an extra action by creating the argdict, while python can do that automatically at caller level. Secondly in this case you cannot use another names for your function, which even OP wants this, this makes it almost useless for most of the cases.Pekin
L
1

So you don't have to actually name the arguments explicitly use:

class foo:
    def __init__(self, w, x, y, z):
        args = locals()# gets a dictionary of all local parameters
        for argName in args:
            if argName!='self':
                setattr(self, argName, args[argName])
Lustick answered 20/12, 2017 at 15:55 Comment(0)
P
-1

The __setattr__ attribute only assigns one attribute at a time, if you want to assign multiple attribute, you can use **kwargs in your function header and for limiting the number of arguments you can simply check the length of kwargs within your function. and call the __setattr__ for each each of the arguments one by one. One good reason for this recipe is that basically assigning attribute to an object without considering anything is not a correct and desirable job, due to a lot of reasons. Thus you have to assign each attribute one at a time by considering all the required conditions.

You can also do this manually by updating the instance dictionary but you should handle the exceptions too.

In [80]: class foo:                      
    def bar(self, **kwargs):
        if len(kwargs) != 4:
            raise Exception("Please enter 4 keyword argument")
        for k, v in kwargs.items():
            foo.__setattr__(self, k, v)
   ....:             

In [81]: f = foo()

In [82]: f.bar(w=1, x=2, y=3, z=4)

In [83]: f.w
Out[83]: 1

In [84]: f.bar(w=1, x=2, y=3, z=4, r=5)
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-84-758f669d08e0> in <module>()
----> 1 f.bar(w=1, x=2, y=3, z=4, r=5)

<ipython-input-80-9e46a6a78787> in bar(self, **kwargs)
      2     def bar(self, **kwargs):
      3         if len(kwargs) != 4:
----> 4             raise Exception("Please enter 4 keyword argument")
      5         for k, v in kwargs.items():
      6             foo.__setattr__(self, k, v)

Exception: Please enter 4 keyword argument

By using __setatter__ it will take care of the exception automatically:

In [70]: f.bar(1, 2)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-70-07d1f3c9e27f> in <module>()
----> 1 f.bar(1, 2)

<ipython-input-65-1049e26120c1> in bar(self, *args)
      2     def bar(self, *args):
      3         for item in args:
----> 4             foo.__setattr__(self, item, item)
      5 

TypeError: attribute name must be string, not 'int'
Pekin answered 9/9, 2016 at 15:54 Comment(8)
This doesn't enforce the specific parameter names and quantity as requested.Prolong
@DavidHeyman I don't see such thing in question. Can you elaborate more?Pekin
First line of last paragraph as I write this: "I wish to retain the defined parameter names so as to limit the number of parameters passed to the function as well as the order in which they are passed"Prolong
Doing it with **kwargs keeps names but still doesn't enforce quantity, and loses enforcement of order.Prolong
@Kasramvd "I wish to retain the defined parameter names so as to limit the number of parameters passed to the function as well as the order in which they are passed." So, if I use *args, then function can take any quantity of arguments and assign them as attributes. I want to be able to strictly enforce a certain number of attributes (in this example, 4 attributes) to be assigned to self. And I want them to be labeled by the names provided. I suppose that **kwargs might solve the labeling problem, but not the quantity problem.Kegan
Using *args means that the method's signature no longer indicates the number of arguments or gives names that would presumably indicate their purpose. The user would have to read the implementation details of the function to know what needs to be passed, and in what order. Also, more or less things could be passed, and if done as a loop instead of manually it would accept that right up until the point when some attribute was actually used, where the original implementation would fail as soon as the method was called improperly.Prolong
@DavidHeyman I missed the restriction on the number of arguments, I just saw the order.Pekin
Down vote means there is something incorrect in this answer, is I'd be happy to hear about that, so that I can correct it.Pekin

© 2022 - 2024 — McMap. All rights reserved.