Reputation: 559
Nothing scares me more than the Python Class concept; and recently I have been trying to use/create classes to understand their purpose, structure and features, etc. However, I am not clear about the concept of class and how to create them.
Please take a look at the following example:
class Prob(object):
def __init__ (self,filename):
self.file_contents = read_file(filename)
def prob_build(self):
self.problem, self.aux_vars = build_problem(file_contents)
first_object = Prob(some_file)
alpha,beta, gamma = first_object.prob_build()
In this case, read_file
, build_problem
are custom functions that read data from a CSV file and build a PuLP-based linear problem, respectively. Now, my understanding is that when I initialize an object based on my Prob
class, the file_contents are available for the class to use internally. In that case, how do I get the variables alpha
, beta
and gamma
? For the current code, I get a TypeError: 'NoneType' object is not iterable.
I have tested the functions and I know that they work without any error, which leaves me thinking that there is something wrong with the way I have defined the class.
Any suggestions?
Upvotes: 0
Views: 374
Reputation: 77952
Nothing scares me more than the Python Class concept;
This is actually not a Python concept - classes exist in most object oriented languages.
and recently I have been trying (...) to understand their purpose, structure and features, etc. However, I am not clear about the concept of class
Before we talk about classes, you have to understand objects. An object is a way to group together a state (a set of data) and a behavior (a set of functions acting on the state or according to the state). Now this is a bit of an abstract definition so let's see how it works with a simple example - a geometric point in a 2d space.
For the state part, a 2d point is defined by it's x and y coordinates. You can represent this with a dict
:
my_point = {"x": 0, "y": 0}
Ok, fine but not very explicit and a bit error prone. We can start with a function that is responsible for creating a new point:
def new_point(x=0, y=0):
return {"x": x, "y": y}
p1 = new_point()
p2 = new_point(42, 84)
Now we can build points without have to worry on the gory details. Ok, now let's a bit of behavior... A first useful function would be to check whether two points are equal (let's say they are equal if they have the same coordinates):
def points_are_equal(p1, p2):
return p1["x"] == p2["x"] and p1["y"] == p2["y"]
You can see that this behavior depends on both points states.
We could also want to move a point along the horizontal axis:
def move_x(p, distance):
p["x"] += distance
or along the vertical axis:
def move_y(p, distance):
p["y"] += distance
or both at the same time:
def move_by(p, x_distance, y_distance):
move_x(p, x_distance)
move_y(p, y_distance)
Notice that here, the behavior is to change the point's state.
And of course we want to have a way to get the point's x or y coordinates:
def get_x(p):
return p["x"]
def get_y(p)
return p["y"]
What we've built here is what is known as an "abstract data type": instead of manually building a dict, manually comparing two dicts, manually updating our dict and manually checking it's state, we have defined a set a function to do all this, more or less hiding the internal representation.
and how to create them.
A class is, mostly, another way to do the same thing, but with a lot of other goodness. Let's rewrite our "point" datatype as a Python class:
class Point(object):
# this is the function that creates a new point
def __init__(self, x=0, y=0):
self.x = x
self.y = y
# equality test:
def __eq__(self, other):
return self.x == other.x and self.y == other.y
# move
def move_x(self, distance):
self.x += distance
def move_y(self, distance):
self.y += distance
def move_by(self, x_distance, y_distance):
self.move_x(x_distance)
self.move_y(y_distance)
And we don't actually need to write get_x()
nor get_y()
, we can directly access x and y:
p = Point(2, 5)
print(p.x)
print(p.y)
p.move_by(3, 1)
print(p.x)
print(p.y)
p2 = Point(p.x, p.y)
print(p == p2) # => True
p2.move_x(3)
print(p == p2) # => False
Actually, behind the hood, our p
object is a dict:
print(p.__dict__)
Other OOPLs might use other ways to store an object's state (structs for C-like languages for example), but in Python an object is actually mainly a dict. Well, a dict plus a class:
print(p.__class__)
and a set of "attribute lookup rules" (provided by the base class object
) that will first lookup attributes on the object's __dict__
then on the object's class (which is how p.move_x(42)
is actually interpreted as Point.move_x(p, 42)
.
Classes and objects provide a lot of other goodies (inheritance etc), but basically they are just this: a dict (which stores the state) and a class (which stores the behavior).
Now for your example:
my understanding is that when I initialize an object based on my Prob class, the file_contents are available for the class to use internally
file_contents
is available for the instance - and the class functions can access it on the current instance - which is the self
parameter. IOW, your build_prob
function should use self.file_contents
:
def prob_build(self):
self.problem, self.aux_vars = build_problem(self.file_contents)
Then you can access the problem
and aux_vars
on your instance:
first_object = Prob(some_file)
first_object.prob_build()
print(first_object.problem)
print(first_object.aux_vars)
Just note that the problem
and aux_vars
attributes only exist after you called prob_build
. This is considered as bad practice, since you can get an AttributeError
:
first_object = Prob(some_file)
# Doesn't work !!!
print(first_object.problem)
A first step to fix this would be to initialize those attributes in the __init__
method (yes, that's why it's called "init"):
class Prob(object):
def __init__ (self,filename):
self.file_contents = read_file(filename)
self.problem = None
self.aux_vars = None
def prob_build(self):
self.problem, self.aux_vars = build_problem(self.file_contents)
but that's hardly better - you still need to call yourobj.prob_build()
to have a usable state. The obvious fix here is to do all the initialization in the initializer and get rid of prob_build
:
class Prob(object):
def __init__ (self,filename):
self.file_contents = read_file(filename)
self.problem, self.aux_vars = build_problem(self.file_contents)
but then you can ask yourself: what's the point of this class if it has no behavior, and all you do is:
prob = Prob("path/to/file.csv")
prob, aux_vars = prob.problem, prob.aux_vars
result = do_something_with(prob, aux_vars)
You could as well replace it with a simple function:
def build_problem_from_file(path):
return build_problem(read_file(path))
prob, aux_vars = build_problem_from_file(...)
result = do_something_with(prob, aux_vars)
As a general rule, if your class as either no state or no behavior, chances are you don't need a class. There are exceptions to this rule of course but this is still a good guideline. In your case, the hypothetical do_something_with(prob, aux_vars)
might be a method too:
class Prob(object):
def __init__ (self,filename):
self.file_contents = read_file(filename)
self.problem, self.aux_vars = build_problem(self.file_contents)
def do_something(self):
# some computations here using self.problem and self.aux_vars
return result
prob = Prob("path/to/file.csv")
result = prob.do_something()
but if that's the only behavior, you still don't need a class:
def build_problem_from_file(path):
return build_problem(read_file(path))
def resolve_problem_from_file(path):
prob, aux_vars = build_problem_from_file(...)
return do_something_with(prob, aux_vars)
result = resolve_problem_from_file(...)
So to make a long story short: ask yourself if and why you want a class. OOP is a good solution for some problems but is not the solution to all problems.
Upvotes: 1
Reputation: 189958
Your prob_build
method needs to return
three values. You are currently not explicitly return
ing anything so it implicitly returns the single value None
, and Python tries to parse that into three values, and obviously failing.
This has nothing to do with the fact that this is a method of a class; functions return values regardless of how and where they are defined.
It's perfectly okay for functions to not return anything, of course; but then obviously it doesn't produce a result you can obtain with something like variable = func()
Upvotes: 1
Reputation: 228
Your class function prob_build() does not return anything. It only sets variables internal to the class.
Upvotes: 0
Reputation: 444
Similar to normal functions
, class methods
need to return something if you want the results! In your code, prob_build
doesn't return anything! So, it should be something like this:
def prob_build(self):
self.problem, self.aux_vars = build_problem(file_contents)
return (self.problem, self.aux_vars)
Note that in this case, it returns two arguments. You should be more specific on what alpha
, beta
, and gamma
are!
Upvotes: 0