5

I have already checked this post and this post, but couldn't find a good way of sorting the problem of my code out.

I have a code as follows:

class foo:
    def __init__(self, foo_list, str1, str2):

        self.foo_list = foo_list
        self.str1 = str1
        self.str2 = str2

    def fun(self, l=None, s1=None, s2=None):

        if l is None:
            l = self.foo_list

        if s1 is None:
            s1 = self.str1

        if s2 is None:
            s2 = self.str2

        result_list = [pow(i, 2) for i in l]

        return result_list, s1[-1], len(s2)

Then I create "f" and call "fun" function:

f = foo([1, 2, 3, 4], "March", "June")
print(f.fun())

The output is:

([1, 4, 9, 16], 'h', 4)

which is correct, but if I do:

print(f.fun("April"))

I get the following error:

TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'

Apparently, python confuses the string argument "April" with the list, how do I fix it?

Coder
  • 306
  • 3
  • 9
  • 4
    Try `print(f.fun(s1="April"))` – Mike67 Nov 06 '20 at 23:59
  • 2
    If you don't name the argument when calling, it assigns it to the parameter positionally. The first unnamed argument is `l`, next is `s1`, next is `s2`. – Barmar Nov 07 '20 at 00:03
  • 2
    `f.fun("April")` is equivalent to `f.fun(l="April")`, because you are passing it as a positional arugment, so it gets passed positionally. Use the named argument to pass what you want specifically – juanpa.arrivillaga Nov 07 '20 at 00:04
  • You can pass arguments using a dictionary with a parameter like `**mydict` - and the dictionary keys must match named parameters of the function. It’s documented search for `**` in the Python 3 documentation. – barny Nov 07 '20 at 00:20
  • 1
    A cleaner way to do the `if l is None:` thing is a single line `l=l or self.foo_list` – barny Nov 07 '20 at 00:23

1 Answers1

5

By default, the first argument passed to the function will be assigned to the first parameter. If you want to assign the first argument to the second (or n:th) parameter, you must give it as keyword argument. See, for example

In [19]: def myfunc(x='X', y=5):
    ...:     print(x,y)
    ...:
    ...:

# No arguments -> Using default parameters
In [20]: myfunc()
X 5

# Only one positional argument -> Assigned to the first parameter, which is x
In [21]: myfunc(100)
100 5

# One keyword argument -> Assigned by name to parameter y
In [22]: myfunc(y=100)
X 100

The type of the arguments do not matter, but the order you used in the function definition.

Notes on terminology

  • By parameter, I mean the variable in the function definition
  • By argument, I mean the actual value passed to the function.
np8
  • 14,736
  • 8
  • 50
  • 67
  • Thanks, apart from naming the argument when calling, is there any other way rather than using _None_ in the fun signature for this situations? – Coder Nov 07 '20 at 00:10
  • Is there any other way to do what? – np8 Nov 07 '20 at 00:11
  • To call a function with multiple optional arguments of different types? – Coder Nov 07 '20 at 00:11
  • No you’ll have to name them. It’s nothing to do with types. – barny Nov 07 '20 at 00:15
  • I'm not sure if I understand you correctly, but you can always call function with multiple optional arguments of different types, even if the default value would not be `None`. As you see, in my example, the `myfunc` has default values `x='X'` and `y=5`. Also, the type of the default arguments really does not matter at all: I can still call `myfunc(x=100)`, even if the default argument is a string. – np8 Nov 07 '20 at 00:16
  • @barny Are we talking about the same things when using word "parameter"? I updated the answer with the terminology. See also [What's the difference between an argument and a parameter?](https://stackoverflow.com/questions/156767/whats-the-difference-between-an-argument-and-a-parameter) – np8 Nov 07 '20 at 00:22
  • Sorry yes meant argument. But your ‘by default’ isn’t right - this can’t be changed – barny Nov 07 '20 at 00:23
  • @np8 “by default” implies this can be changed: no: if arguments aren’t named they __will always__ be assigned positionally, which is what your answer shows further down – barny Nov 07 '20 at 00:25
  • @barny I agree with you. Only way to deviate from the default is to use keyword (named) arguments. – np8 Nov 07 '20 at 00:47