Have you ever loaned an LP and can't locate it any more. It bothers me even today when I can't even play an LP. If only I had kept track of the person to whom I had loaned it. Today, no one may borrow an LP but how about a book or your favorite CD/DVD. I just want to keep track of the person and the item he borrowed. In Python, this is called a dictionary. So, let's start the python interpreter from the command line and create an empty dictionary. >>>loaned_items = {} My friend PVR has borrowed my copy of Dr. Strangelove. So, >>>loaned_items['PVR'] =
“Dr. Strangelove” We can use a matching pair of ' or “ to enter names or, more generally, strings. Python interpreter treats both identically. KSN borrows a copy of “The Mouse that Roared”. So, we have >>>loaned_items['KSN']='The
Mouse That Roared' Now you can check the contents of the dictionary you created : >>>loaned_items {'PVR': 'Dr. Strangelove', 'KSN': 'The Mouse That Roared'} >>>loaned_items['PVR'] 'Dr. Strangelove' PVR is a compulsive borrower. He also takes my copy of “The Shop Around the Corner”. So, >>>loaned_items['PVR'] = 'The Shop Around the Corner' >>>loaned_items {'PVR': 'The Shop Around the Corner', 'KSN': 'The Mouse That Roared'} There is no record of PVR having borrowed 'Dr. Strangelove'! We could say that my software does allow him to borrow more than one item – but I would like to retain PVR as my friend. So, we need to manage our information better. The reason for the problem is that the key of a dictionary has to be unique. We can assign only one value to a key. We could create a dictionary of item_borrowers where the key is the item name and the value is the name of the borrower. As long as all the items we are lending have a unique name, this will work. However, let us stick to our original design. The key has to be unique but the value can be complex. Suppose the value could be a list of items the person has borrowed. Can we create a list in Python and if so, how? >>>loaned_items['KSN']=['The Mouse That Roared'] >>>loaned_items['PVR'] = ['Dr. Strangelove','The Shop Around the Corner'] >>>loaned_items {'PVR': ['Dr. Strangelove', 'The Shop Around the Corner'], 'KSN': ['The Mouse That Roared']} >>>b2f = 'Back to the Future' >>>loaned_items['AKS'] = [b2f, b2f + ' II', b2f + ' III'] >>>loaned_items['AKS'] ['Back to the Future', 'Back to the Future II', 'Back to the Future III'] We have defined a variable b2f with the value 'Back to the Future'. Then, we add some string values to it. The result should not surprise us. For a string, it seems reasonable that addition should be the same as appending. What if we tried to subtract 2 strings or divide a string by another? What should it do? As there are no reasonable correspondences in the real world, Python interpreter will tell you that these are unsupported operations on strings. Suppose a friend decides to borrow 'Python A Love Story' a video created by students of Yorktown High School. If he has not borrowed anything before, it is simple. If he has, we wish to add this video to the list of items he has already borrowed. It seems natural to create a function to help us. But before we do that, let us try loaned_items['xyz']. We get a key error. Fortunately, a dictionary has a property called has_key which we can use. >>> def add_item(borrower, item): ... if loaned_items.has_key(borrower): ... loaned_items[borrower] = loaned_items[borrower] + [item] ... else: ... loaned_items[borrower] = [item] ... >>> add_item('AKS', '2001 A Space Odyssey') >>> loaned_items['AKS'] ['Back to the Future', 'Back to the Future II', 'Back to the Future III', '2001 A Space Odyssey'] >>> add_item('SSB', 'Python A Love Story') >>> loaned_items['SSB'] ['Python A Love Story'] We can add two lists and the behaviour is reasonable. It concatenates the two lists. But where did we get the property has_key? How do we know what else is possible? Best way is to try: >>>dir(loaned_items) # gives us the properties available >>>type(loaned_items) # tells us that we are dealing with a 'dict' object >>>help('dict') # display help for the dictionary objects While it may seem complex and terse at present, it is just fine once you understand the language and do not wish to clutter your brain by having to memorise facts. You can repeat the same steps with the b2f variable/object we had created and get a flavour of the amount of fun we can have with strings. Keeping More Information about the BorrowerIf we were keeping a manual record, we would create multiple columns and have a column for the telephone number, one for email address and another one for the items borrowed. This seems like a list of three entries, whose third entry is another list. That is, we could have something like: >>>loaned_item['SSB'] =
['9999888800', 'ssb@mail.in', We can imagine that manipulating such a data can become complex. Making changes can be hard. So, we can use classes in Python for managing such data. Let us call this class loans. We will have an object of the type loans. The loans class with have methods which help us manage our data simply. Let us look at the following code and understand it. class loans: def __init__(self): self.friends = {} self.loaned_items = {}
def add_friend(self, name, phone=None, email=None): self.friends[name] = [phone, email]
def loan_item(self, item_name, name): self.loaned_items[item_name] = name
def return_item(self, item_name): self.loaned_items.remove(item_name) We have written this code in a file loans.py. The method __init__ initialises the object at the time of creation. In this case, we create two empty dictionaries, one containing friends and the other, the loaned items. The keyword 'self' often bothers many programmers from other languages. It can be treated as a syntax requirement. It is the first parameter of a class method and it is the prefix of class variables. (In case you are interested, it is needed in order to avoid ambiguity between local variables and class variables. I also prefer an explicit self to an implicit this.) Notice that in the case of add_friend, we have specified default values for some parameters. This will allow us to ignore these parameters in case we do not have any information about them. How do we use this? Let us try the following on Python interpreter: >>> from loans import loans # from loans.py import the loans class >>> my_loans = loans() # create an object of the type loans >>> dir(my_loans) # what can be done on my_loans? ['__doc__', '__init__', '__module__', 'add_friend', 'friends', 'loan_item', 'loaned_items', 'return_item'] >>> my_loans.add_friend('SSB') >>> my_loans.friends {'SSB': [None, None]} >>> my_loans.loan_item('2001 A Space Odyssey', 'SSB') >>> my_loans.loaned_items {'2001 A Space Odyssey': 'SSB'} We access the data items and the methods of the class by using my_loans as the prefix and '.' as the separator. Notice that we do not pass any parameter for self because that represents the object – my_loans in our case. Create some more friends: >>> my_loans.add_friend('PVR', '9999888800', 'pvr@mail.in') >>> my_loans.add_friend('KSN', email='ksn@mail.in') We notice the advantage of specifying default values for phone and email in the add_friends. We created a friend 'SSB' without specifying either. We create 'PVR' by specifying all the parameters in order. The nicest capability is that we can use the keyword and specify the email without worrying about any intervening parameters, like the phone. Adding Some More Methods to the ClassOnce the data is there, what we would like to know is whether an item is borrowed and if so by whom. We may also want to know the items borrowed by a friend. So, in loans.py, let us add these two methods. Remember, the indentation of these methods will be the same as that of the methods defined above.
def info_friend(self, name): if self.friends.has_key(name): return 'phone = ' + str(self.friends[name][0]) + \ ' & Email = ' + str(self.friends[name][1]) else: return 'No phone or email'
def borrower(self, item_name): if self.loaned_items.has_key(item_name): name = self.loaned_items[item_name] return name + ' contact: ' + self.info_friend(name) else: return item_name + ' : not borrowed'
def borrowed_items(self, name): items = [key for key in self.loaned_items if self.loaned_items[key] == name ] return str(items) We introduce an additional method to keep each method simple. Both info_friend and borrower check return values based on whether the parameter is a valid key. If the entire statement does not fit on a line, '\' is used to continue it on the next line. The str method is useful as it returns a string representation of an object, e.g. a number or a None object. We reference specific fields of a list by using an integer index starting with 0. It should be pretty easy to read the above code. The borrowed_items method makes use of list comprehensions – an incredibly powerful way to manipulate lists. In our case, items is a list which consists of the keys of loaned_items whose value matches the borrower. It's Not a 24x7 ApplicationIf we close our application or shut the system, all our entries are gone. We preserve vegetables and fruit by pickling them. Well, Python provides a module to pickle objects. We continue with the Python interpreter and enter the following commands: >>>import pickle # import the pickle module >>>f = open('loans.pickle', 'w') # open a file in write mode >>>pickle.dump(my_loans, f) # pickle our object >>>f.close() # don't forget to close the file (It is always possible to enter the Python script in a file, e.g. myscript.py. We can then run it by giving the command 'python myscript.py' and use it as often as needed.) There are two ways to import a module. We have used 'import pickle' above. Hence, we have to use the methods in the module by using the module name as well, e.g. pickle.dump. We could have alternatively used 'from pickle import dump', in which case we would have used the method 'dump' without the need to qualify it. Suppose we wanted to import everything from the module, we could then have used 'from pickle import *'. How do we get back the pickled object? It is equally simple. >>>del my_loans # delete the my_loans object. >>>my_loans.friends # you should get an error >>>f = open('loans.pickle') # open the pickle file – default is read >>>my_loans = pickle.load(f) # create the object from the pickle >>>my_loans.friends # verify that the data is correct The first 2 lines are for experimenting only because we are trying to reuse the object in the same session. It is useful to know that if you no longer need some objects, you can delete them. SummaryWe would recommend that you experiment and try more examples. Python made it easy to introduce concepts which are regarded as complex – object oriented programming (classes) and functional programming (list comprehensions). However, it will take a lot more experimentation to gain a proper comprehension and dream of the immense possibilities of solving complex problems simply. You can implement the loans class using a single dictionary. Keep the method names and parameters the same. Then, the programs using this class by relying only on the methods will not change. Pickle is great for saving state of an object, but it is not a substitute for storing dynamic data over long periods. We need to use a database for that. We will have to wait till next month when we alter our implementation of loans. |
Python For Friends >