Skip to content

Commit

Permalink
Adds new __eq__ syntax; uses all and only necessary comparisons
Browse files Browse the repository at this point in the history
cuthbertLab#1466 introduces a new `__eq__` syntax and highlights some failing tests on Time Signature as 'work in progress'. This commit implements that syntax and fixes (uncomments) those tests.

Possible remaining questions:
- Do we want `assertRaises` on the comparison of different classes or is False good (as here)?
- all and only the right checks in `core`?
  • Loading branch information
MarkGotham committed Oct 19, 2022
1 parent a8176ba commit a156163
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 35 deletions.
71 changes: 58 additions & 13 deletions music21/meter/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,21 +449,61 @@ class TimeSignature(TimeSignatureBase):
TimeSignature to contradict what the notes imply. All this can be done
with .displaySequence.
Two time signatures are considered equal if they have both
the same beatSequence (e.g., {{1/8+1/8}+{1/8+1/8+1/8}})
and the same ratioString (e.g., '2/8+3/8').
For example, two 5-time meters with a different
beat structure count as different metres
Equality
--------
For two time signatures to be considered equal,
they have the same name and internal structure.
The name is tested by the :attr:`~music21.meter.TimeSignature.symbol`.
This helps distinguish between 'Cut' and '2/2', for example.
>>> tsCut = meter.TimeSignature('Cut')
>>> ts22 = meter.TimeSignature('2/2')
>>> tsCut == ts22
False
The internal structure is currently tested simply by the
:attr:`~music21.meter.TimeSignature.beatCount` and
:attr:`~music21.meter.TimeSignature.ratioString` attributes.
The check of :attr:`~music21.meter.TimeSignature.beatCount`
helps to distinguish the 'fast' (2-beat) from 'slow' (6-beat)
versions of 6/8.
>>> fast68 = meter.TimeSignature('fast 6/8')
>>> slow68 = meter.TimeSignature('slow 6/8')
>>> fast68 == slow68
False
Complementing this,
:attr:`~music21.meter.TimeSignature.ratioString`
provides a check of the internal divsions such that
'2/8+3/8' is different from '3/8+2/8', for example,
despite the fact that they could both be written as '5/8'.
For a less restrictive test, see
>>> ts2n3 = meter.TimeSignature('2/8+3/8')
>>> ts3n2 = meter.TimeSignature('3/8+2/8')
>>> ts2n3 == ts3n2
False
(Note: for a less restrictive test of this, see
:meth:`~music21.meter.TimeSignature.ratioEqual`
which returns True for all cases of '5/8'.
which returns True for all cases of '5/8').
Yes, equality is ever True:
>>> one44 = meter.TimeSignature('4/4')
>>> another44 = meter.TimeSignature() # '4/4' by default
>>> one44 == another44
True
'''
_styleClass = style.TextStyle
classSortOrder = 4

equalityAttributes = ('beatSequence', 'ratioString', 'symbol')

_DOC_ATTR: dict[str, str] = {
'beatSequence': 'A :class:`~music21.meter.MeterSequence` governing beat partitioning.',
'beamSequence': 'A :class:`~music21.meter.MeterSequence` governing automatic beaming.',
Expand Down Expand Up @@ -506,17 +546,22 @@ def __eq__(self, other) -> bool:
See notes at class doc and in meter.tests
'''

if not isinstance(other, type(self)):
return False # or return NotImplemented -- we do both in the current code, should unify
if not super().__eq__(other):
return False

if self.symbol != other.symbol: # first to solve most comparisons
return False

if self.ratioString != other.ratioString:
return False

if self.beatSequence == other.beatSequence:
return True
else:

if self.beatCount != other.beatCount:
return False

# Note: self.beatSequence apparently not needed.

return True

def resetValues(self, value: str = '4/4', divisions=None):
'''
reset all values according to a new value and optionally, the number of
Expand Down
7 changes: 7 additions & 0 deletions music21/meter/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,13 @@ def __init__(self, value=None, partitionRequest=None):

# SPECIAL METHODS #

def __eq__(self, other) -> bool:

if not super().__eq__(other):
return False

return True

def __deepcopy__(self, memo=None):
'''
Helper method to copy.py's deepcopy function. Call it from there.
Expand Down
42 changes: 20 additions & 22 deletions music21/meter/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,19 +91,17 @@ def testMeterSequenceDeepcopy(self):
a.load('4/4', 4)
b = copy.deepcopy(a)
self.assertIsNot(a, b)
# TODO: this is work in progress.
# self.assertEqual(a, b)
self.assertEqual(a, b)

def testTimeSignatureDeepcopy(self):
c = TimeSignature('4/4')
d = copy.deepcopy(c)
self.assertIsNot(c, d)
self.assertEqual(c, d)

# TODO: this is work in progress
# e = TimeSignature('slow 6/8')
# f = TimeSignature('fast 6/8')
# self.assertNotEqual(e, f)
e = TimeSignature('slow 6/8')
f = TimeSignature('fast 6/8')
self.assertNotEqual(e, f)

def testGetBeams(self):
ts = TimeSignature('6/8')
Expand Down Expand Up @@ -621,46 +619,46 @@ def testCompoundSameDenominator(self):

def testEquality(self):
'''
Test timeSignature equality on the basis of
same ratioString and beatSequence
TODO:
These tests set out expected behaviour
but the assertEqual tests are not currently passing.
Additional tests of TimeSignature object equality.
Apart from this and the doc tests,
see also testTimeSignatureDeepcopy for a test of
time signatures with different structure with same ratioString
(fast vs slow 6/8).
'''

# 5/8: but different internal structure

oneKindOf5 = TimeSignature('2+3/8')
sameKindOf5 = TimeSignature('2+3/8')

self.assertEqual(oneKindOf5.ratioString, '2/8+3/8')
self.assertEqual(str(oneKindOf5.beatSequence), '{{1/8+1/8}+{1/8+1/8+1/8}}')
self.assertEqual(sameKindOf5.ratioString, '2/8+3/8')
self.assertEqual(str(sameKindOf5.beatSequence), '{{1/8+1/8}+{1/8+1/8+1/8}}')
self.assertEqual(oneKindOf5, sameKindOf5) # TODO fails, despite the above
self.assertEqual(oneKindOf5, sameKindOf5)

otherKindOf5 = TimeSignature('3+2/8')
self.assertEqual(otherKindOf5.ratioString, '3/8+2/8')
self.assertEqual(str(otherKindOf5.beatSequence), '{{1/8+1/8+1/8}+{1/8+1/8}}')
self.assertNotEqual(oneKindOf5, otherKindOf5)

# 4/4: same internal structure, different written time signature.

oneKindOf44 = TimeSignature('4/4')
sameKindOf44 = TimeSignature()
self.assertEqual(oneKindOf44, sameKindOf44) # TODO fails
self.assertEqual(oneKindOf44, sameKindOf44)

otherKindOf44 = TimeSignature('Cut')
self.assertEqual(otherKindOf44.ratioString, '2/2')
self.assertNotEqual(oneKindOf5, otherKindOf44)

# Further tests
# Likewise, Different 'symbol'
self.assertNotEqual(TimeSignature('Cut'), TimeSignature('2/2'))
self.assertNotEqual(TimeSignature('slow 6/8'), TimeSignature('fast 6/8'))

with self.assertRaises(AttributeError): # TODO fails - does not raise
TimeSignature() == note.Note()
with self.assertRaises(AttributeError):
note.Note() == TimeSignature()
# Completely different class (either way round)

self.assertNotEqual(TimeSignature(), note.Note())
self.assertNotEqual(note.Note(), TimeSignature())
# NB: not self.assertRaises(AttributeError). Do we want that instead?


if __name__ == '__main__':
import music21
Expand Down

0 comments on commit a156163

Please sign in to comment.