Improving wrapper function when handling different embedded if-else scenarios - python

I wish to refactor this fugly function - running on an object collection robot - handling all possible sounds in response to stimuli of different data types.
How can I Pythonically improve the following?
'''
Produce different sounds based on various parameters comprising robot's
proximity to different objects & time taken for collecting said objects.
`obj` := nearest object captured within front-camera's field of view
`timeSinceAnyObjLastCollected` := global timer
`objPassThruInfraRedEntryGates` := returns bool
`cameraSensorWithinTwoFeetOfObj` := returns bool
'''
def soundManager(obj, timeSinceAnyObjLastCollected):
## Order of these if-statements determine sound-output precedence
if timeSinceAnyObjLastCollected > 60:
timeSinceAnyObjLastCollected = 0 ## Reset timer
playSound("A")
elif objPassThruInfraRedEntryGates(obj):
timeSinceAnyObjLastCollected = 0 ## Reset timer
if obj.color == "RED":
playSound("B")
elif obj.color == "GREEN":
playSound("C")
elif cameraSensorWithinTwoFeetOfObj(obj)
if obj.color == "RED":
playSound("D")
elif obj.color == "GREEN":
playSound("E")
else:
playSound("F")
Moreover, is implementing this "catch-all" soundManager() any more scalably elegant than separately calling playSound(), where those situations indented within these if-statements are actually encountered within the script?

Related

Updating local variable within function not working correctly [duplicate]

This question already has answers here:
Static variable in Python?
(6 answers)
Closed 1 year ago.
I'm trying to write a function that updates its local variable each time it is run but it is not working for some reason.
def max_equity(max_equity=0):
if current_equity() > max_equity:
max_equity = current_equity()
print(max_equity)
return max_equity
else:
print(max_equity)
return max_equity
and the function which it is calling
def current_equity():
for n in range(len(trade_ID_tracker)-1):
equity_container = 0
if (trade_ID_tracker[n,2]) == 0:
break
else:
if (trade_ID_tracker[n, 1].astype(int) == long):
equity_container += (df.loc[tbar_count,'Ask_Price'] - trade_ID_tracker[n, 2]) * trade_lots * pip_value * 1000
elif (trade_ID_tracker[n, 1].astype(int) == short):
equity_container += 0 - (df.loc[tbar_count,'Ask_Price'] - trade_ID_tracker[n, 2]) * trade_lots * pip_value * 10000
return (current_balance + equity_container)
but for some reason the max_equity() function prints current_equity() which I can only imagine means that either:
if current_equity() > max_equity:
is not doing it's job and is triggering falsely
or
max_equity = current_equity()
is not doing its job and max_equity starts at zero every time it is run.
In other words if I put max_equity() in a loop where current_equity() is
[1,2,3,4,5,4,3,2,1]
then max_equity() should return
[1,2,3,4,5,5,5,5,5]
But instead it returns
[1,2,3,4,5,4,3,2,1]
Here's a quick example test
ar = [1,2,3,4,5,4,3,2,1]
def stuff(max_equity=0):
if ar[n] > max_equity:
max_equity = ar[n]
print(max_equity)
else:
print(max_equity)
for n in range(len(ar)):
stuff()
Either way I'm kind of stumped.
Any advice?
local function variables are reset at each function call. This is essential for the behavior of functions as idempotent, and is a major factor in the success of the procedural programming approach: a function can be called form multiple contexts, and even in parallel, in concurrent threads, and it will yield the same result.
A big exception, and most often regarded as one of the bigger beginner traps of Python is that, as parameters are reset to the default values specified in the function definition for each call, if these values are mutable objects, each new call will see the same object, as it has been modified by previous calls.
This means it could be done on purpose by, instead of setting your default value as 0 you would set it as a list which first element was a 0. At each run, you could update that value, and this change would be visible in subsequent calls.
This approach would work, but it is not "nice" to depend on a side-effect of the language in this way. The official (and nice) way to keep state across multiple calls in Python is to use objects rather than functions.
Objects can have attributes tied to them, which are both visible and writable by its methods - which otherwise have their own local variables which are re-started at each call:
class MaxEquity:
def __init__(self):
self.value = 0
def update(max_equity=0):
current = current_equity()
if current > self.value:
self.value = current
return self.value
# the remainder of the code should simply create a single instance
# of that like ]
max_equity = MaxEquity()
# and eeach time yoiu want the max value, you should call its "update"
# method

How to avoid using global variables?

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())

How do you create a new thread in python?

I'm building a game in python, and I want to create an event listener that checks for when the main character's hp is smaller or equal to 0, and then executes a game over function. In other languages (vb.net) i have achieved this by creating a new thread that continuously loops an if statement until the condition is met, then runs the game over code, then closes itself. How do you create/start/close threads in python? Also, is there a better way of doing this that is sitting right in front of me?
from threading import Thread
def my_function():
while True:
if player.lives < 5:
do_stuff()
Thread(my_function).start()
However most of the times the games are developed following a frame-loop rule, with the following structure:
def my_game():
should_continue = False
while should_continue:
should_continue = update_logic()
update_graphics()
What you define in update_logic and update_graphics is up to you and the graphic library you're using (since you're using text, your function would just print text in your console), but some examples of the logic would be like this:
def update_logic():
if player.lives < 5:
return False
# these are just examples, perhaps not valid in your game
player.xdirection = 0
player.ydirection = 0
player.speed = 0
player.hitting = False
if player.damage_received_timer > 0:
player.damage_received_timer -= 1
if right_key_pressed:
player.xdirection = 1
if left_key_pressed:
player.xdirection = -1
if up_key_pressed:
player.ydirection = -1
if down_key_pressed:
player.ydirection = +1
if player.ydirection or player.xdirection:
player.speed = 20
if space_key_pressed:
player.hitting = True
# bla bla bla more logic
return True
This does not make use of threads and using threads is most of the times a bad practice if multiple events occur. However in your text games, perhaps not so much elements are involved, so it's unlikely a race condition would occur. Be careful, however. I always prefer these loops instead of threads.

How should I break down this huge function into smaller parts

I am trying to understand good design patterns in Python and I cannot think of a way to break this huge function into smaller parts without making the code cluttered, overly complex or plain ugly.
I didn't want to clutter my question by posting the whole file, I mean this function itself is already very large. But the class has only two methods: parse_midi() and generate_midi(file_name, file_length).
pitches, velocities, deltas, durations, and intervals are all MarkovChain objects. MarkovChain is a simple class with methods: add_event(event), generate_markov_dictionary(), and get_next_event(previous_event). MarkovChain.src_events is a list of events to generate the Markov chain from. It is a simple implementation of first order Markov Chains.
def parse_midi(self):
# on_notes dictionary holds note_on events until corresponding note_of event is encountered
on_notes = {}
time = 0
previous_pitch = -1
tempos = []
delta = 0
for message in self.track_in:
time += message.time
delta += message.time
# There are also MetaMessages in a midi file, such as comments, track names, etc.
# We just ignore them
if isinstance(message, mido.Message) and message.type in ["note_on", "note_off"]:
# some midi files use note_on events with 0 velocity instead of note_oof events
# so we check if velocity > 0
if message.velocity > 0 and message.type == "note_on":
on_notes[message.note] = time
self.pitches.add_event(message.note)
self.velocities.add_event(message.velocity)
self.deltas.add_event(delta)
delta = 0
if previous_pitch == -1:
self.intervals.add_event(0)
else:
self.intervals.add_event(message.note - previous_pitch)
else:
# KeyError means note_off came without a prior associated note_on event!"
# Just ignore them
with ignored(KeyError):
self.durations.add_event(time - on_notes[message.note])
del on_notes[message.note]
previous_pitch = message.note
# Tempo might be many tempo changes in a midi file, so we store them all to later calculate an average tempo
elif message.type == "set_tempo":
tempos.append(message.tempo)
elif message.type == "time_signature":
self.time_signature = self.TimeSignature(message.numerator, message.denominator,
message.clocks_per_click, message.notated_32nd_notes_per_beat)
# some tracks might be aempty in a midi file. For example they might contain comments as track name, and no note events
if len(self.pitches.src_events) == 0:
print("There are no note events in track {}!\n"
"The file has {} tracks. Please try another one.".format(self.selected_track, self.num_tracks))
exit(1)
# a midi file might not contain tempo information at all. if it does, we calculate the average
# else we just assign a default tempo of 120 bpm
try:
self.average_tempo = int(sum(tempos) / len(tempos))
except ZeroDivisionError:
self.average_tempo = mido.bpm2tempo(120)
It turns out there is not much to refactor in this method, however, the best attempt to answer this question can be found here

How to check whether script input param is among list of acceptable values?

I have a simple case here, but it made me realize I don't have a good strategy for handling more interesting input. How can I check (ideally in O(1) time), that the input is among a list of whitelisted values?
import time
global wait_time_secs
def validateUsage(arg_list):
"""Validates that the user called this program correctly and teaches the
expected usage otherwise"""
global wait_time_secs
# Discard the first argument which is always the script name
arg_list.pop(0)
# Ensure the next argument exists and is valid
if len(arg_list) < 1:
teachUsage()
sys.exit(0)
wait_time_secs = int(arg_list.pop(0))
# My hard-coded list of acceptable values. How to do for non-trivial input?
if ((10 == wait_time_secs) or (20 == wait_time_secs)) is not True:
print "invalid parameter"
teachUsage()
sys.exit(0)
def main():
validateUsage(list(sys.argv))
time.sleep(wait_time_secs)
if __name__ == '__main__':
main()
One way is to use a set to include all the acceptable values like this:
acceptable_values = set([10, 20])
and then change your condition to:
if wait_time_secs not in acceptable_values:
teachUsage()
A set will ensure O(1) lookup.

Categories