
>>> s[0]
<music21.note.Note C>
>>> s[-1]
<music21.note.Note A>

Out of range notes raise an IndexError:

>>> s[99]
Traceback (most recent call last):
IndexError: attempting to access index 99 while elements is of size 8

If a slice of indices is given, a list of elements is returned, as if the Stream
were a list or Tuple.

>>> subslice = s[2:5]
>>> subslice
[<music21.note.Note E>, <music21.note.Rest quarter>, <music21.note.Note F>]
>>> len(subslice)
3
>>> s[1].offset
1.0
>>> subslice[1].offset
3.0


If a class is given, then a :class:`~music21.stream.iterator.RecursiveIterator`
of elements matching the requested class is returned, similar
to `Stream().recurse().getElementsByClass()`.

>>> len(s)
8
>>> len(s[note.Rest])
2
>>> len(s[note.Note])
6

>>> for n in s[note.Note]:
...     print(n.name, end=' ')
C D E F G A

Note that this iterator is recursive: it will find elements inside of streams
within this stream:

>>> c_sharp = note.Note('C#')
>>> v = stream.Voice()
>>> v.insert(0, c_sharp)
>>> s.insert(0.5, v)
>>> len(s[note.Note])
7

When using a single Music21 class in this way, your type checker will
be able to infer that the only objects in any loop are in fact `note.Note`
objects, and catch programming errors before running.

Multiple classes can be provided, separated by commas. Any element matching
any of the requested classes will be matched.

>>> len(s[note.Note, note.Rest])
9

>>> for note_or_rest in s[note.Note, note.Rest]:
...     if isinstance(note_or_rest, note.Note):
...         print(note_or_rest.name, end=' ')
...     else:
...         print('Rest', end=' ')
C C# D E Rest F G Rest A

The actual object returned by `s[module.Class]` is a
:class:`~music21.stream.iterator.RecursiveIterator` and has all the functions
available on it:

>>> s[note.Note]
<...>

If no elements of the class are found, no error is raised in version 7:

>>> list(s[layout.StaffLayout])
[]


If the key is a string, it is treated as a `querySelector` as defined in
:meth:`~music21.stream.iterator.getElementsByQuerySelector`, namely that bare strings
are treated as class names, strings beginning with `#` are id-queries, and strings
beginning with `.` are group queries.

We can set some ids and groups for demonstrating.

>>> a.id = 'last_a'
>>> c.groups.append('ghost')
>>> e.groups.append('ghost')

'.ghost', because it begins with `.`, is treated as a class name and
returns a `RecursiveIterator`:

>>> for n in s['.ghost']:
...     print(n.name, end=' ')
C E

A query selector with a `#` returns the single element matching that
element or returns None if there is no match:

>>> s['#last_a']
<music21.note.Note A>

>>> s['#nothing'] is None
True

Any other query raises a TypeError:

>>> s[0.5]
Traceback (most recent call last):
TypeError: Streams can get items by int, slice, class, class iterable, or string query;
   got <class 'float'>

* Changed in v7:
  - out of range indexes now raise an IndexError, not StreamException
  - strings ('music21.note.Note', '#id', '.group') are now treated like a query selector.
  - slices with negative indices now supported
  - Unsupported types now raise TypeError
  - Class and Group searches now return a recursive `StreamIterator` rather than a `Stream`
  - Slice searches now return a list of elements rather than a `Stream`

* Changed in v8:
  - for strings: only fully-qualified names such as "music21.note.Note" or
  partially-qualified names such as "note.Note" are
  supported as class names.  Better to use a literal type or explicitly call
  .recurse().getElementsByClass to get the earlier behavior.  Old behavior
  still works until v9.  This is an attempt to unify __getitem__ behavior in
  StreamIterators and Streams.
  - allowed iterables of qualified class names, e.g. `[note.Note, note.Rest]`

Nr