1

I'm writing a Python program that will perform calculations for the power triangle, and need some help figuring out if my way of picking equations is the most efficient.

If you're not familiar, it's just an application of the law of cosines. Out of the five possible inputs, I only need two, and can calculate the others. I'm also implementing matplotlib to visualize the output, but for now, I just want to get the math running.

I'm using tkinter to get the input, and a dictionary to store the inputs.

fields = 'kW', 'kVA', 'kVAR', 'PF', 'Theta'
calcInputs = {}

def fetch(entries):
    for entry in entries:
        field = entry[0]
        text = entry[1].get() # User input for each textbox
        print('%s: %s' % (field, text)) # Print to stdout with name
        if len(text) == 0:
            pass # Don't update dictionary with nulls
        else:
            calcInputs.update({field:text}) # name : value updated to dictionary

Later I store each dictionary entry as a variable, which I'll cast to a float later.

kW = calcInputs.get('kW')
kVA = calcInputs.get('kVA')
kVAR = calcInputs.get('kVAR')
pF = calcInputs.get('PF')
theta = calcInputs.get('Theta')

This is a placeholder that lets me know which ones are empty:

for i in calcInputs:
    if i is not None:
        print({i})
    else:
        pass

Do I make a function for each combination of Nones, and call the appropriate one based on what data I have? That seems like the most straight-forward, but I feel like there must be a better way.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Stephan Garland
  • 155
  • 1
  • 3
  • 10

2 Answers2

3

Let's make some of your code a little more "Pythonic" first. You wrote:

calcInputs = {}

def fetch(entries):
    for entry in entries:
        field = entry[0]
        text = entry[1].get() # User input for each textbox
        print('%s: %s' % (field, text)) # Print to stdout with name
        if len(text) == 0:
            pass # Don't update dictionary with nulls
        else:
            calcInputs.update({field:text}) # name : value updated to dictionary

This has the problem that calcInputs is going to be persistent - that is, it will stay around until the next cycle (assuming there are multiple cycles) and so might keep data around from one time to the next. Let's initialize that empty dictionary inside the function, so we always start with a clean slate. (Note: if the entry box keeps its value from cycle to cycle, that's okay with me - the user can see it and clean it if she wants.)

def fetch(entries):
    inputs = {}

This next bit seems okay, although I'm not familiar with TkInter under Python:

    for entry in entries:
        field = entry[0]
        text = entry[1].get()

This is obviously debug code - keep it until you're happy.

        print('%s: %s' % (field, text)) # Print to stdout with name

The next part is "just wrong." It looks like you're coming from Java - which is a great language to come from. But we can do things a little cleaner because of "truthiness" and syntactic sugar. In Python, strings are "true" if they aren't empty, and dictionaries can be indexed using dict[key] notation:

        if text:
            inputs[field] = text

And that's it. But since we're not using a global variable, let's go ahead and return the result:

    return inputs

Now you can call fetch, and use the result. Or, you could reassign the result to a global, if you like:

    global calcInputs
    calcInputs = inputs  # instead of return inputs

Once you have the inputs, go ahead and process them. You have a problem in that you don't actually know which inputs you've been given. One easy way is just a series of if/then statements involving two values, which we can make easier by defining some constants (so as not to have to type quote marks all the time):

KW = 'kW'
PF = 'PF'
THETA = 'Theta'
:

calcInputs = fetch(entries)  # Or however you choose to init calcInputs

if KW in calcInputs and PF in calcInputs:
    triangle_from_KWPF(calcInputs)
elif KW in calcInputs and THETA in calcInputs:
    triangle_from_KWTHETA(calcInputs)
elif ...
  :
else:
    report_error("You didn't provide enough inputs! I need at least 2!")

Python's in operator works for dictionaries and sets as a key- or membership test, for strings as a substring search, and lists as a linear scan.

This approach is the binary style sequence that @Alden describes, with everything spelled out.

Another way would be to create a hashable object that encodes your given parameters, and use a dictionary to dispatch them:

given_keys = []

for key in sorted(fields):
    if key in calcInputs:
        given_keys.append(key)
        if len(given_keys) == 2:
            break
else:  # Fell through
    report_error("You didn't provide enough inputs! I need at least 2!")

# given_keys has 2 field-names in it. Concatenate them.
calc_key = '_'.join(given_keys)

# calc_key looks like "KW_Theta"

dispatch = {  # dictionary: string -> function
    'KW_Theta' : triangle_from_KW_Theta,
    'KW_PF'    : triangle_from_KW_PF,
     # etc....
}

calc_function = dispatch[calc_key]

calc_function(calcInputs)

Note: for...else is a valid Python form. It runs the else clause when the for loop exhausts its iterable. So it is pretty much tailor-made for a block with a break statement in the middle.

Community
  • 1
  • 1
aghast
  • 14,785
  • 3
  • 24
  • 56
  • Fixed the calcInput not clearing, thank you. I'm actually more familiar with Python than Java (only used Java for college), but clearly not that great with either. The "if text:" part is basically like While true:, I think? Relying on an intrinsic property? The hashing/concatenating method seems like the most compact and readable to me, so I think I'll try implementing that. Thank you! – Stephan Garland Apr 13 '17 at 21:27
  • For bonus points, if you pick your function names correctly, then the dictionary returned by `globals()` can be the dispatch dictionary- just compute the actual function name, look it up in the current module, and call it! – aghast Apr 14 '17 at 03:20
1

You could use a simple binary mapping to see what your inputs are and apply the proper function. Here is an example:

kW = input('kW')
kVA = input('kVA')
kVAR = input('kVAR')
pF = input('PF')
theta = input('Theta')

inputs = (kW, kVA, kVAR, pF, theta)

def cmp(t):
    z = zip(inputs, t)
    z = [a and b for a, b in z]
    return sum(z) == sum(t) # 2

if   cmp((1,1,0,0,0)):
    pass # kW and kVA given
elif cmp((1,0,1,0,0)):
    pass # kW and kVAR given
elif cmp((1,0,0,1,0)):
    pass
elif cmp((1,0,0,0,1)):
    pass
elif cmp((0,1,1,0,0)):
    pass
elif cmp((0,1,0,1,0)):
    pass
elif cmp((0,1,0,0,1)):
    pass
elif cmp((0,0,1,1,0)):
    pass
elif cmp((0,0,1,0,1)):
    pass
elif cmp((0,0,0,1,1)):
    pass
Alden
  • 2,229
  • 1
  • 15
  • 21
  • That looks very compact, but I fear I would struggle with updating it in the future. I only do coding when I have a specific task, and then often don't update the tools for months. I appreciate the lesson on another way to accomplish something, though! – Stephan Garland Apr 13 '17 at 21:24