The id is defined to be unique for an object across its lifetime. That is, two separate objects existing at the same time cannot have the same id. However, two separate objects existing at different time as well as objects not required to be separate may have the same id.
Return the “identity” of an object. This is an integer which is guaranteed to be unique and constant for this object during its lifetime. Two objects with non-overlapping lifetimes may have the same id() value.
Thus, one has to be mindful of two things when reasoning about id: When must the lifetime of objects overlap, and when must two objects be separate.
When the objects whose id we look at are created only for the id:
>>> #  /  / first a[:]
>>> #  v  v       v    v second a[:]
>>> id(a[:]) == id(a[:])
True
then the object are not required to exist at the same time. Each id(a[:])  expression can create the slice, get its id and then discard the slice before the equality between the ids is ever checked. This means both slice can have the same id as they never exist at the same time.
In contrast, when a slice is assigned to a variable it has to exist at least as long as the variable. Thus, when we check the id of an object via a variable
>>> b = a[:]           # < ------------- first a[:]
>>> id(b) == id(a[:])  # < second a[:]     |
False
>>> b                  # < ----------------/
… 
its lifetime overlaps with that of the temporary slice. This means both slices must not have the same id as they never exist at the same time…
… iff slicing must create separate objects.
When comparing the behaviour of list and str, the key difference is that the latter does not have behaviour depending on its identity – roughly, this corresponds to mutable and immutable types.
When working with lists, identity is important because we can mutate a specific object. Even if two objects have the same initial value, mutation has a different effect:
>>> a, b = [1, 2, 3], [1, 2, 3]
>>> c = a         # a, b, c have same value
>>> c += [4]      # changing c has different effect on a and b
>>> a == b
False
When working with str, identity is irrelevant because we cannot mutate a specific object.  If two objects have the same initial value, immutability guarantees they will always have the same value:
>>> a, b = "123", "123"
>>> c = a         # a, b, c have same value
>>> c += "4"       # changing c has *no* effect on a and b
>>> a == b
True
As a result, slicing a mutable list to a new list must always create a new object. Otherwise, mutating the slice would have unreliable behaviour.
In contrast, slicing an immutable str to a new str may create a new object. Even if it always provides the same object the behaviour is the same.
As a result of how id is defined – in respect to lifetime and separation – a Python implementation must use separate ids in specific cases but may use separate ids in all other cases.
In specific, a Python implementation is free to re-use ids if objects don't exist at the same time and is free to share ids if behaviour does not depend on identity.