0

I have the following problem: There is a function to which I have to pass some arguments. These arguments are typically stored in tuples and I'm using *args to receive the arguments. What is the most elegant way to check if the original argument was just a string? The problem with the code below is that every character is individually captured by *args because the () do not create a tuple for the 2nd entry.

def fun(*args):
    for arg in args:
        print(arg)


if __name__ == "__main__":
    l = [(0, 1),
         ("some string"), ]
    for i in l:
        fun(*i)
        print('---')

# should return:
# 0
# 1
# ---
# "some string
# ---

one solution would be to simply add a , after ("some string",) but that is easy to forget if done manually and the argument list relies on external tools and software beyond my control. Is there a better way to do this?

user7431005
  • 3,899
  • 4
  • 22
  • 49
  • 4
    *"one solution would be to simply add a , after ("some string",) but that is easy to forget if done manually "* -- so your question is "How to write code that also works if somebody [else] inserts incorrect code"? – user2390182 Feb 18 '21 at 15:05
  • The function will work perfectly fine if you do `fun("some string")`. The problem is with how you call it. One silly solution is to do `if len(i) == 1: i = tuple(i)` – Tomerikoo Feb 18 '21 at 15:09
  • @schwobaseggl that is true :-) – user7431005 Feb 18 '21 at 15:12
  • 2
    By the way your question wrongly focuses on strings. You will have a problem for any single, non-iterable argument passed as `i` – Tomerikoo Feb 18 '21 at 15:12
  • @Tomerikoo I'm not sure that'll work. both `(0, 1)` and `"some string"` don't have a length of 1, so the if statement will do nothing. – Roy Cohen Feb 18 '21 at 15:13
  • @RoyCohen Very true! I was testing you guys... ;) – Tomerikoo Feb 18 '21 at 15:14
  • In your calling code, if you do `fun(i)` instead of unpacking with `fun(*i)`, you will get your expected output. – Basil Feb 18 '21 at 15:18

5 Answers5

0

Use isinstance(). For example:

if isinstance(var, str):
   # do something
Ronie Martinez
  • 1,254
  • 1
  • 10
  • 14
0

If you are generating the tuple dynamically from outside software, it shouldn't have the ("abc") is expression, but ("abc",) is tuple problem. If it is unavoidable though, you can try using this

import functools

def fun(*args):
    if functools.reduce((lambda a,b: a and type(b) == str and len(b) == 1),args,"init string"):
        # It was most likely a string they passed in
        print("".join(args))
    else:
        for arg in args:
            print(arg)
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
Zachiah
  • 1,750
  • 7
  • 28
  • Thanks for editing @Tomerikoo I've been doing to much javascript that's why I had the "threequals" – Zachiah Feb 18 '21 at 15:20
  • No problem, try to always test your code before posting, even if it's small and simple – Tomerikoo Feb 18 '21 at 15:21
  • 1
    Maybe I missunderstood the purpse of the `functools.reduce` call, but couldn't it be replaced by `all`? Something like: `all(isinstance(arg, str) and len(arg) == 1 for arg in args)` – Roy Cohen Feb 18 '21 at 15:22
  • Yes you are probably right @Roy Cohen I didn't even know there was an ```all``` function. Good to know! – Zachiah Feb 18 '21 at 15:23
0

As noted by @Tomerikoo, this problem is not exclusive for strings, in fact, it is (in my opinion) better to check if the argument is a tuple and act differently, then check if it's a string and act differently.

if __name__ == "__main__":
    l = [(0, 1),
         ("some string"), ]
    for i in l:
        if isinstance(i, tuple):
            fun(*i)
        else:
            fun(i)
        print('---')
Roy Cohen
  • 1,540
  • 1
  • 5
  • 22
  • See [In Python, how do I determine if an object is iterable?](https://stackoverflow.com/questions/1952464/in-python-how-do-i-determine-if-an-object-is-iterable) to check for any iterable and not just `tuple`s – Tomerikoo Feb 18 '21 at 15:20
  • @Tomerikoo The problem is that a string is an iterable, so we need to check if the argument is iterable but not a string: `isinstance(i, collections.abc.Iterable) and not isinstance(i, str)`. But this will fail if there's another iterable values we don't want to treat as iterable (I can't think of anything right now but assuming there isn't is prone to errors). – Roy Cohen Feb 18 '21 at 15:28
0
def fun(*args):
    for arg in args:
        print(arg)


if __name__ == "__main__":
    l = [(0, 1),
         ("some string"), ]
    for i in l:
        fun(*i)
        print('---')

Output:

0
1
---
s
o
m
e

s
t
r
i
n
g
---

Not

0
1
---
"some string
---

Because along the way the string was also unpacked by *i. But if you want to print it as you mentioned, you can make use of isinstance() method to check the type. i.e

if __name__ == "__main__":
    l = [(0, 1),
         ("some string"), ]
    for i in l:
        if not isinstance(i, tuple):
            fun(i)
        else:
            fun(*i)
        print('---')
Comsavvy
  • 630
  • 9
  • 18
0

I propose to reformat the list, so all elements in there are tuples. Like this:

l = [(0, 123), ("some string")]
new_l = list(map(lambda x: tuple([x]) if not isinstance(x, tuple) else x, l))

Result:

>>> print(new_l)
[(0, 123), ('some string',)]

So now the items in the list are as you want to pass them to your function

Aelius
  • 1,029
  • 11
  • 22