3

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?

Raz
  • 595
  • 4
  • 16

6 Answers6

5

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.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
3

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.

johnsyweb
  • 136,902
  • 23
  • 188
  • 247
  • Thanks, although I accepted @unutbu's answer because it was a question about style, the use of str.partition is really neat! – Raz Nov 07 '11 at 10:14
  • My first instinct was to use `str.partition()`, but I think `str.rsplit()` is probably better since it does the same thing for you but without the initial search for the `@`. – johnsyweb Nov 07 '11 at 10:16
  • 1
    That conditional logic for `partition` isn't needed at all, because there is also `rpartition`. `inp.rpartition('@')[2]` works fine. – Karl Knechtel Nov 07 '11 at 11:08
  • @KarlKnechtel: I'm kicking myself that I didn't think of that! – johnsyweb Nov 08 '11 at 00:32
0

How about this instead?

c.split('@')[0]
Marcelo Cantos
  • 181,030
  • 38
  • 327
  • 365
0

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().
Beni Cherniavsky-Paskin
  • 9,483
  • 2
  • 50
  • 58
0

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

re.sub('^.*@', '', c)
Beni Cherniavsky-Paskin
  • 9,483
  • 2
  • 50
  • 58
0

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.

Ski
  • 14,197
  • 3
  • 54
  • 64
  • no sense worrying about which if-statement you should use when it turns out its not needed. Cutting code out is the most pythonic thing to do, `c = c[:c.find('@')]` works on its own. – Conrad.Dean Nov 09 '11 at 06:14