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?

Community
  • 1
  • 1
S. Gamgee
  • 501
  • 5
  • 16
  • The information you're looking for would be defined in the [Python Language Reference - Data Model - Objects, values and types](https://docs.python.org/2/reference/datamodel.html#objects-values-and-types) reference ([version 3 reference](https://docs.python.org/3/reference/datamodel.html#objects-values-and-types)). It does not appear that the parameter list is defined as part of a function object. – J Earls Sep 09 '16 at 15:48
  • 1
    Just found this after I posted the question. [Getting List of Parameter Names inside Python Function](http://stackoverflow.com/questions/582056/getting-list-of-parameter-names-inside-python-function) This might answer my question. Feel free to mark this question as a duplicate. – S. Gamgee Sep 09 '16 at 15:58
  • @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). – Vivian Sep 09 '16 at 16:02

3 Answers3

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.

Vivian
  • 1,539
  • 14
  • 38
  • 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()`? – S. Gamgee Sep 09 '16 at 16:14
  • 1
    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. – Vivian Sep 09 '16 at 16:17
  • 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. – S. Gamgee Sep 09 '16 at 16:21
  • 1
    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. – Vivian Sep 09 '16 at 16:23
  • It's not a proper way to hard code the name of arguments in function. – Mazdak Sep 09 '16 at 16:27
  • @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? – Vivian Sep 09 '16 at 16:31
  • @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. – S. Gamgee Sep 09 '16 at 16:32
  • @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. – Mazdak Sep 09 '16 at 16:34
  • @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. – Vivian Sep 09 '16 at 16:36
  • 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. – Mazdak Sep 09 '16 at 16:40
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])
Pavel Komarov
  • 1,153
  • 12
  • 26
-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'
Mazdak
  • 105,000
  • 18
  • 159
  • 188
  • This doesn't enforce the specific parameter names and quantity as requested. – Vivian Sep 09 '16 at 16:01
  • @DavidHeyman I don't see such thing in question. Can you elaborate more? – Mazdak Sep 09 '16 at 16:04
  • 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" – Vivian Sep 09 '16 at 16:07
  • Doing it with `**kwargs` keeps names but still doesn't enforce quantity, and loses enforcement of order. – Vivian Sep 09 '16 at 16:08
  • 1
    @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. – S. Gamgee Sep 09 '16 at 16:10
  • 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. – Vivian Sep 09 '16 at 16:14
  • @DavidHeyman I missed the restriction on the number of arguments, I just saw the order. – Mazdak Sep 09 '16 at 16:24
  • 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. – Mazdak Sep 09 '16 at 16:29