Music note class - v2 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 &gt; 0):
            raise Exception("You can only add to low notes (ABCDEFG)")
        if (self.note in Notes.low_notes) and (n &lt; 0):
            raise Exception("You can only subtract from notes (abcdefg)")
        if(abs(n) &gt; 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
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:

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