Jython Class Inheritance - Can you initialize a class with an instance of the base class?

Hello,

This is a programming question, not so much Fiji specific. I want to extend and wrap the functionality of certain classes of the ImageJ API with Jython to make things a bit feel more Pythonic in my scripts. I can create a new object of a type inheriting from a base class without difficulty, but I am a bit confused as to how you can initialize a new object using an existing instance of the base class. To illustrate, here is an example using the ResultsTable and wrapping it into a DictTable which extends the ResultsTable class by providing a __getitem__ method for dict like access to the columns.

Create and work with a new instance of the super class - easy enough…

Jython Script…

import ij.measure.ResultsTable

# A class wrapping the ResultsTable to behave more dict-like...
class Spreadsheet(ij.measure.ResultsTable):
	def __init__(self):
			super(Spreadsheet, self).__init__()
	def __getitem__(self, key):
		return(list(self.getColumnAsVariables(key)))

# Create an instance of it...
ss = Spreadsheet()

# Add some data in there...
for i in range(1,11,1):
	ss.incrementCounter()
	ss.addValue("C1","row %s" % i)

# Display it thanks to the ResultsTable base class...
ss.show("Test")

# Fetch rows 1-5 using Python dict/list syntax...
print ss["C1"][0:5]

Output…

[“row 1”, “row 2”, “row 3”, “row 4”, “row 5”]

Wrapping an existing ResultsTable into a Spreadsheet

However, say I already have an instance of ResultsTable, can I initialize the Spreadsheet object with it without having to repopulate the empty table of the Spreadsheet? Copying the information over is easy enough, but I would imagine you should be able to since the base class is initialized when the in parent class instance is created. At the end it would behave something like this:

# Create a ResultsTable
rt = ij.measure.ResultsTable()

# Do a bunch of stuff with the results table...
rt.incrementCounter()
rt.addValue("C1", "Some stuff")

# Now lets make it a Spreadsheet instead...
ss = Spreadsheet(rt)
ss.show("A Spreadsheet")
print(ss["C1"])

Let me know if I can clarify the problem better. Also, any help is appreciated including links out to related problems and tutorials. My internet scouring didn’t turn up anything, but I could be using lousy search terms.

Cheers,
Andrew

I am not a Python expert, but from some brief research: have you tried using composition instead of inheritance?

  • Make Spreadsheet have a member results (which in practice will be of type ResultsTable).
  • Implement a constructor i.e. __init__(self, results) so the existing ResultsTable instance can be wrapped as a Spreadsheet.
  • Implement the special __getattr__(self, attr) method in Spreadsheet to return getattr(self.results, attr). This will allow your Spreadsheet object to delegate all not-overridden functionality to the wrapped results member.

See this blog post for further details and explanation.

Maybe Python experts here have better suggestions.

1 Like

Hi @ctrueden,

Thank you. That seems to solve the problem for me. I had not heard of composition before, but that would appear to be what I have been looking for. If it acts as a useful example for anyone else, the following example provides the wrapping functionality using composition and the __getattr__ method.

import ij.measure.ResultsTable

# A class wrapping the ResultsTable to behave more dict-like...
class Spreadsheet():
	def __init__(self, rt=None):
		if rt == None:
			self.table = ij.measure.ResultsTable()
		else:
			self.table = rt
	
	def __getitem__(self, key):
		return(list(self.getColumnAsVariables(key)))

	def __getattr__(self, attr):
		return(getattr(self.table, attr))

# Create an instance of it and a results table...
ss = Spreadsheet()
rt = ij.measure.ResultsTable()

# Add some data in there...
for i in range(1,11,1):
	ss.incrementCounter()
	rt.incrementCounter()
	ss.addValue("C1","ss row %s" % i)
	rt.addValue("C1","rt row %s" % i)

# Wrap the existing ResultsTable
ss2 = Spreadsheet(rt)

# Display it both...
ss.show("Spreadsheet")
ss2.show("Wrapped ResultsTable")

# Nice access...
print("Rows 1-5 of ss:  %s"%ss["C1"][0:5])
print("Rows 1-2 of ss2: %s"%ss2["C1"][0:2])

This script will print out the following, as expected. It also provides nice access to the member variable table (a ResultsTable) for using all of its functionality.

Rows 1-5 of ss: [“ss row 1”, “ss row 2”, “ss row 3”, “ss row 4”, “ss row 5”]
Rows 1-2 of ss2: [“rt row 1”, “rt row 2”]

Cheers,
Andrew

1 Like