Improvements for first script? - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: General (https://python-forum.io/forum-1.html) +--- Forum: Code Review (https://python-forum.io/forum-46.html) +--- Thread: Improvements for first script? (/thread-31681.html) |
Improvements for first script? - alloydog - Dec-27-2020 My way of learning is to think of something I need doing, then try a line, step by step, googling each error message and so on. I *don't* look for a complete script, that someone else has already done, to modify. I know it's re-inventing the wheel, but it works for me :) Anyway, this is my first ever python script. It very simple, but it works. But, I'm just wondering if it is OK as it stands, or could it be a bit more elegant? I mean, would a grizzled old Python hack give a gentle nod of his head and say something like "Not bad, not bad. For a hardware engineer", or would he curl up in the corner, realising 2020 just got worse?... EDIT: I know it needs things like making sure you can only input numeric values where they're required. # Convert millivolts to engineering units print ("4-20 mA to Engineering units convertor") print ("") def main(): # Set engineering units and range measUnits = input("Enter the units of measurement: ") measMin = input("Enter the minimum measurement value: ") measMin2 = abs(float(measMin)) # removes "minus" bit measMax = input("Enter the maximum measurement value: ") measRange = float(measMin2) + float(measMax) print ("Measurement range is {} {}".format(measRange, measUnits)) # Input measured value rawValue = input("Input mV value: ") # Calculate measured value step1 = float(rawValue) - 400 step2 = step1 / 1600 measValue = (step2 * measRange) - measMin2 output = round(measValue,2) print ("Measured value is {} {}".format(output, measUnits)) main() # Restart? yay or nay? restart = input("Do you want to convert another value? Y/N: ") if restart.lower() == 'y': main() else: print ("Goodbye") RE: Improvements for first script? - perfringo - Dec-28-2020 Addressing style part of code: I think that it would be good idea to read and implement PEP 8 -- Style Guide for Python Code, specifically 'Function and Variable names': Quote:Function names should be lowercase, with words separated by underscores as necessary to improve readability. I would remove meas part from majority of variable names as it is not needed (but look for not clashing with built-in functions like min, max, range).I would use f-strings instead of .format :>>> name = 'Bob' >>> occupation = 'The Builder' >>> f'Hello {name} {occupation}' 'Hello Bob The Builder'There is no need for empty space after print . It will not raise error, but it's not a good style.
RE: Improvements for first script? - alloydog - Dec-28-2020 Thanks for the tips. Didn't know about the style guide - Heading over to there ASAP. For not using uppercase letters, then, measUnits would become meas_units. I appreciate your comment about not needing to use "meas" everywhere, but I have reading issues and it helps me follow what's going on :) I will also check out "f-strings". Are they related to g-strings EDIT - updated script: # Convert millivolts to engineering units print("4-20 mA to Engineering units convertor") print("") def main(): # Set engineering units and range meas_units = input("Enter the units of measurement: ") # Input minimum value of measurement range while True: try: meas_min = float(input("Enter the minimum measurement value: ")) except ValueError: print("Must be a numeric value...") continue else: break meas_min2 = abs(float(meas_min)) # removes "minus" bit # Input maximum value of measurement range while True: try: meas_max = float(input("Enter the maximum measurement value: ")) except ValueError: print("Must be a numeric value...") continue else: break # Display measurement range meas_range = float(meas_min2) + float(meas_max) print(f"Measurement range is {meas_min} to {meas_max} {meas_units}") # Input measured value while True: try: raw_value = float(input("Input mV value: ")) except ValueError: print("Must be a numeric value...") continue else: break # Calculate measured value step1 = float(raw_value) - 400 step2 = step1 / 1600 meas_value = (step2 * meas_range) - meas_min2 output = round(meas_value,2) print("Measured value is {} {}".format(output, meas_units)) restart = input("Do you want to convert another value? Y/N: ") print("") if restart.lower() == 'y': main() else: print("Goodbye") main() RE: Improvements for first script? - perfringo - Dec-30-2020 Some observations. There is the repetition of input validation. If such pattern emerges then it is clear that function should be used. Currently the code is not DRY (Don't Repeat Yourself). Simple validation function should be used, something along those lines: def validate(request): while True: answer = input(request) try: return abs(float(answer)) except ValueError: print(f'Expected numeric value but {answer!r} was entered')Now you can use this or similar function when you need to validate user input to be numeric and absolute value. You can be 'clever' and use loop and unpacking while using it: low, high = (validate(f'Enter the {boundary} measurement value: ') for boundary in ['minimum', 'maximum']) RE: Improvements for first script? - alloydog - Dec-30-2020 I will give that a bash - always good to reduce typing ;) Thanks! RE: Improvements for first script? - DeaD_EyE - Dec-30-2020 If I see PLC Content, I can't resist... Just as additional information: You can have different measurement ranges. The most used ranges I know: Current:
Voltage:
The namedtuple is very useful. from collections import namedtuple def select_range(): Range = namedtuple("range", "text low high unit") # just to have dot access ranges = [ Range("0 - 10 V", 0, 10, "V"), Range("-10 - +10 V", -10, 10, "V"), Range("0 - 20 mA", 0, 20, "mA"), Range("4 - 20 mA", 4, 20, "mA"), ] while True: for idx, key in enumerate(ranges, 1): print(f"{idx}) {key.text}") print() selector = input("Please select a range: ") try: # two possible Exceptions # ValueError if the conversion to an int is not possible # IndexError if you selected a not existing index return ranges[int(selector) - 1] except (ValueError, IndexError): # catching this two exceptions to continue the while True loop # and ask again print("Invalid input")I named the namedtuple Range, which is not a good name. It has nothing to do with range() .If the input was valid, the return value is a namedtuple. You can access the values with dot.: my_range = select_range() print(my_range.text) low, high = my_range.low, my_range.high unit = my_range.unitAnd to normalize and scale, I use these functions: def scale(value, low, high): """ Scale the value from low .. high >>> scale(0, -10, 10) -10 >>> scale(1, -10, 10) 10 """ return value * (high - low) + low def normalize(value, low, high): """ Normalize value to 0 .. 1 >>> normalize(0, 0, 10) 0.0 >>> normalize(10, 0, 10) 1.0 >>> normalize(5, 0, 10) 0.5 """ return (value - low) / (high - low)Example with selected range: # normalize 0 .. 27648 to 0 .. 1 # scale 0 .. 1 to 0 .. 100 percent = scale(normalize(15000, 0, 27648), 0, 100)The thing is, if you know the minimum range and maximum range of the detected physical quantity, the step to convert it to a current or voltage, is not required. Only on PLC-Side the right range must be selected and the right connections must be used. Example: https://cdn.sick.com/media/pdf/7/47/747/dataSheet_UC30-214163_6054712_en.pdf The operating range is: 350 mm ... 3400 mm The scaling with a Siemens PLC will look like this: input_value = 1337 normalized_value = normalize(input_value, 0, 27648) # 0 - 10 V range # the 27648 is the highest value of Rated Range scaled = scale(normalized_value, 350, 3400)In real world applications you can get also values, which are out of normal range. For example, the sensor could output a higher current/voltage than 10V/20mA to signalling the PLC that something is not in operation range. If the range is 4 - 20 mA, then you can also detect a broken cable. In this case, the current is 0 mA. SIEMENS has defined these areas for example:
I've also made for this a function (but without the units): def scale_siemens(value, min_scale, max_scale, bipolar=False): """ Scale Analog Values with siemens components: https://support.industry.siemens.com/cs/mdm/8859629?c=23218577931&t=1&s=27648&lc=en-DE Look at Table: Representation of analog values in the ±10 V output range """ if bipolar: normalized = normalize(value, -27648, 27648) else: normalized = normalize(value, 0, 27648) ranges = (-32513, -27649, 27648, 32511, 32767) areas = [ "Underflow, at zero voltage and current", "Undershoot range", "Rated range", "Overshoot range", "Overflow, off power", ] idx = bisect.bisect_left(ranges, value) try: kind = areas[idx] except IndexError: kind = areas[-1] if idx == 0 or idx + 1 >= len(ranges): return 0.0, kind return scale(normalized, min_scale, max_scale), kindThe interesting part here is the use of the bisect module. Otherwise, you've a pattern like: value = 32760 # overshoot range # From High to low... if 32512 <= value <= 32767: kind = "Overflow, off power" elif 27649 <= value <= 32511: kind = "Overshoot range" elif -27648 <= value <= 27648: kind = "Rated range" elif -32512 <= value <= -27649: kind = "Undershoot range" elif -32768 <= value <= -32513: kind = "Underflow, at zero voltage and current" print(kind) RE: Improvements for first script? - alloydog - Dec-30-2020 Thanks for extras! I'll have a look through them, as I'm thinking about expanding my script later on - it was written as "My First Python Script", as earlier I did the same thing in HTML and javascript at work. We sometimes do quick tests on 4-20mA output sensors by strapping a 100 ohm resistor across them and measuring the voltage drop. RE: Improvements for first script? - Mark17 - Dec-30-2020 (Dec-28-2020, 05:25 AM)perfringo Wrote: I would use f-strings instead of Is that just a preference or something more? RE: Improvements for first script? - alloydog - Jan-01-2021 Apparently f-strings are the new improved way of doing it... https://realpython.com/python-f-strings/ RE: Improvements for first script? - perfringo - Jan-01-2021 (Jan-01-2021, 01:45 PM)alloydog Wrote: Apparently f-strings are the new improved way of doing it... In addition to comprehensive information in this very good coverage of f-strings I would add feature what is available from Python <= 3.8: f-strings support = for self-documenting expressions and debugging. >>> spam = 'Life, The Universe and Everything' >>> print(f'{spam=}') spam='Life, The Universe and Everything' |