Firstly, thank you all for your help. But I do not understand how should I change this code
I have started modifying your code so that it would run without errors, but there are a few other mistakes in there as well, and I have not tried to make sense of all your parameters.
It would look something like this, but I will explain below.
# --- OP's attempt that fails ---
# BinSearch.objtobin(obj_function=max(objectivelist))
# -- -- -- -- -- -- -- -- -- -- --
# --- Using an instance ---
figure_this_out_yourself = 100
# this variable is a placeholder for any parameters I had to supply
myBinSearchInstance = BinSearch(
length = figure_this_out_yourself,
leng = [figure_this_out_yourself],
r = figure_this_out_yourself,
obj_function = figure_this_out_yourself,
middle = figure_this_out_yourself)
myBinSearchInstance.objtobin(obj_function = max(objectivelist))
There is one important concept to be grasped here: self
.
Let us consider this simple example function here, which shall always output a number one larger than last time.
counter = 0
def our_function ():
global counter
counter = counter + 1
return counter
print(our_function())
It is okay as it is, but it uses a global variable to keep track of its state. Imagine using it for two different purposes at the same time. That would be chaos!
So instead, we package this inside a class.
# unfinished apprach
counter = 0
class OurClass:
# This is called a static method
def our_function ():
global counter
counter = counter + 1
return counter
print(our_function())
When we try to run this, we run into a problem.
NameError: name our_function
is not defined
This happens because it is now accessible only within that class. So we need to call it as
print(OurClass.our_function())
That makes it okay to have functions with the same name around - as long as they are in different classes - but it does not solve our chaos for using our_function
multiple times at once. What we want is basically to have two independent counter
variables. This is where instances come into play: Of course we could manually create a second function that uses a second global variable, but that gets out of hand quickly when you use it more and more.
So let's move counter
inside our class.
class OurClass:
counter = 0
def our_function ():
global counter
counter = counter + 1
return counter
You guessed it - now counter
is no longer defined:
NameError: name counter
is not defined
So let us pass the instance variable that we want to use into the function as a parameter. And then use that instance to get its counter:
class OurClass:
counter = 0
def our_function (the_instance):
the_instance.counter = the_instance.counter + 1
return the_instance.counter
myInstance = OurClass()
mySecondInstance = OurClass()
print(OurClass.our_function(myInstance))
print(OurClass.our_function(mySecondInstance))
And successfully, both print statements print 1
!
But that is a bit annoying because this the_instance
is something that is not like the other arguments. To make it distinct, python allows us to avoid the first parameter and instead provide it as the receiver. Both of these work:
print(myInstance.our_function())
print(OurClass.our_function(mySecondInstance))
Python uses a very strong convention for these parameters. Instead of the_instance
, call it self
. See Why is self only a convention?.
class OurClass:
counter = 0
def our_function (self):
self.counter = self.counter + 1
return self.counter
myInstance = OurClass()
mySecondInstance = OurClass()
print(myInstance.our_function())
print(mySecondInstance.our_function())
Now we're almost done! Just one thing left to understand: Where do the parameters of __init__()
come from?
They are passed to __init__()
from the line where we construct it. So let me demonstrate by adding a starting value for our counter
:
class OurClass:
counter = 0
def __init__ (self, starting_value):
self.counter = starting_value
def our_function (self):
self.counter = self.counter + 1
return self.counter
myInstance = OurClass(5)
mySecondInstance = OurClass(10)
print(myInstance.our_function())
print(OurClass.our_function(mySecondInstance))
This prints 6
and 11
.
But what do those comments mean with @staticmethod
? For that, see Difference between staticmethod and classmethod and Do we really need @staticmethod decorator in python to declare static method
.
In short: You can annotate any method in a class with either @staticmethod
or @classmethod
.
@staticmethod
means that it can be called like myInstance.foo()
when OurClass.foo()
does not take self
as a parameter. Without that decorator, you could only call it as OurClass.foo()
but not as myInstance.foo()
.
@classmethod
means that it can be called like myInstance.foo()
and it does not get myInstance
as the first parameter, but instead the class of myInstance
, which is OurClass
. That allows you e.g. to define alternative constructors. Also, a class method is not inherited when you subclass it, so it won't be mistakenly called.
The comments are pointing out that you could also use a @staticmethod
and avoid creating an instance. For that, you would have to not use any variables in the class itself - but you aren't using those for long anyways, so you could all pass them as parameter to the function.