Reputation: 7
I'm learning python from the basics and i need a little help regarding a practice example from my study book. Original task is to replace the string "Don't panic!" with "on tap" using slices.
So, this is my original code, which didn't work:
phrase = "Don't panic!"
plist = list(phrase)
print(phrase)
print(plist)
new_phrase = ''.join([plist[1:3], plist[5], plist[4], plist[7], plist[6]])
With the int-r mistake:
TypeError: sequence item 0: expected str instance, list found
The correct answer from the book is:
new_phrase = ''.join(plist[1:3])
new_phrase = new_phrase + ''.join([plist[5], plist[4], plist[7], plist[6]])
And that's what i don't understand, what's the difference between my wrong line and the right one? As i look at this, both sequences have string instances?
Upvotes: -1
Views: 721
Reputation: 1121744
You are mixing lists and strings inside another list by mixing slicing and indexing. Slicing produces a list, indexing an individual element.
So the working code only puts individual values into a list:
[plist[5], plist[4], plist[7], plist[6]]
while your code also uses slicing:
[plist[1:3], plist[5], plist[4], plist[7], plist[6]]
# ^^^^^
so now you the first element is a list with 2 elements.
str.join()
can only join strings, so all elements must be strings, any other object types (such as a list) lead to errors.
The code in the book works around this by joining the slice result separately:
new_phrase = ''.join(plist[1:3])
This takes the sliced list, passing that to str.join()
directly (not as an element of another list). The code in the book then appends the result of a second str.join()
call to that on the next line:
new_phrase = new_phrase + ''.join([plist[5], plist[4], plist[7], plist[6]])
Modern Python (3.5 or newer) does have syntax to move elements from a list into a list you are creating with the [...]
syntax, by prepending the item with *
; that causes all the elements to be taken out and placed in the current list. You can use that here too to take all the sliced strings out of the slice and into the new list:
''.join([*plist[1:3], plist[5], plist[4], plist[7], plist[6]])
# ^ note the * here, meaning: take all elements from plist[1:3], not plist[1:3] itself.
It'll depend on the age of the book you are using whether or not it'll introduce this syntax.
It's always helpful to look at what is happening with the component elements of Python expressions, in the interactive interpreter. Here is what is happening for your code:
>>> phrase = "Don't panic!"
>>> plist = list(phrase)
>>> plist
['D', 'o', 'n', "'", 't', ' ', 'p', 'a', 'n', 'i', 'c', '!']
>>> [plist[1:3], plist[5], plist[4], plist[7], plist[6]]
[['o', 'n'], ' ', 't', 'a', 'p']
See that ['o', 'n']
element at the start there? That's not a single string value, so str.join()
won't accept it:
>>> ''.join([plist[1:3], plist[5], plist[4], plist[7], plist[6]])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: sequence item 0: expected str instance, list found
but if you ignored that part the remainder can be joined:
>>> ''.join([plist[5], plist[4], plist[7], plist[6]])
' tap'
If we use the *
syntax, you get a flat list, and ''.join(...)
accepts that:
>>> ''.join([*plist[1:3], plist[5], plist[4], plist[7], plist[6]])
'on tap'
Upvotes: 4
Reputation: 1872
As Madhan pointed at his response, you are trying to use a list as the first index, not a string.
Taken from pydocs:
str.join(iterable) Return a string which is the concatenation of the strings in iterable. A TypeError will be raised if there are any non-string values in iterable, including bytes objects. The separator between elements is the string providing this method.
So you need to provide only strings in your iterable. But if you see your code, you are using a list of multiple type of objects.
The first index is a list, and other indexes are strings.
Here is what is going on:
your_param = [plist[1:3], plist[5], plist[4], plist[7], plist[6]]
type(your_param)
<class 'list'>
for param in your_param:
type(param)
<class 'list'> # <--- HERE
<class 'str'>
<class 'str'>
<class 'str'>
<class 'str'>
If you want everything in one line, you can do something like:
your_param = [''.join(plist[1:3]), plist[5], plist[4], plist[7], plist[6]]
for param in your_param:
type(param)
<class 'str'>
<class 'str'>
<class 'str'>
<class 'str'>
<class 'str'>
Upvotes: 0
Reputation: 2776
You were so close with your original implementation. I don't know if your book has introduced the * (star) notation yet, but you can pass each item in an iterable with *my_iter
like so: my_function(*my_iter)
or just put it into a tuple: (val_a, val_b, val_c, *val_iter)
quick example:
my_list = [1,2,3,4]
my_tuple = (5,6,7,8)
combined_list = [*my_list, *my_tuple]
print(combined_list)
[1, 2, 3, 4, 5, 6, 7, 8]
Now, back to your question: Using *
-notation, we can easily solve your issue with the star so that your list of strings is passed, element by element, into the ''.join()
:
phrase = "Don't panic!"
plist = list(phrase)
new_phrase = ''.join([*plist[1:3], plist[5], plist[4], plist[7], plist[6]])
print(new_phrase)
on tap
Upvotes: 0
Reputation: 60957
You construct plist
using
plist = list(phrase)
This means that plist
is a list
of str
instances, one for each character in the original phrase
string:
>>> plist
['D', 'o', 'n', "'", 't', ' ', 'p', 'a', 'n', 'i', 'c', '!']
Slicing a list yields another list, so plist[1:3]
is a list:
>>> type(plist)
<class 'list'>
>>> type(plist[0])
<class 'str'>
>>> type(plist[0:1])
<class 'list'>
So the argument you're passing into join
contains both str
objects and a list
object (the list created by slicing plist
):
>>> [plist[1:3], plist[5], plist[4], plist[7], plist[6]]
[['o', 'n'], ' ', 't', 'a', 'p']
>>> [type(x) for x in [plist[1:3], plist[5], plist[4], plist[7], plist[6]]]
[<class 'list'>, <class 'str'>, <class 'str'>, <class 'str'>, <class 'str'>]
It doesn't matter that that inner list itself contains str
objects, since join
doesn't inspect into that list
object. As soon as it sees something inside the iterable you passed it that is not a str
, it raises the TypeError
you see.
Upvotes: 0
Reputation: 9
Let's discuss the correct answer first. Here you are joining plist[1:3] and making a string object then storing it into new_phrase. Keep in mind this is now a string. Then you are adding this string to another string (which is being made by another join function), hence no error as you can add a string to string.
Coming back to your solution, first of all, plist[1:3] is a list and not a string, so you cannot use it inside the join function as it expects a list and not a list of list.
So you can make plist[1:3] to string
new_phrase = ''.join([''.join(plist[1:3]), plist[5], plist[4], plist[7], plist[6]])
which is equivalent to the correct code as you can see.
Upvotes: 0