Skip to content

Commit 3a04fb6

Browse files
authored
Merge pull request #1826 from cuthbertLab/stream-not-flat
Remove Stream.flat - use .flatten() instead
2 parents 6b3e8cb + 4536d08 commit 3a04fb6

File tree

7 files changed

+75
-116
lines changed

7 files changed

+75
-116
lines changed

music21/musicxml/xmlToM21.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1864,9 +1864,12 @@ def copy_into_partStaff(source: stream.Stream,
18641864
removeClasses = STAFF_SPECIFIC_CLASSES[:]
18651865
if staffIndex != 0: # spanners only on the first staff.
18661866
removeClasses.append('Spanner')
1867-
newPartStaff = self.stream.template(removeClasses=removeClasses,
1868-
fillWithRests=False,
1869-
exemptFromRemove=EXEMPT_FROM_REMOVE)
1867+
newPartStaff = t.cast(
1868+
stream.PartStaff,
1869+
self.stream.template(removeClasses=removeClasses,
1870+
fillWithRests=False,
1871+
exemptFromRemove=EXEMPT_FROM_REMOVE)
1872+
)
18701873
partStaffId = f'{self.partId}-Staff{staffKey}'
18711874
newPartStaff.id = partStaffId
18721875
# set group for components (recurse?)

music21/note.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1935,9 +1935,7 @@ class Rest(GeneralNote):
19351935
gets rests as well.
19361936
19371937
>>> r = note.Rest()
1938-
>>> r.isRest
1939-
True
1940-
>>> r.isNote
1938+
>>> isinstance(r, note.Note)
19411939
False
19421940
>>> r.duration.quarterLength = 2.0
19431941
>>> r.duration.type
@@ -1985,6 +1983,13 @@ class Rest(GeneralNote):
19851983
19861984
>>> r1 == note.Note()
19871985
False
1986+
1987+
Currently, there are these convenience features, but they are going away
1988+
(They were originally added because isinstance was slow. It is now very fast)
1989+
>>> r.isRest
1990+
True
1991+
>>> r.isNote
1992+
False
19881993
'''
19891994
isRest = True
19901995
name = 'rest'

music21/prebase.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def classes(self) -> tuple[str, ...]:
117117
{10.0} <music21.clef.GClef>
118118
{30.0} <music21.clef.FrenchViolinClef>
119119
120-
`Changed 2015 Sep`: returns a tuple, not a list.
120+
Changed in v2: returns a tuple, not a list.
121121
'''
122122
try:
123123
return self._classTupleCacheDict[self.__class__]

music21/stream/base.py

Lines changed: 19 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -324,9 +324,6 @@ def __init__(self,
324324
# restrictClass: type[M21ObjType] = base.Music21Object,
325325
super().__init__(**keywords)
326326

327-
# TEMPORARY variable for v9 to deprecate the flat property. -- remove in v10
328-
self._created_via_deprecated_flat = False
329-
330327
self.streamStatus = streamStatus.StreamStatus(self)
331328
self._unlinkedDuration = None
332329

@@ -451,13 +448,6 @@ def __iter__(self) -> iterator.StreamIterator[M21ObjType]:
451448
specialized :class:`music21.stream.StreamIterator` class, which
452449
adds necessary Stream-specific features.
453450
'''
454-
# temporary for v9 -- remove in v10
455-
if self._created_via_deprecated_flat:
456-
warnings.warn('.flat is deprecated. Call .flatten() instead',
457-
exceptions21.Music21DeprecationWarning,
458-
stacklevel=3)
459-
self._created_via_deprecated_flat = False
460-
461451
return t.cast(iterator.StreamIterator[M21ObjType],
462452
iterator.StreamIterator(self))
463453

@@ -4699,11 +4689,11 @@ def measure(self,
46994689
def template(self,
47004690
*,
47014691
fillWithRests=True,
4702-
removeClasses=None,
4692+
removeClasses: Iterable[type|str]|set[type|str]|None = None,
47034693
retainVoices=True,
47044694
removeAll=False,
47054695
exemptFromRemove=frozenset(),
4706-
):
4696+
) -> t.Self:
47074697
'''
47084698
Return a new Stream based on this one, but without the notes and other elements
47094699
but keeping instruments, clefs, keys, etc.
@@ -4874,14 +4864,11 @@ def template(self,
48744864

48754865
* Changed in v7: all arguments are keyword only.
48764866
* New in v9.9: added exemptFromRemove
4877-
* Note: in v10
4867+
* Note: in v10: removeClasses cannot be boolean -- use removeAll instead
48784868
'''
48794869
out = self.cloneEmpty(derivationMethod='template')
48804870
if removeClasses is None:
48814871
removeClasses = {'GeneralNote', 'Dynamic', 'Expression'}
4882-
elif removeClasses is True:
4883-
removeClasses = set()
4884-
removeAll = True
48854872
elif common.isIterable(removeClasses):
48864873
removeClasses = set(removeClasses)
48874874

@@ -4903,7 +4890,7 @@ def optionalAddRest():
49034890
elOffset = self.elementOffset(el, returnSpecial=True)
49044891

49054892
# retain all streams (exception: Voices if retainVoices is False
4906-
if el.isStream and (retainVoices or ('Voice' not in el.classes)):
4893+
if isinstance(el, Stream) and (retainVoices or ('Voice' not in el.classes)):
49074894
optionalAddRest()
49084895
outEl = el.template(fillWithRests=fillWithRests,
49094896
removeClasses=removeClasses,
@@ -4919,7 +4906,7 @@ def optionalAddRest():
49194906

49204907
# okay now determine if we will be skipping or keeping this element
49214908
skip_element = False
4922-
if removeAll is True:
4909+
if removeAll:
49234910
# with this setting we remove everything by default
49244911
skip_element = True
49254912
elif el.classSet.intersection(removeClasses):
@@ -7770,7 +7757,7 @@ def flatten(self: StreamType, retainContainers=False) -> StreamType:
77707757
A very important method that returns a new Stream
77717758
that has all sub-containers "flattened" within it,
77727759
that is, it returns a new Stream where no elements nest within
7773-
other elements.
7760+
other elements. (Prior to v7 this was the property .flat)
77747761

77757762
Here is a simple example of the usefulness of .flatten(). We
77767763
will create a Score with two Parts in it, each with two Notes:
@@ -7847,6 +7834,7 @@ def flatten(self: StreamType, retainContainers=False) -> StreamType:
78477834

78487835
If `retainContainers=True` then a "semiFlat" version of the stream
78497836
is returned where Streams are also included in the output stream.
7837+
(Prior to v7 this was the property semiFlat)
78507838

78517839
In general, you will not need to use this because `.recurse()` is
78527840
more efficient and does not lead to problems of the same
@@ -7963,25 +7951,19 @@ def flatten(self: StreamType, retainContainers=False) -> StreamType:
79637951
<music21.note.Note D>,
79647952
<music21.note.Note D>)
79657953

7966-
OMIT_FROM_DOCS
7967-
7968-
>>> r = stream.Stream()
7969-
>>> for j in range(5):
7970-
... q = stream.Stream()
7971-
... for i in range(5):
7972-
... p = stream.Stream()
7973-
... p.repeatInsert(base.Music21Object(), [0, 1, 2, 3, 4])
7974-
... q.insert(i * 10, p)
7975-
... r.insert(j * 100, q)
7976-
7977-
>>> len(r)
7978-
5
7979-
7980-
>>> len(r.flatten())
7981-
125
7954+
.. note::Why did `.flat` become `.flatten()`? Early on music21's philosophy
7955+
was to use properties for commonly accessed "views" of a stream. This
7956+
worked well in the pre-IDE/debugger days of early Python 2. Now however
7957+
most of us program with IDEs and AI assistance that freely introspect the
7958+
attributes of objects. So if you're working with a Pitch object, your IDE
7959+
may have already looked up its .name or .accidental without your knowledge.
7960+
Properties are generally considered the same as attributes (that's what
7961+
they're designed for). The problem is that flattening a stream is a
7962+
time consuming algorithm that alters the stream and all its elements in
7963+
the process. Therefore streams should only be flattened when the programmer
7964+
requests them to be. The way to tell a modern IDE that a process may
7965+
have consequences is to make it a `.method()` not a `.property`.
79827966

7983-
>>> r.flatten()[124].offset
7984-
444.0
79857967
'''
79867968
# environLocal.printDebug(['flatten(): self', self,
79877969
# 'self.activeSite', self.activeSite])
@@ -8046,20 +8028,6 @@ def flatten(self: StreamType, retainContainers=False) -> StreamType:
80468028

80478029
return sNew
80488030

8049-
@property
8050-
def flat(self):
8051-
'''
8052-
Deprecated: use `.flatten()` instead
8053-
8054-
A property that returns the same flattened representation as `.flatten()`
8055-
as of music21 v7.
8056-
8057-
See :meth:`~music21.stream.base.Stream.flatten()` for documentation.
8058-
'''
8059-
flatStream = self.flatten(retainContainers=False)
8060-
flatStream._created_via_deprecated_flat = True
8061-
return flatStream
8062-
80638031
@overload
80648032
def recurse(self,
80658033
*,

music21/stream/core.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,7 @@ def coreGatherMissingSpanners(
693693
'''
694694
sb = self.spannerBundle
695695
sIter: StreamIterator|RecursiveIterator
696-
if recurse is True:
696+
if recurse:
697697
sIter = self.recurse() # type: ignore
698698
else:
699699
sIter = self.iter() # type: ignore
@@ -708,16 +708,16 @@ def coreGatherMissingSpanners(
708708
if constrainingSpannerBundle is not None and sp not in constrainingSpannerBundle:
709709
continue
710710
if requireAllPresent:
711-
allFound = True
711+
allFound: bool = True
712712
for spannedElement in sp.getSpannedElements():
713713
if spannedElement not in sIter:
714714
allFound = False
715715
break
716-
if allFound is False:
716+
if not allFound:
717717
continue
718718
collectList.append(sp)
719719

720-
if insert is False:
720+
if not insert:
721721
return collectList
722722

723723
if collectList: # do not run elementsChanged if nothing here.

music21/stream/iterator.py

Lines changed: 3 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,6 @@ class StreamIterator(prebase.ProtoM21Object, Sequence[M21ObjType]):
7979
* StreamIterator.streamLength -- length of elements.
8080
8181
* StreamIterator.srcStreamElements -- srcStream._elements
82-
* StreamIterator.cleanupOnStop -- should the StreamIterator delete the
83-
reference to srcStream and srcStreamElements when stopping? default
84-
False -- DEPRECATED: to be removed in v10.
8582
* StreamIterator.activeInformation -- a dict that contains information
8683
about where we are in the parse. Especially useful for recursive
8784
streams:
@@ -114,6 +111,7 @@ class StreamIterator(prebase.ProtoM21Object, Sequence[M21ObjType]):
114111
- StreamIterator inherits from typing.Sequence, hence index
115112
was moved to elementIndex.
116113
* Changed in v9: cleanupOnStop is deprecated. Was not working properly before: noone noticed.
114+
* Changed in v10: remove cleanupOnStop.
117115
118116
OMIT_FROM_DOCS
119117
@@ -152,7 +150,6 @@ def __init__(self,
152150
self.sectionIndex: int = -1
153151
self.iterSection: t.Literal['_elements', '_endElements'] = '_elements'
154152

155-
self.cleanupOnStop: bool = False
156153
self.restoreActiveSites: bool = restoreActiveSites
157154

158155
self.overrideDerivation: str|None = None
@@ -358,7 +355,6 @@ def __getitem__(self, k: int|slice|str) -> M21ObjType|list[M21ObjType]|None:
358355
>>> sI.srcStream is s
359356
True
360357
361-
362358
To request an element by id, put a '#' sign in front of the id,
363359
like in HTML DOM queries:
364360
@@ -388,23 +384,6 @@ def __getitem__(self, k: int|slice|str) -> M21ObjType|list[M21ObjType]|None:
388384
>>> s.iter().notes[0]
389385
<music21.note.Note F#>
390386
391-
Demo of cleanupOnStop = True; the sI[0] call counts as another iteration, so
392-
after it is called, there is nothing more to iterate over! Note that cleanupOnStop
393-
will be removed in music21 v10.
394-
395-
>>> sI.cleanupOnStop = True
396-
>>> for n in sI:
397-
... printer = (repr(n), repr(sI[0]))
398-
... print(printer)
399-
('<music21.note.Note F#>', '<music21.note.Note F#>')
400-
>>> sI.srcStream is s # set to an empty stream
401-
False
402-
>>> for n in sI:
403-
... printer = (repr(n), repr(sI[0]))
404-
... print(printer)
405-
406-
(nothing is printed)
407-
408387
* Changed in v8: for strings: prepend a '#' sign to get elements by id.
409388
The old behavior still works until v9.
410389
This is an attempt to unify __getitem__ behavior in
@@ -652,23 +631,9 @@ def resetCaches(self) -> None:
652631

653632
def cleanup(self) -> None:
654633
'''
655-
stop iteration; and cleanup if need be.
634+
stop iteration; and cleanup if need be. Can be subclassed. does nothing.
656635
'''
657-
if self.cleanupOnStop:
658-
self.reset()
659-
660-
# cleanupOnStop is rarely used, so we put in
661-
# a dummy stream so that self.srcStream does not need
662-
# to be typed as Stream|None
663-
664-
# eventually want this to work
665-
# SrcStreamClass = t.cast(type[StreamType], self.srcStream.__class__)
666-
SrcStreamClass = self.srcStream.__class__
667-
668-
del self.srcStream
669-
del self.srcStreamElements
670-
self.srcStream = SrcStreamClass()
671-
self.srcStreamElements = ()
636+
pass
672637

673638
# ---------------------------------------------------------------
674639
# getting items

0 commit comments

Comments
 (0)