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
    CommentedFeb 15, 2020 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
    CommentedFeb 15, 2020 at 15:56

1 Answer 1

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
    CommentedFeb 15, 2020 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
    CommentedFeb 15, 2020 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
    CommentedFeb 15, 2020 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
    CommentedFeb 15, 2020 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
    CommentedFeb 15, 2020 at 15:49

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.