python - flow control when executing multiple functions inside a function - python

I've been writing some python app and unfortunately I stumbled upon (I believe...) a Design Pattern/Flow Control problem.
Let's say that I have some big function that includes multiple other function and outcome of the big function is strictly determined by small function success.
So the big function simply contains a series of operations.
If just one small function fails then we should abort the big function, unfortunately the deeper we go... then the small functions make more modifications to some objects, so I think that I just cannot abort the big function, because I have to revert/repair some of those unfinished changes made by small functions - "perform cleanup".
Is there a pythonic way to check/control execution of small functions inside a big function? Because the solution that I have now seems extremely ugly and not very ZENish...
Here is some pseudo code that represents the solution that I have now:
def small_func():
try:
# doing something here
return True # if operation successful
except Error:
return False
def big_func():
# state var helping determine if we need to cleanup if some of the
# funtions were unsuccessful
state = True
if not small_func1():
state = False
if not small_func2():
state = False
if not small_func3():
state = False
if not small_func4():
state = False
if not small_func4():
state = False
etc...
if not state:
#perform some cleanup after failed function since we can't
#finish the big function - we need to abort and clean unfinished stuff

You could save all states first, use a single try-block in the big function and reset in the except block if something goes wrong. Like this:
def small_func():
# no try-block
def big_func():
# Save all states here
try:
small_func1()
small_func2()
small_func3()
small_func4()
except Error:
# Reset all states here

Related

How to rewrite a state machine in a clearer style?

I am interacting with an external device, and I have to issue certain commands in order. Sometimes I have to jump back and redo steps. Pseudocode (the actual code has more steps and jumps):
enter_update_mode() # step 1
success = start_update()
if not success:
retry from step 1
leave_update_mode()
How do I handle this the cleanest way? What I did for now is to define an enum, and write a state machine. This works, but is pretty ugly:
class Step(Enum):
ENTER_UPDATE_MODE = 1
START_UPDATE = 2
LEAVE_UPDATE_MODE = 3
EXIT = 4
def main():
next_step = Step.ENTER_UPDATE_MODE
while True:
if next_step == Step.ENTER_UPDATE_MODE:
enter_update_mode()
next_step = Step.START_UPDATE
elif next_step == Step.START_UPDATE:
success = start_update()
if success:
next_step = Step.LEAVE_UPDATE_MODE
else:
next_step = Step.ENTER_UPDATE_MODE
....
I can imagine an alternative would be to just call the functions nested. As long as this is only a few levels deep, it should not be a problem:
def enter_update_mode():
# do stuff ...
# call next step:
perform_update()
def perform_update():
# ...
# call next step:
if success:
leave_update_mode()
else:
enter_update_mode()
I have looked into the python-statemachine module, but it seems to be there to model state machines. You can define states and query which state it is in, and you can attach behavior to states. But that is not what I'm looking for. I am looking for a way to write the behavior code in a very straightforward, imperative style, like you would use for pseudocode or instructions to a human.
There is also a module to add goto to Python, but I think it is a joke and would not like to use it in production :-).
Notes:
This code is synchronous, meaning it is a terminal app or a separate thread. Running concurrently with other code would be an added complication. If a solution allows that (e.g. by using yield) that would be a bonus, but not neccessary.
I left out a lot of retry logic. A step may be only retried a certain number of times.
Releated discussion of explicit state machine vs. imperative style: https://softwareengineering.stackexchange.com/q/147182/62069

In functions that call a check function, where should the error message be generated/printed/sent?

I have started questioning if the way I handle errors is correct or pythonic. The code scenarios below are easy in nature, whilst the actual use would be more in line with discord.py and PRAW (reddit). The boolean indicates success, and the message returned is a message summarising the exception triggered.
Which of the following scenarios (WITH VERY SIMPLIFIED FUNCTIONS) is the proper pythonic way of doing it, or is there none? Or one I haven't thought of/learned yet?
Scenario 1: Main function returns on false from check function, but check function prints message
def main_function():
# calls the check function
success = check_function(1, 2)
if not success:
return
def check_function(intone, inttwo):
if intone != inttwo:
print("Not the same")
return False
return True
Scenario 2: The main functions prints an error message based on boolean returned from check function
def main_function():
# calls the check function
success = check_function(1, 2)
if not success:
print("Failure, not the same")
return
def check_function(intone, inttwo):
if intone != inttwo:
return False
return True
Scenario 3: The check function returns a boolean and a generated error message which the main function prints if false.
def main_function():
# calls the check function
success, error = check_function(1, 2)
if not success:
print(error)
return
def check_function(intone, inttwo):
if intone != inttwo:
return False, "Not the same"
return True
Obviously, the coding I'm doing that made me think about this is slightly more complicated, but I believe the examples are carried over.
If sounds like you are used to outputting a Boolean for whether things went well or not.
If an error occurs, then you output False, or True.
Additionally, if there is an an error, you are using print() statements to display the error.
An example of pseudo-code using an approach similar to yours is shown below:
# define function for baking a cake:
def make_a_pancake():
"""
outputs an ordered pair (`ec`, cake)
`ec` is an error code
If `ec` is zero, then `cake` is a finished cake
ERROR CODES:
0 - EVERYTHING WENT WELL
1 - NO MIXING BOWLS AVAILABLE
2 - WE NEED TO GET SOME EGGS
3 - WE NEED TO GET SOME FLOUR
"""
# IF THERE IS A MIXING BOWL AVAILABLE:
# GET OUT A MIXING BOWL
if kitchen.has(1, "bowl"):
my_bowl = kitchen.pop(1, "bowl")
else: # no mixing bowl available
print("WE NEED A MIXING BOWL TO MAKE A CAKE")
return 1 # NO MIXING BOWL IS AVAILABLE
# IF THERE ARE EGGS AVAILABLE:
# PUT THE EGGS INTO THE BOWL
if kitchen.has(6, "eggs"):
my_eggs = kitchen.pop(6, "eggs")
my_bowl.insert(my_eggs)
else: # OUT OF EGGS
print("RAN OUT OF EGGS. NEED MORE EGGS")
return 2
# IF THERE IS WHEAT FLOUR AVAILABLE:
# put 2 cups of flour into the bowl
if len(kitchen.peek("flour")) > 0:
f = kitchen.pop("flour", 2, "cups")
my_bowl.insert(f)
else:
print("NOT ENOUGH FLOUR IS AVAILABLE TO MAKE A CAKE")
RETURN 3, None
# stir the eggs and flour inside of the bowl
# stir 3 times
for _ in range(0, 3):
bowl.stir()
# pour the cake batter from the bowl into a pan
my_batter = bowl.pop()
pan.push(my_batter)
# cook the cake
stove.push(pan)
stove.turn_on()
stove.turn_off()
the_cake = pan.pop()
return err_code, the_cake
The code above is similar to the way code was written many decades ago.
Usually,
0 is interpreted as False
1, 2, 3, etc... are all True.
It might be confusing that 0 means no error occurred
However,
There is only one way for things to go right.
There are many ways for things to go wrong.
Every program written in python, Java, C++, C#, etc... will give the operating system (Microsoft windows, Linux, etc...) an error code when the whole program is done running.
A "clear" error flag (zero) is good.
An error flag of 1, 2, 3, ..., 55, ... 193, etc... is bad.
The most pythonic way to handle printing error messages is something you have not learned about yet.
It is called, exception handling
It looks like the following:
class NO_MORE_EGGS(Exception):
pass
def cook_omelet():
# if (THERE ARE NO EGGS):
# raise an exception
if(kitchen.peek("eggs") < 1):
msg = "you cannot make an omelet without breaking some eggs"
raise NO_MORE_EGGS(msg)
pan.push(eggs)
# cook the cake
stove.push(pan) # `push` means `insert` or `give`
stove.turn_on()
stove.turn_off()
pan = stove.pop()
the_omelet = pan.pop()
return the_omelet
Ideally, a function is like a small component in a larger machine.
For example, a car, or truck, contains many smaller components:
Alternators
Stereos (for music)
Radiator (to lower the engine temperature)
Brake-pads
Suppose that Bob designs a stereo for a car made by a company named "Ford."
Ideally, I can take the stereo Bob designed put the stereo in a design for a different car.
A function is also like a piece of equipment in a kitchen.
Examples of kitchen equipment might be:
A rice-cooker
A toaster
A kitchen sink
Suppose that you design a rice-cooker which works in Sarah's kitchen.
However, your rice-cooker does not work in Bob's kitchen.
That would be a very badly designed rice-cooker.
A well designed rice-cooker works in anybody's kitchen.
If you write a function in Python, then someone else should be able to use the function you wrote in somebody else's code.
For example, suppose you write a function to calculate the volume of a sphere.
If I cannot re-use your function in someone else's computer program, then you did a bad job writing your function.
A well-written function can be used in many, many, many different computer programs.
If you write a function containing print statements, that is a very bad idea.
Suppose you make a website to teach children about math.
Children type the radius of a sphere into the website.
The website calculates the volume of the sphere.
The website prints "The volume of the sphere is 5.11 cubic meters"
import math
def calc_sphere_vol(radius):
T = type(radius)
rad_cubed = radius**T(3)
vol = T(4)/T(3)*T(math.pi)*rad_cubed
print("the volume of the sphere is", vol)
return vol
In a different computer program, people want to calculate the radius of a sphere, quickly and easily without seeing any message printed to the console.
Maybe calculating the volume of a sphere is one tiny step on the way to a larger more complicated result.
A function should only print message to the console if the person using the function given the function permission to do so.
Suppose that:
you write some code.
you post your code on Github
I download your code from Github.
I should be able to run your code without seeing a single print statement (if I want to).
I should not have to re-write your code to turn-off the print statements.
Imagine that you were paid to design next year's model of some type of car.
You should not have to look inside the radio/stereo-system.
IF you are designing a large system, you should not have to see what is inside each small component.
A computer program is too big and complicated to re-write the code inside of the existing functions.
Imagine pieces of a computer program as small black cubes, or boxes.
Each box has input USB ports.
Each box has output USB ports.
I should be able to plug in any wires I want into the small box you designed and built.
I should never have to open up the box and re-wire the inside.
A computer programmer should be able to change where output from a function goes without modifying the code inside of that function.
print statements are very very bad.
I would avoid option 1 in the majority of cases. By having it do the reporting, you're limiting how it can be used in the future. What if you want this function to be used in a tkinter app later? Is printing still appropriate? If the function's job is to do a check, it should just do the check. The caller can figure out how they want to use the returned boolean.
Options 2 and 3 are both viable, but which you'd use would depend on the case.
If there's exactly one way in which a function can fail, or all different failures should be treated the same, a simple False is fine, since False implies the single failure reason.
If there's multiple distinct ways in which a function can fail, returning some failure indicator that allows for multiple reasons would be better. A return of type Union[bool, str] may be appropriate for some cases; although I don't thinks strings make for good error types since they can be typo'd. I think a tuple of Union[bool, ErrorEnum] would be better; where ErrorEnum is some enum.Enum that can take a restricted set of possible values.
Options 3 bears a weak resemblence to Haskell's Maybe and Either types. If you're interested in different ways of error-handling, seeing how the "other side of the pond" handles errors may be enlightening. Here's a similar example to what you have from the Either link:
>>> import Data.Char ( digitToInt, isDigit )
>>> :{
let parseEither :: Char -> Either String Int
parseEither c
| isDigit c = Right (digitToInt c)
| otherwise = Left "parse error"
>>> :}
parseEither returns either a parsed integer, or a string representing an error.

How can you return good data from inside a function if the function breaks?

I am gathering data from a web api (using python) and I am using a loop to go through thousands of calls to the api. The function was running fine but my computer went to sleep and the internet connection was lost. I am storing the data as a list of dictionarys while calling the api. My question is this: When the function failed, since my list was inside the function I can't even get the several hundred successful calls it made before it failed. How can I add error handling or some other method so that if it fails at some point, say after 500 calls, I can still get 499 pieces of data?
If I had run the code without putting it into a function, my list would still be viable up to the point the code broke, but I felt like putting it into a function was "more correct"
#this is how the function is set up in pseudo-code:
def api_call(x):
my_info = []
for i in x:
dictionary = {}
url=f'http://www.api.com/{x}'
dictionary['data'] = json['data']
my_info.append(dictionary)
return my_info
another_variable = api_call(x)
Just wrap it in a try/except/finally block. The finally is always executed before leaving the try statement. Explanation of what the finally block does is here.
def api_call(x):
my_info = []
try:
for i in x:
dictionary = {}
url=f'http://www.api.com/{x}'
dictionary['data'] = json['data']
my_info.append(dictionary)
except Exception as e:
print('Oopsie') # Can log the error here if you need to
finally:
return my_info
another_variable = api_call(x)

How to prevent a repeat of a program call within a while loop

I am trying to prevent this switch function from constantly repeating within the probability while loop, i want it to be called once promoting an input and then using the return of that input for each time in the while loop instead of asking every time
Click here to see screenshot of code
(it won't let me add a second picture of the switch function so ill just copy and paste it)
def switch_door():
switch=raw_input("Switch doors?:")
if switch!="y" and switch!="n":
return "Incorrect inputs"
elif switch=='y':
return True
elif switch=='n':
return False
You may set a variable e.g.if_switch=switch_door() in the probability() function before your while loop and pass that variable to your simulation function as a parameter.
Note that you will need to change your simulation definition to e.g. def simulation(doors, if_switch):; you will also need to change these two lines:
if switch_door()==True: to if if_switch==True:
elif switch_door()==False: to simply else:
Now your problem should solved.

Python Memory leak using Yocto

I'm running a python script on a raspberry pi that constantly checks on a Yocto button and when it gets pressed it puts data from a different sensor in a database.
a code snippet of what constantly runs is:
#when all set and done run the program
Active = True
while Active:
if ResponseType == "b":
while Active:
try:
if GetButtonPressed(ResponseValue):
DoAllSensors()
time.sleep(5)
else:
time.sleep(0.5)
except KeyboardInterrupt:
Active = False
except Exception, e:
print str(e)
print "exeption raised continueing after 10seconds"
time.sleep(10)
the GetButtonPressed(ResponseValue) looks like the following:
def GetButtonPressed(number):
global buttons
if ModuleCheck():
if buttons[number - 1].get_calibratedValue() < 300:
return True
else:
print "module not online"
return False
def ModuleCheck():
global moduleb
return moduleb.isOnline()
I'm not quite sure about what might be going wrong. But it takes about an hour before the RPI runs out of memory.
The memory increases in size constantly and the button is only pressed once every 15 minutes or so.
That already tells me that the problem must be in the code displayed above.
The problem is that the yocto_api.YAPI object will continue to accumulate _Event objects in its _DataEvents dict (a class-wide attribute) until you call YAPI.YHandleEvents. If you're not using the API's callbacks, it's easy to think (I did, for hours) that you don't need to ever call this. The API docs aren't at all clear on the point:
If your program includes significant loops, you may want to include a call to this function to make sure that the library takes care of the information pushed by the modules on the communication channels. This is not strictly necessary, but it may improve the reactivity of the library for the following commands.
I did some playing around with API-level callbacks before I decided to periodically poll the sensors in my own code, and it's possible that some setting got left enabled in them that is causing these events to accumulate. If that's not the case, I can't imagine why they would say calling YHandleEvents is "not strictly necessary," unless they make ARM devices with unlimited RAM in Switzerland.
Here's the magic static method that thou shalt call periodically, no matter what. I'm doing so once every five seconds and that is taking care of the problem without loading down the system at all. API code that would accumulate unwanted events still smells to me, but it's time to move on.
#noinspection PyUnresolvedReferences
#staticmethod
def HandleEvents(errmsgRef=None):
"""
Maintains the device-to-library communication channel.
If your program includes significant loops, you may want to include
a call to this function to make sure that the library takes care of
the information pushed by the modules on the communication channels.
This is not strictly necessary, but it may improve the reactivity
of the library for the following commands.
This function may signal an error in case there is a communication problem
while contacting a module.
#param errmsg : a string passed by reference to receive any error message.
#return YAPI.SUCCESS when the call succeeds.
On failure, throws an exception or returns a negative error code.
"""
errBuffer = ctypes.create_string_buffer(YAPI.YOCTO_ERRMSG_LEN)
#noinspection PyUnresolvedReferences
res = YAPI._yapiHandleEvents(errBuffer)
if YAPI.YISERR(res):
if errmsgRef is not None:
#noinspection PyAttributeOutsideInit
errmsgRef.value = YByte2String(errBuffer.value)
return res
while len(YAPI._DataEvents) > 0:
YAPI.yapiLockFunctionCallBack(errmsgRef)
if not (len(YAPI._DataEvents)):
YAPI.yapiUnlockFunctionCallBack(errmsgRef)
break
ev = YAPI._DataEvents.pop(0)
YAPI.yapiUnlockFunctionCallBack(errmsgRef)
ev.invokeData()
return YAPI.SUCCESS

Categories