0
\$\begingroup\$

Version 2 of Music note class

#!/usr/bin/env python3

from string import ascii_uppercase
from string import ascii_lowercase


class Notes:
    octave_number = 7
    low_notes = ascii_uppercase[:octave_number]
    high_notes = ascii_lowercase[:octave_number]
    notes = low_notes + high_notes
    notes_number = range(octave_number * 2)
    notes_dict = dict(zip(notes, notes_number))
    numbers_dict = dict(zip(notes_number, notes))

    def __init__(self, _note):
        if not _note in Notes.notes:
            raise Exception("Not a valid note")
        self.note = _note
        self.note_number = Notes.notes_dict[self.note]

    def add(self, n):
        if (self.note in Notes.high_notes) and (n > 0):
            raise Exception("You can only add to low notes (ABCDEFG)")
        if (self.note in Notes.low_notes) and (n < 0):
            raise Exception("You can only subtract from notes (abcdefg)")
        if(abs(n) > Notes.octave_number):
            raise Exception("Maximum amount of increase or decrease is %d" % Notes.octave_number)


        new_number = self.note_number + n
        return Notes(Notes.numbers_dict[new_number])

Test:

x = Notes("A")
x.add(7).note
x = Notes("b")
x.add(-6).note

Changes:

  • Removed minus method
  • Expanded notes to two octaves
  • add now returns a Notes object
\$\endgroup\$
  • \$\begingroup\$ What is the question? \$\endgroup\$ – sebix Dec 6 '14 at 15:13
4
\$\begingroup\$

You should be aware of Python's "magic methods" to implement class behaviours, including numerical methods like __add__ and __sub__. This allows you to write e.g. note3 = note1 + note2, rather than note3 = note1.add(note2). There are also methods for display, including __repr__, so you can easily view the instance. Additionally:

  • the class name should be singular, as this only holds one note, and constants should be UPPERCASE;
  • it's easier to handle either uppercase or lowercase and convert all input accordingly;
  • you can access class attributes via self too, which makes inheritance easier;
  • you check note in __init__, but there's nothing to protect it afterwards; and
  • number could be calculated based on note as required, rather than stored as duplicate information.

Here's an alternative implementation with those ideas in.

#!/usr/bin/env python3

from string import ascii_uppercase

class Note:

    NOTE_COUNT = 7
    NUMBERS = dict(enumerate(ascii_uppercase[:NOTE_COUNT]))
    NOTES = {v: k for k, v in NUMBERS.items()}

    def __init__(self, note=None, number=None):
        if note is not None and note.upper() in self.NOTES:
            note = note.upper()
        elif number is not None and number in self.NUMBERS:
            note = self.NUMBERS[number]
        else:
            raise Exception("Not a valid note")
        self._note = note 

    def __repr__(self):
        return "Note({._note!r})".format(self)

    @property
    def number(self):
        return self.NOTES[self._note]

    @property
    def note(self):
        return self._note

    @note.setter
    def note(self, new_note):
        new_note = new_note.upper()
        if new_note not in self.NOTES:
            raise Exception("Not a valid note")
        self._note = new_note

    def __add__(self, other):
        try:
            num = other.number
        except AttributeError:
            num = other
        return Note(number=((self.number+num)%self.NOTE_COUNT))

    def __sub__(self, other):
        try:
            num = other.number
        except AttributeError:
            num = other
        return Note(number=((self.number-num)%self.NOTE_COUNT))

In use:

>>> B = Note("B")
>>> C = Note("C")
>>> B + C
Note('D') # not sure this addition makes sense, but it works!
>>> B.note = "X"
Traceback (most recent call last):
  File "<pyshell#38>", line 1, in <module>
    B.note = "X"
  File "<pyshell#34>", line 30, in note
    raise Exception("Not a valid note")
Exception: Not a valid note
>>> B.number
1

Now all you need is octaves and accidentals! You should also add some docstrings explaining what the class does and how to use it.

\$\endgroup\$
  • \$\begingroup\$ If you overload binary operators, you should return NotImplemented in ambiguous cases. See this discussion. Consider also the different behaviour of x = []; x = x + () and x = []; x += (). In this vein I strongly disagree with overloading __add__ to support addition of notes with numbers, but would be fine with __iadd__ doing so. \$\endgroup\$ – Veedrac Dec 8 '14 at 14:24
  • 1
    \$\begingroup\$ @Veedrac if anything, I think that in this case adding Note instances makes less sense than adding a number to a Note! Interesting point though, I will take it on board. Do you have a more canonical source than something you also asserted elsewhere? \$\endgroup\$ – jonrsharpe Dec 8 '14 at 14:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.