import datetime
# store next available id for all new notes
last_id = 0
class Note:
''' Represent note in the notebook.
Match against a string in text content searches.
Store tags for each note. '''
def __init__(self, memo, tags=''):
''' Initialize a note with memo and optional space-separated tags.
Automatically set note creation date and unique note id. '''
self.memo = memo
self.tags = tags
self.creation_date = datetime.date.today()
global last_id
last_id += 1
self.id = last_id
def match(self, filter):
''' Determine if this note matches filter text. Return True or False.
Search is case-sensitive, and matches text and tags.
Return filter in self.memo or filter in self.tags.
- Iteration I Testing
- Focused on testing Notes objects.
- Consider ownership of notebook OR notes?
>>> from notebook import Note
>>> n1 = Note("hello 1")
>>> n2 = Note("hello 2")
>>> n1.id # 1
>>> n2.id # 2
>>> n1.match('hello') # True
>>> n1.match('bollo') # False
class Notebook:
''' Represent a collection of notes that can be tagged, modified and searched. '''
def __init__(self):
''' Initialize a notebook with an empty list. '''
self.notes = []
def new_note(self, memo, tags=''):
''' Create a new note and add it to the list. '''
self.notes.append(Note(memo,tags))
def modify_memo(self, note_id, memo):
''' Find the note with the given id and change its memo to value provided. '''
for note in self.notes:
if note.id == note_id:
note.memo = memo
break
def modify_tags(self, note_id, tags):
''' Find the note with the given id and change its tags to value provided. '''
for note in self.notes:
if note.id == note_id:
note.tags = tags
break
def search(self, filter):
''' Find all notes that match the given filter '''
return [ note for note in self.notes if note.match(filter) ]
>>> from notebook import Note, Notebook
>>> n = Notebook()
>>> n # ??
>>> n.new_note('hello 1')
>>> n.new_note('hello 2')
>>> n.new_note('hello 3')
>>> n.new_note('hello world')
>>> n # ??
>>> n.notes # object info
>>> n.notes[0].id # 1
>>> n.notes[1].id # 2
>>> n.notes[2].id # 3
>>> n.notes[0].memo # 'hello 1'
>>> n.search('hello')
>>> n.search('world')
>>> n.modify_menu(4, 'howdy globe')
- Iteration III Test-Driven Code Enhancements
def _find_note(self, note_id):
''' Locate the note with provided given id. '''
for note in self.notes:
if note.id == note_id:
return note
return None
def modify_memo(self, note_id, memo):
''' Find the note with the given id and change its memo to the given value. '''
self._find_note(note_id).memo = memo
def modify_tags(self, note_id, tags):
''' Find the note with the given id and change its tags to value provided. '''
self._find_note(note_id).tags = tags
import sys
from notebook import Notebook, Note
class Menu:
''' Display menu of choices and respond to choices when run. '''
def __init__(self):
self.notebook = Notebook()
self.choices = {
"1": self.show_notes,
"2": self.search_notes,
"3": self.Add_note,
"4": self.modify_note,
"5": self.quit
}
def display_menu(self):
print("""
Notebook App Menu
1. Show All Notes
2. Search Notes
3. Add Note
4. Modify Note
5. Quit
```)
def run(self):
''' Display menu and respond to choices. '''
while True:
self.display_menu()
choice = input('Enter an option: ')
action = self.choices.get(choice)
if action:
action()
else:
print("{0} is an invalid menu choice".format(choice))
def show_notes(self, notes=None):
if not notes:
notes = self.notebook.notes
for note in notes:
print("{0}: {1}\n{2}".format(note.id, note.tags, note.memo))
def search_notes(self):
filter = input('Search For: ')
notes = self.notebook.search(filter)
self.show_notes(notes)
def add_note(self):
memo = input('Enter a memo: ')
self.notebook.new_note(memo)
print('Your new note has been added.')
def modify_note(self):
id = input('Enter a note id: ')
memo = input('Enter a memo: ')
tags = input('Enter tags: ')
if memo:
self.notebook.modify_memo(id, memo)
if tags:
self.notebook.modify_tags(id, tags)
def quit(self):
print('Thank you for using your note book today.')
sys.exit(0)
if __name__ == "__main__":
Menu().run()
def modify_memo(self, note_id, memo):
''' Find the note with given id, and change its memo to the value provided. '''
note = self._find_note(note_id)
if note:
note.memo = memo
return True
return False
def modify_tags(self, note_id, tags):
''' Find the note with given id, and change its tags to the value provided. '''
note = self._find_note(note_id)
if note:
note.tags = tags
return True
return False
- Why not put this in the _find_note method?
- Other DRY options?
- Do we want case-sensitive searches?
- We will soon further enhance this method by raising an exception.
- Bug Fix #2: Convert both note.id and note_id to strings to support non-numeric entries.
def _find_note(self, note_id):
''' Locate the note with provided given id. '''
for note in self.notes:
if str(note.id) == str(note_id):
return note
return None
- [app-name]
- domains --< urls
- pages --E metadata + content + tags
- url-pages-registry.py
- url-pages-tests.py
- url-aliases-registry.py
- url-aliases-tests.py
- menu-interface.py => menu interface
- cli-tool.py => cli interface
- web-app.py => web interface
- Code Sample Name
- Code Sample Versions: Step Rewinds + Replays
- Diagrams
- Anti-patterns
- Iterations + Diffs
- Code Templates
- Tests --E
- REST Patterns
- Docs
- Demos
class MySubClass(object):
pass
- Working example: Contact Manager (page 60) Supporting Varied Contact Types
class Contact:
''' Each Contact Type gets its own methods.
CLASS VARIABLE goes into each object instantiated from this class. '''
all_contacts = []
def __init__(self, name, email):
self.name = name
self.email = email
Contact.all_contacts.append.(self)
class Supplier(Contact):
def order(self, order):
print("In a real apps, then we would send '{}' order to '{}'.".format(order, self.name)
>>> c = Contact('John Contact Goop', 'goop@gmail.com')
>>> s = Supplier('Jill Supplier Bloof', 'bloof@gmail.com')
>>> print(c.name, c.email, s.name, s.email)
>>> c.all_contacts
>>> c.order('I need pliers.'). # Error
>>> s.order('I also need pliers.') # Real app would send message to proper target.
class ContactList(list):
def search(self, name):
''' Return all contacts containing the search value in their name. '''
matching_contacts = []
for contact in self:
if name in contact.name:
matching_contacts.append(contact)
return matching_contacts
class Contact:
- all_contacts = []
+ all_contact = ContactList()
# testing
>>> [] == list() # True
>>> isinstance([], object). # True
>>> c1 = Contact('john 1','j1@example.net')
>>> c2 = Contact('john 2','j2@example.net')
>>> c3 = Contact('joe 3','joe3@example.net')
>>> [ c.name for c in Contact.all_contacts.search('john') ]# Is case-sensitive config toggle desirable?
- Second extend builtin: dict
class LongNameDict(dict):
def longest_key(self):
longest = None
for key in self:
if not longest or len(key) > len(longest):
longest = key
return longest
>>> longkeys = LongNameDict()
>>> longkeys['hello'] = 1
>>> longkeys['longest yet'] = 5
>>> longkeys['hello2'] = 'world'
>>> longkeys.longest_key() # 'longest yet'
- Most builtins (object, list, set, dict, file, str) can be extended.
- Overriding and super (page 63)
class Friend(Contact):
def __init_(self, name, email, phone):
super().__init__(name, email)
self.phone = phone
-
III: Multiple inheritance
-
Simplest and most useful form of multiple inheritance: mixin
- Mixins are superclasses designed to be inherited and extended.
- A fine example from std lib: smtplib
-
Contact types have multiple common communication methods, like phone calls or email sends.
class MailSender:
def send_mail(self, message):
print('Sending mail to ' + self.email)
# Add email logic here...
- Now we can combine two+ parent classes...
class EmailableContact(Contact, MailSender):
pass
>>> e = EmailableContact('John Smith', 'jsmith@example.net')
>>> Contact.all_contacts
>>> e.send_mail("Hello, test email here!")
-
Alternatives to mixins
-
Had we added send_mail to the subclass, we would still need to dupe it for other subclasses.
-
Create standalone function for sending email.
-
Explored a few ways of using composition rather than inheritance.
-
Monkey-patch Contact class.
-
inheritance : composition as 'is a' : 'has a'
class AddressHolder:
def __init__(self, street, city, state, code):
self.street = street
self.city = city
self.state = state
self.code = code
- IV: Polymorphism and duck-typing