3

I need to append all elements in list_ to a string; at the end I need to add a suffix. A dot '.' has to separate all elements:

list_ = args[f(n) : f(n+1)]
if list_:
  string += '.' + '.'.join(list_) + '.' + suffix # works except when list_ is empty
else:
  string += '.' + suffix
# list_ isn't used after this

Can I rewrite it in a simpler way in one line? If join added a separator after each element, it would just this:

string += '.' + '.'.join(args[f(n) : f(n+1)]) + '.' + suffix

Edit

I just learned that:

Slices are copies even if they are never assigned to: Does Python do slice-by-reference on strings?

But islice may be even worse since it iterates through the start of the list: itertools.islice compared to list slice

Some alternatives are discussed here: Avoiding unnecessary slice copying in Python

Community
  • 1
  • 1
max
  • 49,282
  • 56
  • 208
  • 355

4 Answers4

5

I would go with this (updated to reflect edits to the question):

'.'.join([''] + args[f(n):f(n+1)] + [suffix])

EDIT: taking inspiration from sblom's and unutbu's answers, I might actually do this:

from itertools import chain, islice
string = '.'.join(chain([string],  islice(args, f(n), f(n+1)), [suffix]))

if I were concerned about the memory cost of slicing args. You can use a tuple (string,) or a list [string] for the string and the suffix; given that they're one element each, there's no significant difference in memory usage or execution time, and since they're not being stored, you don't have to worry about mutability. I find the list syntax a little cleaner.

However: I'm not sure if Python actually creates a new list object for a slice that is only going to be used, not assigned to. If it doesn't, then given that args is a proper list, using islice over [f(n):f(n+1)]doesn't save you much of anything, and in that case I'd just go with the simple approach (up top). If args were a generator or other lazily evaluated iterable with a very large number of elements, then islice might be worth it.

David Z
  • 128,184
  • 27
  • 255
  • 279
3

If list_ is a generator (or can be trivially changed to be one), you can get away without materializing any lists at all using chain() from itertools.

from itertools import chain
'.'.join(chain(('',),list_,(suffix,)))

(This takes inspiration from David Zaslavsky's answer.)

sblom
  • 26,911
  • 4
  • 71
  • 95
  • I like this approach - but I'm not sure if it's easy to create a generator that returns a slice of an existing list? – max Feb 01 '12 at 22:22
  • Sure, you can use `itertools.islice(list_, f(n), f(n+1))`. – David Z Feb 01 '12 at 22:39
  • @DavidZaslavsky: I guess in light of what I've learned (see the update to the question), the `islice` has terrible performance on a list... I would use this approach if I had a true generator. – max Feb 06 '12 at 03:15
3

Also riffing on David Zaslavsky answer:

string = '.'.join([string] + list_ + [suffix])

The advantage of doing it this way is that there is no addition of strings.

Community
  • 1
  • 1
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
1
string += ('.' + '.'.join(list_) if list_ else '') + '.' + suffix 
JBernardo
  • 32,262
  • 10
  • 90
  • 115
  • Sorry, forgot to mention that `list_` is actually an expression; I was hoping to avoid creating an extra variable for it (see update to my question).. – max Feb 01 '12 at 22:14
  • Well, you already do that on your code... Then go with David's code if you don't matter creating 2 extra lists. – JBernardo Feb 01 '12 at 22:16
  • Yes, in my case, it's not a performance issue, just making the code look simple. – max Feb 01 '12 at 22:20