How to avoid using global variables? - python

I use global variables but I've read that they aren't a good practice or pythonic. I often use functions that give as a result many yes/no variables that I need to use in the main function. For example, how can I write the following code without using global variables?
def secondary_function():
global alfa_is_higher_than_12
global beta_is_higher_than_12
alfa = 12
beta = 5
if alfa > 10:
alfa_is_higher_than_12 = "yes"
else:
alfa_is_higher_than_12 = "no"
if beta > 10:
beta_is_higher_than_12 = "yes"
else:
beta_is_higher_than_12 = "no"
def main_function():
global alfa_is_higher_than_12
global beta_is_higher_than_12
secondary_function()
if alfa_is_higher_than_12=="yes":
print("alfa is higher than 12")
else:
print("alfa isn't higher than 12")
if beta_is_higher_than_12=="yes":
print("beta is higher than 12")
else:
print("beta isn't higher thant 12")
main_function()

The term "Pythonic" doesn't apply to this topic--using globals like this is poor practice in any programming language and paradigm and isn't something specific to Python.
The global keyword is the tool that Python provides for you to opt out of encapsulation and break the natural scope of a variable. Encapsulation means that each of your components is a logical, self-contained unit that should work as a black box and performs one thing (note: this one thing is conceptual and may consist of many, possibly non-trivial, sub-steps) without mutating global state or producing side effects. The reason is modularity: if something goes wrong in a program (and it will), having strong encapsulation makes it very easy to determine where the failing component is.
Encapsulsation makes code easier to refactor, maintain and expand upon. If you need a component to behave differently, it should be easy to remove it or adjust it without these modifications causing a domino effect of changes across other components in the system.
Basic tools for enforcing encapsulation include classes, functions, parameters and the return keyword. Languages often provide modules, namespaces and closures to similar effect, but the end goal is always to limit scope and allow the programmer to create loosely-coupled abstractions.
Functions take in input through parameters and produce output through return values. You can assign the return value to variables in the calling scope. You can think of parameters as "knobs" that adjust the function's behavior. Inside the function, variables are just temporary storage used by the function needed to generate its one return value then disappear.
Ideally, functions are written to be pure and idempotent; that is, they don't modify global state and produce the same result when called multiple times. Python is a little less strict about this than other languages and it's natural to use certain in-place functions like sort and random.shuffle. These are exceptions that prove the rule (and if you know a bit about sorting and shuffling, they make sense in these contexts due to the algorithms used and the need for efficiency).
An in-place algorithm is impure and non-idempotent, but if the state that it modifies is limited to its parameter(s) and its documentation and return value (usually None) support this, the behavior is predictable and comprehensible.
So what does all this look like in code? Unfortunately, your example seems contrived and unclear as to its purpose/goal, so there's no direct way to transform it that makes the advantages of encapsulation obvious.
Here's a list of some of the problems in these functions beyond modifying global state:
using "yes" and "no" string literals instead of True/False boolean values.
hardcoding values in functions, making them entirely single-purpose (they may as well be inlined).
printing in functions (see side effects remark above--prefer to return values and let the calling scope print if they desire to do so).
generic variable names like secondary_function (I'm assuming this is equivalent to foo/bar for the example, but it still doesn't justify their reason for existence, making it difficult to modify as a pedagogical example).
But here's my shot anyway:
if __name__ == "__main__":
alpha = 42
beta = 6
print("alpha %s higher than 12" % ("is" if alpha > 12 else "isn't"))
print("beta %s higher than 12" % ("is" if beta > 12 else "isn't"))
We can see there's no need for all of the functions--just write alpha > 12 wherever you need to make a comparison and call print when you need to print. One drawback of functions is that they can serve to hide important logic, so if their names and "contract" (defined by the name, docstring and parameters/return value) aren't clear, they'll only serve to confuse the client of the function (yourself, generally).
For sake of illustration, say you're calling this formatter often. Then, there's reason to abstract; the calling code would become cumbersome and repetitive. You can move the formatting code to a helper function and pass any dynamic data to inject into the template:
def fmt_higher(name, n, cutoff=12):
verb = "is" if n > cutoff else "isn't"
return f"{name} {verb} higher than {cutoff}"
if __name__ == "__main__":
print(fmt_higher("alpha", 42))
print(fmt_higher("beta", 6))
print(fmt_higher("epsilon", 0))
print(fmt_higher(name="delta", n=2, cutoff=-5))
We can go a step further and pretend that n > cutoff was a much more complicated test with many small steps that would breach single-responsibility if left in fmt_higher. Maybe the complicated test is used elsewhere in the code and could be generalized to support both use cases.
In this situation, you can still use parameters and return values instead of global and perform the same sort of abstraction to the predicate as you did with the formatter:
def complex_predicate(n, cutoff):
# pretend this function is much more
# complex and/or used in many places...
return n > cutoff
def fmt_higher(name, n, cutoff=12):
verb = "is" if complex_predicate(n, cutoff) else "isn't"
return f"{name} {verb} higher than {cutoff}"
if __name__ == "__main__":
print(fmt_higher("alpha", 42))
print(fmt_higher("beta", 6))
print(fmt_higher("epsilon", 0))
print(fmt_higher(name="delta", n=2, cutoff=-5))
Only abstract when there is sufficient reason to abstract (the calling code becomes clogged or when you're repeating similar blocks of code multiple times are classic rules-of-thumb). And when you do abstract, do it properly.

One could ask what reasons you might have to structure your code like this, but assuming you have your reasons, you could just return the values from your secondary function:
def secondary_function():
alfa = 12
beta = 5
if alfa > 10:
alfa_is_higher_than_12 = "yes"
else:
alfa_is_higher_than_12 = "no"
if beta > 10:
beta_is_higher_than_12 = "yes"
else:
beta_is_higher_than_12 = "no"
return alfa_is_higher_than_12, beta_is_higher_than_12
def main_function():
alfa_is_higher_than_12, beta_is_higher_than_12 = secondary_function()
if alfa_is_higher_than_12=="yes":
print("alfa is higher than 12")
else:
print("alfa isn't higher than 12")
if beta_is_higher_than_12=="yes":
print("beta is higher than 12")
else:
print("beta isn't higher thant 12")

Never write 'global'. Then you are sure you are not introducing any global variables.
You could also pass the values as arguments:
def secondary_function():
alfa = 12
beta = 5
if alfa > 10:
alfa_is_higher_than_12 = "yes"
else:
alfa_is_higher_than_12 = "no"
if beta > 10:
beta_is_higher_than_12 = "yes"
else:
beta_is_higher_than_12 = "no"
return alfa_is_higher_than_12, beta_is_higher_than_12
def main_function(alfa_is_higher_than_12, beta_is_higher_than_12):
if alfa_is_higher_than_12=="yes":
print("alfa is higher than 12")
else:
print("alfa isn't higher than 12")
if beta_is_higher_than_12=="yes":
print("beta is higher than 12")
else:
print("beta isn't higher thant 12")
main_function(*secondary_function())

Related

Running unit tests on a non OOP code without any return value

I have a script of this form which is called in a program (from another similar function). I need to use unittest module to write tests for this function.
It doesn't exactly return anything but changes a lot of globals
It takes inputs
I can't change it to an OOP code right now
I want to test cases where I change a certain global varible and see if ,let's say, TOTFRAC is positive or something.
I have read tests for OOP codes where I call each variable as an object parameter, but what do I do if my code isn't Object Oriented.
Note: I have removed a lot of lines of code because it is rather long, so things might not exactl make sense
import numpy
import math
def SETUP(LAST):
def GOTO999():
print(' ERROR IN GAS INPUT : NGAS=',NGAS,'\n')
for J in range(1,6):
# print(J)
print(' N=',J,' NGAS=',NGASN[J],' FRAC=',FRAC[J])
LAST=1
return
# A lot of globals
# Initialising variables
NBREM=[]
EBRTOT=[]
for K in range(1,6):
NBREM.append(0)
EBRTOT.append(0.0)
NGAS=int(input('NGAS'))
NEVENT=int(input('NEVENT'))
IMIP=int(input('IMIP'))
NDVEC=int(input('NDVEC'))
NSEED=int(input('NSEED'))
ESTART=float(input('ESTART'))
ETHRM=float(input('ETHRM'))
ECUT=float(input('ECUT'))
ICOUNT=0
if(IMIP == 1):
ICOUNT=1
if(NGAS == 0):
LAST=1
return
if(ESTART > 3.0*(10**6) and IMIP == 3):
print(' SUBROUTINE STOPPED: X-RAY ENERGY=','%.3f' % ESTART,'EV. MAXIMUM ENERGY 3.0MEV')
sys.exit()
if(IMIP != 1 and NEVENT > 10000):
print(' SUBROUTINE STOPPED: NUMBER OF EVENTS =',NEVENT,' LARGER THAN ARRAY LIMIT OF 10000')
sys.exit()
NGASN=[]
for i in range(1,6):
NGASN.append(int(input('NGASN'+str(i))))
FRAC=[]
for i in range(1,6):
FRAC.append(round(float(input('FRAC')),4))
TEMPC=round(float(input('TEMPC')),4)
TORR=round(float(input('TORR')),4)
# more inputs
if(IWRITE != 0):
outputfile=open("DEGRAD.OUT","w")
EBIG=0.05*ESTART/1000.
EFINAL=ESTART*1.0001+760.0*EBIG/TORR*(TEMPC+ABZERO)/293.15*EFIELD
if(EFINAL < (1.01*ESTART)):
EFINAL=1.01*ESTART
# CHECK INPUT
TOTFRAC=0.00
if(NGAS == 0 or NGAS > 6):
GOTO999()
for J in range(1,NGAS):
print('J',J)
if(NGASN[J]== 0 or FRAC[J] == 0.00):
GOTO999()
TOTFRAC=TOTFRAC+FRAC[J]
if(abs(TOTFRAC-100.00)> 1*(10**-6)):
print(TOTFRAC)
GOTO999()
if(NDVEC): #22594
PHI=0
THETA=0
elif(NDVEC==-1):
PHI=0
THETA=numpy.arccos(-1)
elif(NDVEC==0):
PHI=0.0
THETA=API/2.0
elif(NDVEC==2):
R3=DRAND48(0.0,1.0)
PHI=TWOPI*R3
R4=DRAND48(1.5, 1.9)
THETA=numpy.arccos(1.0-2.0*R4)
else :
print('DIRECTION OF BEAM NOT DEFINED NDVEC =',NDVEC)
sys.exit()
if(NSEED != 0):
RM48(NSEED,0,0)
CORR=ABZERO*TORR/(ATMOS*(ABZERO+TEMPC)*100.00)
GOTO999()
# end
As #hoefling has pointed out, the code as it is now is hardly testable. But, this is not because it is not object oriented. You can easily test non-object oriented code as long as it has a suitable structure. Which means, break long functions down into smaller ones, use function arguments and return values rather than global variables etc. - none of this has anything to do with OOP. In fact, your code violates many coding principles that were known and formulated long before OOP was en vogue (1974), see https://en.wikipedia.org/wiki/The_Elements_of_Programming_Style.
I recommend reading one or some of the following books:
Code Complete by Steve McConnell - a classic book about how to write great code.
Refactoring by Martin Fowler - how to migrate from not-so-great code to better code.
Clean Code by Robert C. Martin - again, how to write great code.

Python TypeError: 'function' object is not subscriptable in nested function when i try to implement switch case

I am trying to code a random decision between 3 machines.
Since Python does not have any switch/case function, I resorted to if and elif.
Each machine (line1(),line2(),line3()) has its own function as well.
However, I have been getting errors.
Please advise me on what is going wrong.
machine_choice = [1,2,3]
selected_machine = random.choice(machine_choice)
print(selected_machine)
def machines(selected_machine):
if selected_machine == 1:
print("machine 1 randomly selected")
line1()
elif selected_machine == 2:
print("machine 2 randomly selected")
line2()
else:
print("machine 3 randomly selected")
line3()
machines(selected_machine)
def line1():
if machine1["hardness"] < rev[i][1]["condition"]:
print("\tMachine 1 hardness breached, will take 30mins for changing the system")
machine1["hardness"] = 10
time = line1
machine1["schedule"].append([d,"machine 1",time,"change",30.,time.add(minutes=30)])
print("\tno activities from {} to {}".format(time,time.add(minutes=30)))
time = time.add(minutes=30)
print("\tdone changing at time: ",time)
print("\tcurrent log: ",machine1["schedule"],"\n")
d+=1
line1=time
#line1 = max(time,line1,line2)
machine1["schedule"].append([d,"machine 1",line1,"feeding",5.,line1.add(minutes=5)])
line1 = line1.add(minutes=5)
d+=1
machine1["schedule"].append([d,"machine 1",line1,rev[i][0],rev[i][1]["duration"],line1.add(minutes=rev[i][1]["duration"])])
line1 = line1.add(minutes=rev[i][1]["duration"])
time = time.add(minutes=5)
d+=1
if rev[i][1]["quantity"] == 0:
i += 1
I am trying to code a random decision between 3 machines.
In Python, functions are nothing special, you can use them just like any other object.
def machine_one():
return 'Machine One'
def machine_two():
return 'Machine Two'
def machine_three():
return 'Machine Three'
machines = [machine_one, machine_two, machine_three]
print(random.choice(machines)())
The message means that you try to access to some python object defined as a function as if it were an array. The most likely candidate looking as the code you provided is machine_choice.
But please provide a working minimal example, which in that case means including import of necessary modules (random) and lines function.
Regarding the purpose of the code (faking a switch) the cascaded if solution is usually not a very good solution. Using random.choice with an array or a dictionnary or an array with random number as key or index would be much more pythonic.
Also using switch is a common code smell in OO programming and is often best replaced using object polymorphism and a factory.
You may have a look at python switch replacement for details.

Saving output of a function as a variable and using it in another function

I'm new to programming, so this question might be dumb.
I need to introduce the value of Tr1 into the Bzero1 function. When I run the module I get the result below:
.
The program is not running the Bzero1 function and I'm not sure why. Is it because I am not introducing the Tr1 value correctly or something else? I want Bzero1 to perform the operation 0.083-(0.422/Tr1**1.6), with Tr1 obtained from the result of T/Tc1.
I would appreciate your help a lot.
T = float(input("Introduce system temperature in Kelvin: "))
print("System temperature is: ", T)
Tc1 = float(input("Introduce critical temperature of component 1: "))
print("Critical temperature of component 1 is: ", Tc1)
def Tr1(T, Tc1):
print("Relative temperature 1: ", T/Tc1)
Tr1 = Tr1(T, Tc1)
def Bzero1(Tr1):
print("Bzero 1: ", 0.083-(0.422/Tr1**1.6))
Do not replace Tr1 function value, to avoid such replacement change:
Tr1_value = Tr1(T, Tc1)
Call Bzero1 function with code:
Bzero1(Tr1_value)
Modify Tr1 to return value:
def Tr1(T, Tc1):
result = T/Tc1
print("Relative temperature 1: ", result)
return result
Also, let me suggest you to take a look on python official tutorial - there you can learn a lot about python ...
Good Luck !
def is only defining a function, not calling it. E.g.
def foo(a):
print a * 2
means there is now a function foo that takes argument a. The a in foo(a) is the name of the variable inside the function.
So in your case
def Bzero1(Tr1):
print("Bzero 1: ", 0.083-(0.422/Tr1**1.6))
defines the function Bzero1 as taking argument Tr1, but doesn't call it. You need to call the function, just like you called Tr1:
Bzero1(Tr1)
You can see that this way it becomes confusing quite quickly on which is a variable outside of your function, and which are variables inside functions. Therefore it is better to use different names for variables in your outer program v.s. those inside functions.
Here are a few more best practices that you might find useful:
It is generally better to first define all functions and then execute the program's main code, as opposed to intermixing function definitions and the main program.
Another best practice is to make functions only calculate output from inputs, and handle output somewhere else. This way you get to reuse your functions in other parts of your program while always keeping control of when and what to output to the user.
Finally, you shouldn't generally reassign names, e.g. Tr1 = Tr1(...) means that Tr1 is now no longer the name of the function but the name of the result returned by Tr1. In short, use different names for different things.
Applying these tips, your code might look as follows:
# function definitions first
def Tr1(vt, vtc1):
return vt/vtc1
def Bzero1(vtr1):
return 0.083-(0.422 / vtr1 ** 1.6)
# get user input
T = float(input("Introduce system temperature in Kelvin: "))
print("System temperature is: ", T)
vTc1 = float(input("Introduce critical temperature of component 1: "))
print("Critical temperature of component 1 is: ", vTc1)
# run calculations
vTr1 = Tr1(T, vTc1)
vBz1 = Bzero1(vTr1)
# print output
print("Relative temperature 1: ", vTr1)
print("Bzero 1: ", vBz1)
Note
Since I don't know the semantic meaning of your variables I have just used small letter v as a prefix - in general it is better to use meaningful names like temperature or temp1 and temp2 etc. Programs are not math papers.

Parameter hint assignment through a function in LMFIT

I want to set the parameter hints for models held in a dictionary. I have created a function which is called for setting the hints. First, a primary model is created and then I want to create different models, identical to the primary, but with different prefixes. The set_hints function accepts a parameter comp which defined what hints will be set. This is a simplified part of my code:
import lmfit
def foo (x, a):
return x + a
def set_hints(mod, comp="2"):
mod.set_param_hint("a", value=1, vary=True)
if comp == "2":
mod.set_param_hint("a", value=0, vary=False)
return mod.param_hints
m = lmfit.Model(foo)
models = {}
for i in range(2):
hints = set_hints(m, comp="2")
models["m%i" % i] = lmfit.Model(m.func, m.independent_vars,
prefix="m%i" %i,
param_names=m.param_names)
for par in m.param_names:
models["m%i" % i].param_hints[par] = hints[par]
# models["m%i" % i].param_hints = hints
for key in models.keys():
print key
print "value:"
print models[key].param_hints["a"]["value"]
print "vary:"
print models[key].param_hints["a"]["vary"]
which outputs:
m0
value:
1
vary:
True
m1
value:
0
vary:
False
Which doesn't make any sense to me! The value and vary hints should be 0 and False respectively in both cases. It is like at the second iteration of the loop, the condition comp == "2" of the set_hints function is not satisfied for the 1st iteration of the loop and the hints are changed retroactively! If I uncomment the commented line and not set the hints iteratively, the result is good. But what is happening now I find it completely absurd. Please help me understand what is happening!
The code seems very weird, but I assume it comes from a larger design. I think this must be a bug, though I'm not certain what that is. I will create an Issue on the lmfit github site.

Randomly generating math questions

My task is to produce a code that greets the user and asks their name storing their name as username. Then generates 2 random numbers and an operation. The question is asked to the user. After that it checks if the users answer is correct or not also adding 1 to questionsAsked. If it is correct, 1 is added to correctAnswers. If it is incorrect, the user is told so with the correct answer. The program should end after 10 questions (hence the while questionAsked > 11). The user should be given their username and how many questions they got correct.
My problem is when I run the code,it comes up with NameError: name 'questionAsked' is not defined. I'm struggling to work out how else I could define questionAsked.
Here is what I've done so far:
import random
import math
def test():
Username=input("What is your name?")
print ("Welcome"+Username+" to the Arithmetic quiz")
num1=random.randint(1, 10)
num2=random.randint(1, 10)
Ops = ['+','-','*']
Operation = random.choice(ops)
num3=int(eval(str(num1) + operation + str(num2)))
print("What is" +" "+str(num1) + operation +str (num2,"?"))
userAnswer= int(input("Your answer:"))
if userAnswer != num3:
print("Incorrect. The right answer is"+" "+(num3))
return False
else:
print("correct")
return True
correctAnswers=0
questionsAsked=0
while questionAsked > 11:
if test () == True:
questionsAnswered +=1
correctAnswers +=1
if test () == False:
questionsAnswered +=1
You have a test while questionAsked > 11 but don't use that name anywhere else in your code. You certainly never defined it. You probably wanted to test questionsAsked (with an s) instead.
There are other problems, however. The loop should continue while you have fewer than 11 questions asked, not more. You also call test() twice, you should only call it once each loop. In your loop you use questionsAnswered but never defined that either and don't increment questionsAsked; you probably meant to increment the latter:
correctAnswers=0
questionsAsked=0
while questionsAsked < 10:
if test():
correctAnswers +=1
questionsAsked +=1
Now test() is only called the once. Both your branches incremented questionsAsked, I moved that out of the tests, and now you no longer need to check if the test failed.
Since you start counting at zero, you want to test for < 10, not 11.
Instead of a while loop, you could use a for loop using the range() function:
for question_number in range(10):
if test():
correctAnswers +=1
Now the for loop takes care of counting the number of questions asked, and you no longer need to increment a variable manually.
Next, you need to move the username handling out of the test() function. You don't need to ask the user for their name each time. Ask for the name once, before the loop, so that you can access the user's name after the 10 questions:
def test():
num1=random.randint(1, 10)
num2=random.randint(1, 10)
# ... etc.
Username = input("What is your name?")
print("Welcome", Username, "to the Arithmetic quiz")
correctAnswers = 0
for question_number in range(10):
if test():
correctAnswers +=1
# print the username and correctAnswers
You need to be careful about your names in the test() function too; you define the names Ops and Operation but try to use them as ops and operation instead. That won't work, you need to use the same case everywhere to refer to those names. The Python style guide recommends you use all lowercase with underscores for local names, to distinguish them from class names (which use CamelCase, initial uppercase letters and no spaces between words).
Next problem: you are using str() with two arguments here:
print("What is" +" "+str(num1) + operation +str (num2,"?"))
That won't work; a two-argument str() call is meant for decoding bytes to a Unicode string.
Rather than use string concatenation, just pass your values to print() as separate arguments. The function will take care of converting things to strings and adds spaces between separate arguments for you:
print("What is", num1, operation, num2, "?")
Now there will be a space between num2 and the "?" but that is not that big a problem. You can use the str.format() method to create a string with placeholders where arguments to the method are filled in for you, again converting to strings automatically. This allows you to control spaces more directly:
print("What is {} {} {}?".format(num1, operation, num2))
The three arguments are placed where each {} appears, in order.
You have many discrepancies in variable names and indentation. Remember Python is case sensitive. And by the way, the condition in your while loop will cause your program not to ask any questions.
So for example, you created a list of operations called Ops and then used the random module to select an operation from ops. Python will inevitably throw an error, as ops is not actually defined. Instead, you should use Ops because that is the variable you actually declared, with a capital letter. Again, Python is case sensitive.
Likewise, Python recognizes a difference between questionAsked and questionsAsked. It's one or the other, so choose a name and be consistent.

Categories