In [ ]:from modsim import System # If this doesn't work, move this file into your /code folder. # It needs to be in the same folder as modsim.py.
Let's write a function, like they do in ModSim notebooks all the time. We'll give it some parameters just to make it feel important.
In [ ]:def func1(input1, input2): print("Input 1 = ", input1) print("Input 2 = ", input2) output = input1 + input2 print("Output = ", output)
In [ ]:func1(1, 2.5)
All it does is add two numbers and print a lot of stuff. Printing is fun, let's do it some more.
In [ ]:print(output)
Well, shucks. That didn't work.
output isn't defined? But we defined it in that super technical function!
You can try printing
input2 as well, and you'll probably see the same error.
In [ ]:output = 5 print(output)
So, output was defined in the function, but doesn't exist outside of the function executing. What happens if you run the function again and print output?
In [ ]:func1(1, 2.5) # will print input1, input2, and output print("Output also =", output)
What? There are 2 versions of
output? Well ya see, this is where we come to global and local variables. Local variables only exist within a function or class* that is using them (they are temporary for a function/specific to a class). Global variables can be accessed by any function or class.
When you create
func1 (and when you pass in values for
input2) those are local variables that only
func1 "knows" about. When you assign a value to output outside of
func1, that is a global variable that you can access any time.
*Classes are pre-defined or user-defined objects that have different methods and parmeters. TimeSeries, Series, State and System are all classes defined in the modsim.py library.
In [ ]:def func2(input1, input2): print("Input 1 = ", input1) print("Input 2 = ", input2) print("Output = ", output) # this will be the global value from earlier, since it's not locally computed
In [ ]:func1(1, 2.5) func2(1, 2.5)
Here we're running two similar functions one after another, and they print their results one after another.
The only difference between the functions is that one computes a value for
output locally, and the other just prints
func2 accesses the global version of
output because it has no other option.*
func1 accesses the local variable
output because local variables override global values with the same name.
*This isn't universally true, in some languages/cases you have to declare a variable as global for it to be accessed anywhere.
Let's look at another example:
In [ ]:def forgetful_func(input1, input2): print(input1, input2) forgetful_func(1, 6) print("Now we try to print those:") print(input1, input2)
Why isn't the second
input2 still aren't global. We getting this? Good.
Now let's do what the book does all the time:
In [ ]:def useful_func(input1, input2): print(input1 + input2)
In [ ]:useful_func(2, 3) # This should make sense. You pass in these parameters, they get used, they aren't stored after printing.
In [ ]:input1 = 1.5 input2 = 4 useful_func(input1, input2)
Now, I dare you to do something w_i_l_d: change those variable names in the cell above. Change them to be all different. Change them to be all the same. How does the function care about your variable names?
In [ ]:cat = 5 dog = 10 useful_func(cat, dog) useful_func(dog, dog) useful_func(input2, dog)
Cool, so that's inputs (mostly). What about outputs? That poor little small-town
output variable wants to be a global star.
This, friends, is why
return is so gosh darn important.
In [ ]:def will_you_remember_me(thing1, thing2): new_thing = thing1 + thing2 return new_thing
In [ ]:will_you_remember_me(3, 8) print(new_thing)
Foiled again! But we used
return and everything! Well guess what, a return value that doesn't get assigned to anything is like a letter without an address (cue joke about The Twitter and snail mail implying that I'm old or something).
To store a return value, you HAVE TO assign it to a variable OUTSIDE of the function.
In [ ]:I_will_remember_you = will_you_remember_me(6, 7) print(I_will_remember_you) # Please, never name things like this. Please.
If you're lazy you can do this, but your variable will not be saved so be careful. It just feeds the output (return value) of your function to the input of print().
In [ ]:print(will_you_remember_me(5, 6))
You can do the above as a shortcut, but remember that your return value will be LOST FOREVER after that.
Like Jack at the end of Titanic.
Think about the choices that you make.
Okay, I guess I should talk about keyword arguments and that whole
System(this=this, that=that) nonsense you're seeing.
So basically, a class (object) has a bunch of properties (attributes). Usually a given class will have specific attributes, but classes like
System are set up to pretty much take any parameters you give them and accept them as attributes.
In [ ]:solar_system = System(planets=8, central_mass="Sun") print(solar_system.planets, solar_system.central_mass)
In [ ]:other_system = System(planets=5, central_mass="Me") print(other_system.planets, other_system.central_mass)
In [ ]:friends = 1000 center_of_universe = " " # Type your name here? personal_universe = System(friends=friends, center_of_universe=center_of_universe) print(personal_universe.friends, personal_universe.center_of_universe)
So, you've created two systems by directly setting parameters, and one by passing in premade variables. In the book those variables usually have the same name as their target, which obscures what's actually happening.
In [ ]:apples = 1 bananas = 1 cherries = 7 grapes = 13 fruit_salad = System(a=apples, b=bananas, c=cherries, d=grapes) print(fruit_salad.a, fruit_salad.b, fruit_salad.c, fruit_salad.d)
In [ ]:print(fruit_salad.apples)
Oh dear, did that last one fail? Of course it did, apples isn't an attribute of fruit_salad. You can print plain old apples, because it's global:
In [ ]:print(apples)
You can't print plain old a, because that's an attribute of fruit_salad and isn't defined outside of that.
In [ ]:print(a)
You can, however, do this:
In [ ]:global_a = fruit_salad.a # This is a name I chose, it doesn't 'make' the variable global. print(global_a)
And you could just as well call
global_a something else, like
a, and there would be a global variable
a as well as a property
a, and if you changed one the other would not be affected.
In [ ]:b = fruit_salad.b b += 10 fruit_salad.b += 100 print(b, fruit_salad.b)
So, when you type
bikeshare = System(this=this, that=that),
the left parts are specific attributes of the System object, local to the system.
The right parts are values you are passing in, which happen to already be defined as global variables with the same names.
You're just telling a function or class to use the global value on the right, calling it by the name on the left.
In [ ]: