-1

I was wondering what the best (most pythonic) solution is to pass a list or a variable number of arguments to a function/method.

For example I have to following class:

class Dataset():
def __init__(self, *args):
    self.data_set = args

and this is a part of the setter property:

@data_set.setter
def data_set(self, args):
    data = []
    for item in args:
        if type(item) is list:
            data = [k for k in item]
            break
        data.append(item)
    self._data_set = data

I searched the web, but coudn't find much on the topic. My goal here is to design the class constructor such that it works with either a list as given parameter:

ds1 = Dataset([4,3,5])

or variable number or arguments:

ds1 = Dataset(4,3,5)

So, what is the best implementation and am I on the right track?

  • 1
    I don't think this is what you want but if you put a `*` before the list you are passing (i.e: `ds1 = Dataset(*[4,3,5])` ) the list will be unpacked, and it will be the same as if you had typed: `ds1 = Dataset(4,3,5)`. Since you already are accepting *args as a parameter that's about all you have to do. If this is what you want please tell me so I can put this up as an answer for other visitors to see. –  Oct 28 '18 at 12:47
  • Accept *either* a list *or* varargs. Don't do both. – Aran-Fey Oct 28 '18 at 12:57
  • @Aran-Fey can you explain why? – Kamen Hristov Oct 28 '18 at 21:53
  • @DennisPatterson great answer, but for this particular case I think progmatico's solution answers my question best ;3 – Kamen Hristov Oct 28 '18 at 21:55
  • @KamenHristov Because it introduces ambiguity. If your function is called like `func([1, 2])`, how do you know if that means `func(1, 2)` or `func([[1, 2]])`? – Aran-Fey Oct 28 '18 at 21:56
  • @Aran-Fey I'm sorry, as a new user maybe my question isn't as well written as it can be, but I can't follow why func([1,2]) could be mistaken for func([[1,2]]) ? I've stripped the code of the exception handling for the sake of the example, but if the method is called with a two dimensional array (or more) it would raise an exception. – Kamen Hristov Oct 28 '18 at 22:26
  • If your function is called like `func(1)` or `func(1, 2)` or `func([1], [2])` then it's obvious what should happen. But if the function is called like `func([1, 2])` it's unclear - is that list supposed to be unpacked (like `func(1, 2)`) or the list supposed to be the single argument (similar to `func(1)`, except `1` is replaced with `[1, 2]`)? – Aran-Fey Oct 28 '18 at 22:28
  • @Aran-Fey I'd say that it should be unpacked because every element should be checked if it's in the appropriate format (int, float..) – Kamen Hristov Oct 28 '18 at 22:40
  • @Aran-Fey I edited my answer. – progmatico Oct 29 '18 at 17:00

1 Answers1

0

You can try this,

class Dataset():
    def __init__(self, *args):
        if isinstance(args[0], list) and len(args) == 1:
            print('Received a list')
        else:
            print('Received:', args)

obj = Dataset(1,2,3)
obj = Dataset([1,2,3], 2)
obj = Dataset([1,2,3])

ouputs:

Received: (1, 2, 3)                                                       
Received: ([1, 2, 3], 2)                                                  
Received a list 

Edit:

This code does what you want in a simple way. Nothing more. There are other ways, they don't look simple to me.

Your comment is actually an excellent question.

The problem is not in this code, but in what you are asking and the Python language. You want method overloading, and Python doesn't have it.

Testing argument types is unpythonic because Python is a dynamic typed language.You can do it, but you are restricting your functions usefulness to a specific type.

Testing the argument type also results in a typical if..elif chain.

If you are writing functions take a look at functools.singledispatch decorator. That eliminates the if..elif chain from code. Using it you can define a base function and register specific implementations for each type. Simple and readable. But this routes to the function implementation based on first argument. Now, for instance methods, this won't work because of self. You can change that, but it doesn't look simple any more.

Because Python doesn't support method/function overloading directly, what you asked is not a common pattern to use.

Now Aran Fey gives you good advice. Coding behaviour like this is uncommon in Python and actually introduces ambiguity. Your API contract becomes unclear. Should I pass a list or varargs. And why the choice? Just because you already have tuples and dicts with *args and *kwargs and want lists too? What about building the "varargs" as the list elements?

You ask for a list or a variable number of arguments, but a list has also a "variable number of arguments" in itself.

So use one or the other. If you go ahead with the initial idea, at least keep it simple like in this answer.

progmatico
  • 4,714
  • 1
  • 16
  • 27
  • 1
    Exactly what I was looking for! But I'd be very grateful if you (or someone else) could tell more about if this the most common way to accomplish this task , for example, would this be considered a good approach in a professional environment or is it unlikely? – Kamen Hristov Oct 28 '18 at 22:02
  • I edited my answer @KamenHristov, maybe good to match the requirement, but the requirement isn't a good one in Python. – progmatico Oct 29 '18 at 16:58
  • Thank you for the response, you provided just the type of information I was looking for! Definitely will look into functools.singledispatch and try to make my code as unambiguous as I can. – Kamen Hristov Oct 29 '18 at 19:14