In line Conditional Expression or Function - Pythonic?

430 Views Asked by At

I have a situation where I would like to conditionally slice a string from the reported position of an '@' symbol; the condition being: slice the string if the '@' is there, else leave it untouched. I thought up two ways, one using a function, the other using an inline conditional expression. Which method is the most Pythonic?

Using a function

>>> def slice_from_at(inp):
...     res = inp.find('@')
...     if res == -1:
...         return None
...     else:
...         return res     
>>> c = 'agent_address@agent_address'
>>> c[:slice_from_at(c)]
... 'agent_address'

Using an inline conditional expression

>>> c = 'agent_address@agent_address'
>>> c[:None if c.find('@') == -1 else c.find('@')]
... 'agent_address'

Although using the inline conditional expression is more terse and, some may argue more economical - is the function method is more Pythonic because it more readable?

6

There are 6 best solutions below

1
On BEST ANSWER

Not only is a function more readable, it is also reusable.

The inline expression may call c.find('@') twice, which is inefficient.

As Useless has mentioned in the comments, there are already built in functions to do this; you really don't need to define your own function in this case:

agent,_,address = c.partition('@')

By the way, a callback is a function that is passed in as an argument and called later. You don't have a callback since it is not being called later. I think it should just be called a function.

6
On

How about this instead?

c.split('@')[0]
4
On

Most Pythonic?

Don't reinvent the wheel, use str.partition()

def slice_from_at(inp):
    if '@' in inp:
        return inp.partition('@')[2]
    return inp

If you're more concerned about speed than readability, try str.rsplit():

def slice_from_at(inp):
    return inp.rsplit('@', 1)[-1]

Neither of your examples includes a "callback". Nor should they.

A well-named function that does one thing and does that one thing well is about as Pythonic as it gets. If it's backed-up with unit tests, so much the better.

0
On

The stylistic reason that [existing, but you could have wrote them yourself] functions like .partition() or .rsplit() are cleaner than c[:slice_from_at(c)] that the API of slice_from_at() is low-level: it returns an index into the string that it was given, but that index only makes sense to cut the string, so why not return the desired fragment(s) of the string directly?

Generally, indexes into sequences are considered unpythonic when a higher-level alternative exists, as evidenced by the growing amount of built-in helpers that reduce exposure to indexes. Particular examples that come to mind:

  • zip() and enumerate() reducing need for for i in range(len(seq)): loops.
  • substring in string, str.split(), then str.partition().
0
On

I tend to prefer string methods when they do the job easily, but for completeness, regular expressions should be mentioned:

re.sub('^.*@', '', c)
1
On

I code like this:

if '@' in c:
    c = c[:c.find('@')]

I don't move 2 lines of code in to separate function in most of cases.