OU blog

Personal Blogs

ExLibris

Python 1: How to use a class as a table

Visible to anyone in the world
Edited by Martin Thomas Humby, Wednesday, 31 Oct 2018, 23:07

There is a simple and perhaps obvious relationship between a class of objects, a database table and the table represented as a two dimensional array in a programming language: the class is the table and every instance of the class forms a row in that table. Using Python class to array conversion is equally simple.

Basic Python does not provide multi-dimensional arrays in the C / pascal static-array sense but goes straight to the more flexible but less computationally efficient lists of lists. The Numpy extension does support these types directly so, to keep things simple, it is convenient to use Numpy. A two dimensional can be declared as follows:

import numpy as np
array = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

The numpy.ndarray returned by this call displays itself as

array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])

np.array() iterates the Python list of lists to get this result.

Another extension, Pandas, built on Numpy, moves closer to a database table by supplying an index of column names and an index of rows:

import pandas as pd
series3 = pd.DataFrame(array, columns=['from_1', 'from_2', 'from_3'])

To allow a list of objects to be converted to a Numpy array or Pandas DataFrame, objects need an __iter__() method that returns each desired attribute of the object replacing the sublists in the abpve example. Optionally a __str__() method to supply a string representation when an instance is seen in a column and a class  method columns() that returns a list of column names can also be provided:

class Person:
def __init__(self, name, age, notes):
self.name = name
self.age = age
self.notes = notes
900
def __iter__(self):
yield self
yield self.name
yield self.age
yield self.notes

def hello(self):
return "Hello! I'm " + self.name + '. ' + self.notes

def __str__(self):
return 'Person: ' + self.name

@classmethod
def columns(cls):
return['Items', 'name', 'age', 'notes']

Now we need some kind of storage class to hold the table. Thinking that we might want to select items from the structure by name a dictionary suits:

class Struct:
    def __init__(self):
        self.facet = {}

    def add_to_facet(self, item):
        self.facet[item.name] = item

    @property
    def items(self):
        return pd.DataFrame(list(self.facet.values()), columns=Person.columns())

Putting it together:

people = Struct()
people.add_to_facet(Person('Jim', 42, 'The time has come for all good men to come to the aid of the party'))
people.add_to_facet(Person('Jane', 24, 'She sells sea shells on the sea shore'))
people.add_to_facet(Person('Jill', 17, 'The quick brown fox jumps over the lazy dog'))
print(people.items)


Items are objects:

df = people.items

 

obj = df.Items[0]

 

print(obj.hello())

"Hello! I'm Jill. The quick brown fox jumps over the lazy dog"


So, that is a basic structure with one facet. In a real application with multiple facets represented by multiple dictionaries it may be useful to be able to select the object attributes returned to build a table on the fly:

# modify the DataFrame columns returned by observations
def observations_columns(self, *fields, columns=0):
self._observations_df = None
s = 'def f(self):pass'
for fld in fields:
if fld == 'observation' or fld == '' or fld == 'self':
s += ';yield self'
else:
s += ';yield self.' + fld
s += '\nObservation.__iter__=f'
exec(s)
if columns != 0: # allow for None columns
Observation._cols = columns
else:
names = list(f)
for i, name in enumerate(names):
names[i] = names[i].replace('.', '_')
Observation._cols = names


Without the complications from joining tables, intermediate tables to represent variable content fields or combining documents, the power of OOP allows attributes to be selected from the immediate object or from other objects referenced from it down to any level.

Permalink Add your comment
Share post

This blog might contain posts that are only visible to logged-in users, or where only logged-in users can comment. If you have an account on the system, please log in for full access.

Total visits to this blog: 74842