Welcome to the Treehouse Community
Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.
Looking to learn something new?
Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.
Start your free trial
Iain Simmons
Treehouse Moderator 32,305 PointsMy address book class with search function (Regular Expressions in Python Extra Credit)
Hi all,
I'm a huge fan of Python, and also really like regular expressions and what you can do with them.
Here's my code for an AddressBook class, including a search function:
import re
import sys
class Contact(object):
"""Contact to be created by running regex on a text file/string"""
def __init__(self, match):
for key, value in match.groupdict().items():
setattr(self, key, value)
def __str__(self):
return '\n'.join(
sorted(
["\t{}: {}".format(
key, val
) for key, val in self.__dict__.items()]))
class AddressBook(object):
"""Address Book containing multiple Contact objects."""
def __init__(self, filename):
self.names_file = open(filename, encoding="utf-8")
self.data = self.names_file.read()
self.names_file.close()
self.contacts = [Contact(match) for match in line.finditer(self.data)]
def search(self):
results = []
result_count = 0
search_term = input("\nEnter a term to search the address book with"
+ " or 'q' to quit:\n")
if not search_term:
return self.search()
elif search_term.lower() == 'q':
exit()
for contact in self.contacts:
# if search_term in contact.__dict__.values():
for val in contact.__dict__.values():
if val and re.search("(" + search_term + ")", val):
result_string = "Result:\n{}".format(str(contact))
if result_string not in results:
results.append(result_string)
result_count += 1
else:
continue
if results:
print('\nThere {} {} result{}:\n\n{}'.format(
'is' if result_count == 1 else 'are',
result_count,
's' if result_count > 1 else '',
'\n\n'.join(results)))
else:
print("Sorry, there are no results for that search term.")
return self.search()
if (len(sys.argv) > 1):
address_book = AddressBook(sys.argv[1])
else:
address_book = AddressBook(
input("What's the name of the file to get the contacts from?\n"))
address_book.search()
It's a bit messy... but let me know what you think!
Iain Simmons
Treehouse Moderator 32,305 PointsGlad you asked... I did this a while ago and it helped to go back and look at it. I then realised I didn't post the entire script.
Not sure why I didn't include the actual regex part... maybe because it was already developed before the end of the course.
line = re.compile(r'''
^(?P<name>(?P<name_last>[-\w ]*),\s(?P<name_first>[-\w ]+))\t # Last and first names
(?P<email>[-\w\d.+]+@[-\w\d.]+)\t # Email
(?P<phone>\(?\d{3}\)?-?\s?\d{3}-\d{4})?\t # Phone
(?P<job>[\w\s]+,\s[\w .]+)\t? # Job and Company
(?P<twitter>@[\w\d]+)?$ # Twitter
''', re.X|re.M)
So line is a precompiled regex. finditer will use that regex and produce an iterable containing the matches in the string argument as match objects. In this case, I'm passing the string in self.data that was read from the file.
The list comprehension is then taking each match it finds and creating an instance of the Contact class. It's not creating a list of dictionaries, though the Contact class uses the groupdict method of the match object to create a dictionary containing key/value pairs of the named groups in the match (name, email, etc).
If you haven't already completed the Python Comprehensions workshop, I'd highly recommend it. Comprehensions are one of my favourite features of the Python language.
C H
6,587 PointsThanks for the quick response Iain! Funny thing, I actually had the python comprehensions video open in another tab while I read your comment. I think I'm understanding it better now: search.contacts is a list containing instances of Contact class; which is accessed by iterating through dict (which I'll have to look up info on).
Do you mind also explaining what you're verifying with the re.search on this line and it's formatting here (specifically the lack of 'r' and the '(' ')':
if val and re.search("(" + search_term + ")", val):
#Why not something like:
if val.lower() == search_term.lower():
Iain Simmons
Treehouse Moderator 32,305 PointsThe r before a string makes it a raw string, and just makes it easier to write (you don't have to escape as much).
I've used string concatenation to insert the value of the variable into the regex search pattern.
The reason to use re.search instead of just an equality check is that re.search looks for matches anywhere in the string. The equality conditional would need both strings in their entirety to be equal.
I maybe could have used if search_term.lower() in val.lower(), but since this was a course on regex, it seemed appropriate to do it the way I did!
There's also a way to use format() on a regex pattern to replace the variables in it. You use sets of double curly braces.
C H
6,587 PointsGreat explanations, I now understand!
1 Answer
Kenneth Love
Treehouse Guest TeacherYeah, a bit messy, but a great start. I'd recommend breaking things up into some smaller packages, use more functions/methods, etc. You don't need the else: continue, that'll be the default behavior anyway. But, again, great start. Keep going!
C H
6,587 PointsC H
6,587 PointsHey, thanks for posting this up. Helps me out. I'm wondering what this line does exactly. Is this creating a list of dictionaries--Contact(object) by sending match through again and again?
self.contacts = [Contact(match) for match in line.finditer(self.data)]