1
\$\begingroup\$

I am a novice of Python and I am trying to understand how to use OOP paradigm to improve my scripts. My scripts usually read some data from a formatted csv file and perform some numerical analysis. Here is a short example:

SIMPLY SUPPORTED BEAM 
NNODES <NNnodes><Node,X,Y>
2
1,0,0
2,1,0

This input is for a script for solving a structural analysis problem. Generally one may want to extend the functionalities of the script adding new features, for instance adding elements, materials, section properties and so on.

My idea is to set up the code in such way one can include those functionalities in the easiest possible way.

My solution is to create a class, and then extend it using inheritance. Here what I wrote so far:

  • A class for the properties of the problem, named Mesh
  • Two initial instance variables storing the name of the input file and the title (first row of the csv file)
  • A method for reading information from the file, named read_input
class Mesh() :
    'class defining the property of the problem'

    def __init__(self) :
        self.name = None
        self.title = None

    def read_input(self, input_name) :
        self.name = input_name
        with open(self.name) as file :
            self.title = file.readline()

Let's say that one may want to improve the script including the number of nodes (NNODES in the csv file). First I create a class for node, storing ID and coordinates:

class Node() :
    'class for defining a node'

    def __init__(self, input1 , input2 , input3) :
        self.ID = int(input1)
        self.x = float(input2)
        self.y = float(input3)

Then I add "functionalities" to the class defining a new child class (with the same name of the parent class, because I don't actually need a new child class, just a redefinition of the initial Mesh class ). The new class reads:

  • New constructor, using super() to include the parent class constructor and new instance variables.
  • Redefinition of the read_input method to include the new lines of the csv file.
class Mesh(Mesh) :
    'Mesh that includes nodal loads'

    def __init__ (self) :
        super().__init__()
        self.Nnodes = None
        self.nodes = list()

    def read_input(self, input_name) :
        super().read_input(input_name)
        with open(self.name) as file :
            while not file.readline().startswith('NNODES') : continue
            self.Nnodes = int( file.readline() )
            for node in range(0,self.Nnodes) :
                ID , x, y = file.readline().split(',')
                self.nodes.append( Node( ID , x, y ) )

Finally, the initialization of the class and the use of the method to read the file:

input = Mesh()
input.read_input('input_short.txt')

Here my doubts:

  • Is it a good application of the OOP paradigm or I have totally misunderstood the concept?
  • Does it have any sense to call the child class with the same name of the parent class? (I did not find any examples in previous questions)
  • Is it a good way to structure the code? Maybe other people than me will modify the code in future, and I would like to simplifies this procedure as much as possible.

I have always used procedural languages, such as C and FORTRAN, and I don't have any experience in OOP paradigm.

EDIT I modified the code with suggestions from StackOverflow

  1. Modification of the class Mesh, in this way it should be self-contained.
  2. Child class name different from the parent one.
  3. Use of dataclass for defining the Node class.

Code:

from dataclasses import dataclass

class Mesh() :
    'class defining the property of the problem'

    def __init__(self , input1) :
        self.title = input1

name = 'input_short.txt'''
with open(name) as file :
    mesh1 = Mesh( file.readline() )

#let's add a new functionality
@dataclass
class Node:
    'class for defining a node'

    ID: int
    x: float
    y: float


class Mesh_nodes(Mesh) :
    'Extend Mesh class to account for nodes'

    def __init__(self , input1, input2, input3) :
        super().__init__(input1)
        self.Nnodes = input2
        self.nodes = input3

name = 'input_short.txt'''
with open(name) as file :
    while not file.readline().startswith('NNODES') : continue
    Nnodes = int( file.readline() )
    nodes = list()
    for node in range(0,Nnodes) :
        ID , x, y = file.readline().split(',')
        nodes.append( Node( int(ID) , float(x), float(y) ) )

mesh1 = Mesh_nodes(name,Nnodes,nodes)

Anyway, my original questions are still valid:

  • Is it a good application of the OOP paradigm or I have totally misunderstood the concept?

  • Is it a good way to structure the code? Maybe other people than me will modify the code in future, and I would like to simplifies this procedure as much as possible.

\$\endgroup\$
2
  • 3
    \$\begingroup\$ The edit makes it apparent that the code doesn't work as intended. SECTIONS is not handled by the provided code. \$\endgroup\$
    – Peilonrayz
    Feb 15 '20 at 15:09
  • 1
    \$\begingroup\$ "I am just wondering if this is possible or not." Yes, it's possible. The answer isn't super(). Get the code working with "Edit 2" and then add another object and get it working with that. Once you have it working with 3 objects move them around and see how you can get it to work - you'll notice the while ignore loop is a problem. \$\endgroup\$
    – Peilonrayz
    Feb 15 '20 at 15:56
4
\$\begingroup\$

Your code can probably utilize OOP. However, the way you're currently using objects is not what I'd call good.

But before we start adding in complicated objects, it looks like we can do everything you want with a couple of functions:

  1. Read the title of the document:

    self.title = file.readline()
    
  2. You read until you get to NNODES:

    while not file.readline().startswith('NNODES') : continue
    
  3. You read the amount of lines to read:

    self.Nnodes = int( file.readline() )
    
  4. You read the CSV:

    for node in range(0,self.Nnodes) :
        ID , x, y = file.readline().split(',')
        self.nodes.append( Node( ID , x, y ) )
    

All of 2-4 seems to be related to how you read NNODES objects, so we can just call them in the same function.

def load(file):
    return {
        'title': file.read_line(),
        'nnodes': _load_nnode(file),
    }


_NNODE_KEYS = ['ID', 'x', 'y']


def _load_nnode(file):
    while not file.readline().startswith('NNODES'):
        continue
    amount = int(file.readline())
    values = []
    for node in range(amount):
        values.append(dict(zip(
            _NNODE_KEYS,
            file.readline().split(',')
        )))
    return values


with open('input_short.txt') as f:
    data = load(f)
    import pprint
    pprint.pprint(data)
{
    'title': 'SIMPLY SUPPORTED BEAM',
    'nnodes': [
        {'ID': '1', 'x': '0', 'y': '0'},
        {'ID': '2', 'x': '1', 'y': '0'},
    ]
}

I can understand how OOP would help here, but your current method isn't great.

Inheritance isn't really going to help here. All you need to do is make your objects have a load method like the above. This load method should be a staticmethod so that it's in charge of instantiating the objects. It seriously just needs to be that simple.

from __future__ import annotations
from dataclasses import dataclass


@dataclass
class Mesh:
    title: str
    nnode: NNode

    @classmethod
    def load(cls, file):
        return cls(
            file.readline(),
            NNode.load(file),
        )


@dataclass
class NNode:
    nodes: List[dict]

    @classmethod
    def load(cls, file):
        while not file.readline().startswith('NNODES'):
            continue
        amount = int(file.readline())
        values = []
        for node in range(amount):
            values.append(dict(zip(
                _NNODE_KEYS,
                file.readline().split(',')
            )))
        return cls(values)

Now this could be a cool little file reader project you have. But the thing is, that you've not really implemented much.

  1. You ignore vital data with a while ignore loop.
  2. You ignore what the data should look like, you don't parse NNODES <NNnodes><Node,X,Y>.
  3. You assume everything is the exact same format, you haven't implemented a way to allow different classes.

Overall I'm not going to write these things for you. I suggest you try your arm at them with the above code.

\$\endgroup\$
5
  • \$\begingroup\$ Your code for sure is better than mine, but that's not exactly what i'm looking for. I made a small edit to the question. \$\endgroup\$
    – Stefano
    Feb 15 '20 at 15:06
  • \$\begingroup\$ @Stefano As addressed at the bottom of my answer, you have not written that and so I'm not going to create something you can't be arsed to implement yourself. \$\endgroup\$
    – Peilonrayz
    Feb 15 '20 at 15:08
  • \$\begingroup\$ Of course you are right, but I don't want you to write the code for me. Probably my edit 2 is bad written \$\endgroup\$
    – Stefano
    Feb 15 '20 at 15:20
  • \$\begingroup\$ @Stefano "but I don't know how to start to find a solution." shows the code not working as you intend, and all you're asking is "give me a solution to my problem". That is off-topic here, as your code should be working the way you intend it to; it's clear it's not. \$\endgroup\$
    – Peilonrayz
    Feb 15 '20 at 15:28
  • \$\begingroup\$ The problem is that I don't really know where to find something. I looked at previous questions, I looked in the manuals, I looked at some guides on internet. I'll try to make a code that does not work as I would like \$\endgroup\$
    – Stefano
    Feb 15 '20 at 15:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.