An experienced VBA programmer here, that is starting the delve into Python OOP. I fear it is so simple, that I am having issues finding an answer without asking for help.
I have written the following code:
#Import packages
import openpyxl as xl
import os
class DataExtract:
#Initialize the class
def __init__(self,wb):
self.wb = wb
#Set class method to return sheet for named range
#classmethod
def rng_sht(cls,dest):
for title, coord in dest:
return(title)
#Set class method to return cell for named range
#classmethod
def rng_coord(cls,dest):
for title, coord in dest:
return(coord)
#Set class method to retun value of named range
#classmethod
def rng_val(cls,rng):
#Define destinations
dest = wb.get_named_range(rng).destinations
#Retrieve sheet
sht = DataExtract.rng_sht(dest)
coord = DataExtract.rng_coord(dest)
#Return value
return()
#Define workbook
wb = 'Test_WB'
#Initiate class
wb_cur = DataExtract(wb)
#Find temp for current sheet
Temp = wb_cur.rng_val('Temp')
I'm aware that my indentation is incorrect.
The issue that I am having is that when I call the rng_val class method, it is only returning the current value for the first method I call within (in this case, the "sht"). When I inactivate the "sht" line, the "coord" line functions correctly.
I suspect the issue is likely due to how I am calling class methods or how I have structured the class, but I am not sure.
Update
I have updated the code with feedback from all of you, with my script below. I am still having errors with exiting the loop in the rng_val class, which Max suggested yield to resolve. I attempted to fix to no avail.
#Import packages
import openpyxl as xl
import os
class DataExtract:
#Initialize the class
def __init__(self,wb):
self.wb = wb
#Set class method to return sheet for named range
#classmethod
def rng_sht(cls,dest):
for title, coord in dest:
return title
#Set class method to return cell for named range
#classmethod
def rng_coord(cls,dest):
for title, coord in dest:
return coord
#Set class method to retun value of named range
#classmethod
def rng_val(cls,wb,rng):
#Define destinations
dest = wb.get_named_range(rng).destinations
#Retrieve sheet
sht = cls.rng_sht(dest)
coord = cls.rng_coord(dest)
print(sht)
print(coord)
#Return value
return 1
path = 'C:\\Users\\User\\Desktop\\Python\\PracFiles\\'
#Loop through workbooks in a given folder
for i in os.listdir(path):
#Define workbook
wb = xl.load_workbook(path + i,data_only=True)
#Find temp for current sheet
Temp = DataExtract.rng_val(wb,'Temp')
There are several issues I can see that may have to do with your inexperience with Python OO.
The empty return statements cannot have parenthesis after them. That would cause the function to return an empty tuple instead of nothing.
If you plan on having your function return a value, place the value (or variable) immediately after the return statement like this: return coord. return on its own will just exit the function.
The first parameter in any class method contains the instance of that class when called via an object. You called it self in the constructor and cls in the other methods. They are the same thing. You haven't been using it in your code in places it looks like it should be. See below:
sht = DataExtract.rng_sht(dest)
coord = DataExtract.rng_coord(dest)
That would call the function rng_sht statically passing dest as an instance of DataExtract which I'm near certain isn't intended. What you should do instead is use cls.rng_sht(dest) to references the object instance. Also, you cannot access class fields by just referencing them by themselves, like wb.get_named_range(rng) where wb is a field in DataExtract. Instead reference it via cls like cls.wb.get_named_range(rng)
Related
I've never really used classes before, I just simply went the easy way (global variables), and now I would like to make my code right to avoid future complications.
This is my code:
from dearpygui.core import *
class Engine:
def __init__(self,serial,type,profile):
self.serial = serial
self.type = type
self.profile = profile
def apply_selected_file():
res = []
html_name= "example.html"
path= "C:/"
#Function that reads data from a file and saves selected data in a list
res = html_imp(path + '/' + html_name)
#I would like to remove the code below and use a class for each file instead
setvalue(sn1,es[0]) #shows a label with this value
setvalue(type1,res[1]) #shows a label with this value
setvalue(profile1,res[2]) #shows a label with this value
return res
def button():
#This was my initial idea but it doesn't seem to work.
# res = apply_selected_file()
# E = Engine(res[0],res[1],res[2])
I have in mind reading multiple HTML files so using a class would be much easier than declaring variables for each file:
1- Use apply_selected_file to read a file and assign values (s/n,type,profile) to a new class (E1,E2,E3,...,E20,...)
2- Use another function button() to access those stored class values.
I'm creating a custom class to store information about a CFD simulation results.
Right now the way it is set up is that it instantiates an empty class object, then used a method called load_mesh which calls an external function to read all the information about the mesh, and return a dictionary of all the information. The load_mesh method then assigns a bunch of class attributes from the values in the dictionary.
The problem is that I am planning to store alot more information than just the mesh, and I dont want to have like 1000 attributes to my class object. I want to store then in appropriate containers(?) that each have their own methods.
For example, my code looks like this currently (some stuff omitted that's unnecessary):
class CFD():
def __init__(self, infile=None):
self.file = infile
def load_mesh(self):
mesh = load_cfd_mesh(self) #calls outside function to load mesh info, uses self.file, returns dict
self.proj = mesh['proj']
self.static_items = mesh['static_items']
self.nnodes = mesh['nnodes']
self.node_coords = mesh['node_coords']
self.node_codes = mesh['node_codes']
self.nelements = mesh['nelements']
self.element_types = mesh['element_types_str']
self.node_connectivity = mesh['node_connectivity']
self.element_node_ids = mesh['element_node_ids']
self.element_coords = mesh['element_coords']
self.element_elevs = mesh['element_elevs']
self.horizontal_units = mesh['horizontal_units']
self.vertical_units = mesh['vertical_units']
test = CFD('testfile.txt') #instantiate
test.load_mesh() #load mesh information to attributes
Now, I can access any of the mesh information by doing:
test.proj
self.nnodes
self.coords
etc...
But want I want to do is store all of this information in test.mesh, where test.mesh has all of these attributes but also has the method test.mesh.load().
I THINK I can do something like this:
class CFD():
def __init__(self, infile=None):
self.file = infile
self.mesh = None
def load_mesh(self):
mesh = load_cfd_mesh(self) #calls outside function to load mesh info, uses self.file, returns dict
setattr(self.mesh, 'proj', mesh['proj'])
#etc....
then I'd be able to do:
test = CFD('testfile.txt') #instantiate
test.load_mesh() #load mesh information to attributes
test.mesh.proj
But I can't figure out how to add the load_mesh method to self.mesh?
How is it possible to achieve the following way of doing this:
test = CFD('testfile.txt') #instantiate
test.mesh.load() #load mesh information to attributes
test.mesh.proj
Do I have to define another class within the main class? Like class mesh(self):
Also, if my proposed way of adding attributes to self.mesh doesn't make sense..please help!
I think you might be looking for something like a property to lazily load the mesh when needed – I don't really see why there'd be an "empty" mesh object you explicitly have to .load():
class Mesh:
def __init__(self, filename):
mesh = load_cfd_mesh(filename)
self.proj = mesh["proj"]
self.static_items = mesh["static_items"]
# ...
class CFD:
def __init__(self, filename):
self.filename = filename
self._mesh = None
#property
def mesh(self):
if not self._mesh:
self._mesh = Mesh(self.filename)
return self._mesh
test = CFD("testfile.txt")
print(test.mesh.proj)
You can do that with an inner class (below is a simplified code for demonstrating):
class CFD:
class Mesh:
def __init__(self, file):
self._file = file
def load_mesh(self):
# implement here your own code...
print("loading from file", self._file)
self.proj = "PROJ"
def __init__(self, file):
self.mesh = self.__class__.Mesh(file)
Below I have defined a basic class with some functions.
I am not sure of the best way to pass data from one function to be used in another.
My solution was to pass a dataframe as a parameter to the function. I am sure there is a better and more technically correct way so please call it out.
What I am trying to understand is why some functions in the class require the () to be used when calling them and others you can call just using the function name.
Once a ASX object called "market" is initiated The two examples are:
market.all_companies()
Returns: a dataframe
market.valid_industry
returns a series
class YourError( Exception ): pass
class asx(object):
def __init__(self, name):
try:
#initialise
self.name = name
#all companies on asx downloaded from asx website csv
df = pd.read_csv('http://asx.com.au/asx/research/ASXListedCompanies.csv', skiprows=1)
df.columns = ["company","asx_code","industry"]
df["yahoo_code"] = df["asx_code"]+".AX"
self.companies = df
self.industry = self.all_industry(df)
self.valid_stocks = self.valid_stocks(df)
self.valid_industry = self.valid_industry(df)
except:
raise YourError("asx companies CSV not available")
def all_companies(self):
return self.companies
def valid_industry(self,df):
return df["industry"].value_counts()
def all_industry(self,df):
return df["industry"].value_counts()
def valid_stocks(self,df):
return df[(df["industry"]!= "Not Applic") & (df["industry"]!="Class Pend")]
market = asx("asx")
market.all_companies()
market.valid_industry
All functions require () but you're doing some nasty stuff in you __init__ where you replace function with a series.
self.valid_industry = self.valid_industry(df)
this will overwrite the function valid_industry to no longer be a function on the instance created but to be value returned from self.valid_industry(df)
don't use same name for member properties and methods and all will make sense.
For your methods you don't need to pass in df as argument as you have it assigned to self.companies so your
def valid_industry(self,df):
return df["industry"].value_counts()
becomes:
def valid_industry(self):
return self.companies["industry"].value_counts()
Here is the function (it's in a file, "worldmodel.py"):
def add_entity(world, entity):
pt = entities.get_position(entity)
if within_bounds(world, pt):
old_entity = occ_grid.get_cell(world.occupancy, pt)
if old_entity != None:
entities.clear_pending_actions(old_entity)
occ_grid.set_cell(world.occupancy, pt, entity)
world.entities.append(entity)
And here is the class in a file named, "occ_grid.py":
class Grid:
def __init__(self, width, height, occupancy_value):
self.width = width
self.height = height
self.cells = []
# initialize grid to all specified occupancy value
for row in range(0, self.height):
self.cells.append([])
for col in range(0, self.width):
self.cells[row].append(occupancy_value)
def set_cell(self, point, value):
self.cells[point.y][point.x] = value
My question is, how would I rewrite the line of code in "def add_entity" that refers to "set_cell"? (Now that I've made set_cell a method of the class Grid) NOTE: Before I made set_cell part of the grid class, it was a function outside of the class (but still in the same file as the class) Thanks!
You'll need to import occ_grid in your worldmodel.py, then instantiate a Grid object and call that objects set_cell()-method. The add_entity needs to get the Grid-object as its parameter unless it can safely instantiate new ones at will.
Here's a naive example which does not work but demonstrates what I mean:
import occ_grid
g = occ_grid.Grid(your_width, your_height, occupancy)
def add_entity(world, entity, grid):
pt = entities.get_position(entity)
if within_bounds(world, pt):
old_entity = grid.get_cell(world.occupancy, pt)
if old_entity != None:
entities.clear_pending_actions(old_entity)
grid.set_cell(world.occupancy, pt, entity)
world.entities.append(entity)
add_entity(world, entity, g)
Unless you make the set_cell function a static method of the Grid class, you're going to need and instance of Grid.
from occ_grid import Grid
I am going to make an assumption here, and say that your want your grid to be part of the world? Either way, this is an example of instantiating that class.
class World:
grid = Grid()
def add_entity(world, entity):
# All that other stuff.
world.grid.set_cell(pt, entity)
There are two issues here, (1) calling functions across modules and (2) calling methods of classes.
It seems you can already do (1).
The trick is that although methods are defined as
def methodName(self, ...)
They are called as
object.methodName(...)
And object implicitly becomes the "self" Parameter. Here is an example:
import occ_grid # Import the module (file) that contains Grid.
.
.
world.occupancy = occ_grid.Grid() # Create an instance of Grid.
.
.
def add_entity(world, entity):
pt = entities.get_position(entity)
.
.
world.occupancy.set_cell(pt, entity)
In this example, grid is a global variable, which is probably not a good design. I guess it should be a property of world, but that's only a guess.
I am trying to create a walker that goes through directories. Here are the inputs and outputs which I have partly working. I am using a test directory but I would like this to be done on any directory which is leading to some problems.
[IN]: print testdir #name of the directory
[OUT]: ['j','k','l'] #directories under testdir
[IN]: print testdir.j
[OUT]: ['m','n'] # Files under testdir.j
Here is the code so far:
class directory_lister:
"""Lists directories under root"""
def __init__(self,path):
self.path = path
self.ex = []
for item in os.listdir(path):
self.ex.append(item)
def __repr__(self):
return repr(self.ex)
This returns the directories and files but I have to manually assign the names of the directories.
testdir = directory_lister(path/to/testdir)
j = directory_lister(path/to/j)
etc
Is there a way to automate instances such that:
for root,dirs,files in os.walk(/path/to/testdir/):
for x in dirs:
x = directory_lister(root) #I want j = directory_lister(path/to/j), k = directory_lister(path/to/k) and l = directory_lister(path/to/l) here.
Can there be a:
class directory_lister:
def __init__(self,path):
self.path = path
self.j = directory_lister(path + os.sep + j) # how to automate this attribute of the class when assigned to an instance??
The code above is wrong as the object x only becomes an instance but j,k,l have to be defined manually. Do I have to use another class or a dictionary with getattr but I always run into the same problem. If any extra information is required please ask, I hope I made this clear.
UPDATE 2
Is there a way to add other complex functions to the DirLister by Anurag below? So when it gets to a file say testdir/j/p, it prints out the first line of file p.
[IN] print testdir.j.p
[OUT] First Line of p
I have made a class for printing out the first line of the file:
class File:
def __init__(self, path):
"""Read the first line in desired path"""
self.path = path
f = open(path, 'r')
self.first_line = f.readline()
f.close()
def __repr__(self):
"""Display the first line"""
return self.first_line
Just need to know how to incorporate it in the class below. Thank you.
I assume you want sub-dir to be accessible like a attribute, you can achieve that two ways
Go thru list of files and create variables dynamically
Hook into attribute access and correctly return listers as needed
I prefer second approach as it is lazy, better and easier to implement
import os
class DirLister(object):
def __init__(self, root):
self.root = root
self._list = None
def __getattr__(self, name):
try:
var = super(DirLister).__getattr__(self, name)
return var
except AttributeError:
return DirLister(os.path.join(self.root, name))
def __str__(self):
self._load()
return str(self._list)
def _load(self):
"""
load once when needed
"""
if self._list is not None:
return
self._list = os.listdir(self.root) # list root someway
root = DirLister("/")
print root.etc.apache2
output:
['mods-enabled', 'sites-80', 'mods-available', 'ports.conf', 'envvars', 'httpd.conf', 'sites-available', 'conf.d', 'magic', 'apache2.conf', 'sites-enabled']
You can improve this to have better error checking etc
Code explanation: this is basically a recursive listing of directory, so a DirLister objects lists files under the given root and if some variable is accessed with dotted notation it returns a DirLister assuming that that attribute is a folder under the root. So if we try to create DirLister class step by step it will be more clear
1- A simple DirLister which just lists files/folders under it
class DirLister(object):
def __init__(self, root):
self.root = root
self._list = os.listdir(self.root)
2- Our simple lister just list files one level deep, if we want to get filers under subfolders we can hook into __getattr__ which is called with varname when obj.varname is used. So if our dir-lister doesn't have a attribute named varname we assume user is trying to access that directory under given root, so we create another DirLister whose root is root+subdirname
def __getattr__(self, name):
try:
var = super(DirLister).__getattr__(self, name)
return var
except AttributeError:
return DirLister(os.path.join(self.root, name))
Note: first we check base class for that attribute because we don't want to treat all variable access as sub-dir access, if there is no such attribute hence AttributeError then we create a new DirLister for sub-folder.
3- To improve code so that we don't list all folders even if user did not ask for them, we only list when user requires, hence a load method
def _load(self):
if self._list is not None:
return
self._list = os.listdir(self.root) # list root someway
so this method lists dir if not already listed, and this should be called when we finally need it e.g. while printing the list
Edit: as asked by OP here is the alternate method of recursively list whole tree though I would strongly recommend against it
import os
class RecursiveDirLister(object):
def __init__(self, root):
self._sublist = []
for folder in os.listdir(root):
self._sublist.append(folder)
path = os.path.join(root, folder)
if not os.path.isdir(path):
continue
# add it as attribute, assuming that dir-name is valid python varname
try:
sublister = RecursiveDirLister(path)
except OSError:
continue#ignore permission errors etc
setattr(self, folder, sublister)
def __str__(self):
return str(self._sublist)
etc = RecursiveDirLister("/etc")
print etc.fonts
output:
['conf.avail', 'conf.d', 'fonts.conf', 'fonts.dtd']
Not sure what you're asking, but would this work?
for root,dirs,files in os.walk(/path/to/testdir/):
listers = dict((dir, directory_lister(dir)) for dir in dirs)
#now you can use:
listers['j']
listers['k']
listers['l']