OU blog

Personal Blogs

Attachments:
application/zipRecords.zip
ExLibris

Algorithms 9: Records

Visible to anyone in the world
Edited by Martin Thomas Humby, Tuesday, 19 Jan 2016, 23:01

Again Python offers no exact equivalent of a record type and to get a feel for this primitive we must look elsewhere. A Pascal record definition for use in an address book might be

type
TPerson = record
given_name, other_name, family_name: string;
address: record
house_number: integer;
street, postcode: string;
email: TEmail;
phone: string;
mobile: string;
   end;

Having defined this type instances can be declared in a Pascal program:

var
Johny: TPerson;
all_friends: array[0..current_friends] of TPerson;
next_to_invite: TPerson;

These variables are simply a place-makers: none of their fields have a value or at least not one that is any use. To access the fields and assign values the field name is used placed after a dot:

begin
Johny.given_name := 'Jonathan';
Johny.other_name := 'N/A'
Johny.family_name := 'Hansen'
Johny.address.house_number := 22
// etc.

This works both ways. Having assigned values they can be retrieved with this dot notation:

   next_to_invite := Johny;
invite_name := next_to_invite.given_name + ' ' + next_to_invite.family_name;

The utility of records is now apparent. To store the different fields in separate arrays would need the same number of 'parallel' vectors as there are fields in the record. Reverting back to Python and assuming each array has been given the same name as a field:

from array_types import array_
current_people = 50
people_given_names = array_(current_people)
people_other_names = array_(current_people)
people_family_names = array_(current_people)
people_address_house_number = array_(current_people)
# etc.......

current_person = 0
people_given_names[current_person] = 'Jonathan'
people_other_names[current_person] = 'N/A'
people_family_names[current_person] = 'Hansen'
people_address_house_number[current_person] = 22
# etc.......

next_to_invite = 0
invite_name = people_given_names[next_to_invite] + ' ' + people_family_names[next_to_invite]
print(invite_name)
# etc.......


Python records

The nearest type to a record Python has on offer is a class. Classes are an OOP construct and currently OOP will be avoided as far as possible. Understanding functions, variables referencing a function and records first, OOP requires only two additional very simple concepts.

The current problem is that in Python there are no free variables. When a variable is declared it must be bound to some object by assigning a value to it. Doing this also defines its current type or perhaps more accurately the variable itself is typeless and only the data it references has a type. In the Pascal example, a variable declaration got a free variable but type had to be specified and only objects of that type could be assigned subsequently.

Additionally, Python variables that belong to an instance of a class can only be declared by assigning a value to them in a function that belongs to the instance (an instance method in OOP terms). Before the method is called the variables do not exist. There is a special method called __init__(self) intended for initialization and called automatically when an instance is created so it is convenient to use this method. The variable self refers to the instance and must also precede the field names declared in the method: 

class Address:
    def __init__(self, house_number, street, postcode):
self.house_number = house_number
self.street = street
self.postcode = postcode

class Person:
    def __init__(self, given_name, family_name, other_name='',
house_number='', street='', postcode='',
email='', phone='', mobile=''):
self.given_name = given_name
self.other_name = other_name
self.family_name = family_name
self.address = Address(house_number, street, postcode)
self.email = email
self.phone = phone
self.mobile = mobile


The number of arguments __init__() is written to accept, either positionally or as named arguments, is of course optional. It could have no arguments (other than self) and initialize all fields to None or some other default. It is invoked by calling the class name as a function thereby creating and initializing an instance of the type:

a_person = Person('Jonathan', 'Hansen')

When all fields have been initialized to some value they can be updated and accessed exactly as for the Pascal record.

a_person.email = 'jhansen@concom.com'
a_person.address.house_number = 22
a_person.address.street= 'Nurdle Lee'
print(a_person.given_name, a_person.family_name, a_person.address.house_number,
a_person.address.street, a_person.email)

Used in this way records can provide data structures of some complexity that are simplicity itself to create and access.


Summary

Records provide a means of accessing fields with diverse types by field-name. All fields exist in a single entity and the unwanted complication of holding these elements in parallel arrays is removed. The nearest Python record equivalent is in fact an instance of an OOP class having no instance methods other than  __init__(), required because Python does not feature free variables.

A Pascal record was used as an example of a basic record type. But there are a number of differences between a record in Pascal (or in C, C++, ALGOL) and a Python equivalent. Pascal records are by-value. A record and most other basic types exist at the declared variable. Initially values are null or undefined and must be given a value before they can be used. Assignment from one variable to another means copying all fields to the new location. A Pascal by-reference type must be declared and initialized as such. Not so in Python where all types are by-reference with transparent access as discussed above and in Algorithms 3 in connection with mutability.

In common with most programming languages Pascal features static typing the type of variables is declared in the code and checked by the compiler at compile time. With certain relaxations resulting from type casting and OOP inheritance, only objects of a variable's declared type can be assigned.

Python provides dynamic typing: the type and suitability of an object for processing by a particular function is checked at runtime when the code is executed. Objects of any type can be assigned to a variable and passed to any function but an error or unplanned results will be seen if an object is not of the intended type or types. 

To demonstrate the simplicity of records I have put my money where my mouth is and implemented a simple address-book application that does indeed read records from a file on start-up and save any modifications on shut-down. Clearly some additional fields would be helpful in a real address-book but I have stuck with a few less than those shown previously. The code is in 'address_book.py' in the download.

Points of interest might be that here are no global variables and no side-effects modification of objects passed as arguments apart from the array of records used to store details of people in the book. This array can be seen as an abstract data type defined by indexing and assignment. There is no GUI and the application is menu driven, perhaps a reminder of those older simpler days. If you give the application a try do not forget to click in the Run pane to locate the cursor there. I always forget to do this and end up typing commands into the editor and messing up the script.


[Algorithms 9: Records (c) Martin Humby 2015]

Permalink Add your comment
Share post