4
\$\begingroup\$

I was most interested in feedback on the overall approach to the problem, naming conventions, efficiency, etc.

  • I would like to avoid using argparse/docopt or something similar, unless it would have a major impact on complexity of the program.
  • Any tips on how should I go about adding help-dialogues for ease of use?

from collections import OrderedDict as oDct
from trianglesolver import solve, degree
from termcolor import colored, cprint
import numpy as np
import cmd



def prntDct(dct):
    for k, v in dct.items(): print(k + ' =', v)


def noNones(*args):
    return False if any(arg is None for arg in args) else True


def argify(string_in, ref_dict):
    fin = ref_dict.copy()
    string = string_in.replace(' ', ', ')
    temp = eval('dict({})'.format(string))
    matched = {k: temp[k] for k in temp if k in fin}
    fin.update(matched)
    return fin


def solvify(items):
    a,b,c,A,B,C = items

    if A is not None: A = A * degree
    if B is not None: B = B * degree
    C = C * degree

    try:
        if a is None: outs = solve(c=c, b=b, C=C)
        elif b is None: outs = solve(a=a, c=c, C=C)
        elif c is None: outs = solve(a=a, b=b, C=C)

    except:
        if noNones(a, A): outs = solve(a=a, A=A,C=C)
        elif noNones(a, B): outs = solve(a=a, B=B, C=C)
        elif noNones(b, A): outs = solve(b=b, A=A, C=C)
        elif noNones(b, B): outs = solve(b=b, B=B, C=C)
        elif noNones(c, A): outs = solve(c=c, A=A, C=C)
        elif noNones(c, B): outs = solve(c=c, B=B, C=C)

    finally:
        a,b,c,A,B,C = outs
        return ([a, b, c, A / degree, B / degree, C / degree])


def triangify(items, output_format, prec, scale=None, side='c'):
    a,b,c,A,B,C = solvify(items)

    if side is 'c': ordr = [a, b, c] + ['a', 'b', 'c']; set = [0,1]
    if side is 'b': ordr = [c, a, b] + ['c', 'a', 'b']; set = [1,2]
    if side is 'a': ordr = [c, b, a] + ['c', 'b', 'a']; set = [2,1]

    if scale is not None:
        sides_arrange = np.array(ordr[:3])
        sides_scaling = sides_arrange / ordr[2]
        sVals  = (sides_scaling * scale).tolist()
    else: sVals = ordr[:3]

    sKeys = ordr[3:]

    P = np.sum(sVals)
    S = (sVals[set[0]] * sVals[set[1]]) / 2

    fKeys = sKeys + ['A','B','C','P','S']
    toVals = sVals + [A,  B,  C,  P,  S]

    if type(prec) is int: fVals = np.around(np.array(toVals),prec)
    else: fVals = (np.around(np.array(toVals), 0)).astype(int)

    triangle = oDct(zip(fKeys, fVals))

    if output_format is 'all': output_format = 'abcABCPS'

    fin = {k: triangle[k] for k in list(output_format) if k in triangle}

    return fin



def main():
    scalar = ScalarCmd()
    print('\n' * 4)
    cprint('Triangle Scalar Tool', 'cyan')
    cprint('====================', 'cyan')
    print()
    cprint('type "help" for commands', 'cyan')
    print()
    scalar.ruler = colored('-', 'cyan')
    scalar.cmdloop()



defaults = oDct([
    ('P', None),
    ('S', None),
    ('a', None),
    ('b', None),
    ('c', None),
    ('A', None),
    ('B', None),
    ('C', 90),
    ('prec', 2),
    ('scale', 1),
    ('side', 'c'),
    ('output', 'all')])


class ScalarCmd(cmd.Cmd):
    prompt = (colored('\n>', 'green') + colored(' ', 'white'))

    def default(self, arg):
        print('invalid command, try typing "help"')

    def do_quit(self, arg):
        '''exits the program'''
        cprint('\nexiting...\n', 'red')
        exit()


    def do_solve(self, arg):
        '''type at least 2 sides or angles\n'''

        error = ['',
            'invalid command:',  # most of these docstrings are temps
            'type at least 2 sides named a/b/c or angles named A/B/C',
            'usage: TBD','']

        try:
            if len(arg) < 2:
                error[0] = '\nempty command:'
                raise

            else:
                args = argify(arg, defaults)

                sides_angles = list(args.values())[2:8]

                outs = triangify(
                    sides_angles,
                    args['output'],
                    args['prec'])

                print()
                prntDct(outs)
                print()  # <- easy blank lines

        except Exception:
            print(*error, sep='\n')
            return

    def do_scale(self, arg):
        '''type at least 2 sides or angles\n'''

        error = ['',
            'invalid command:',
            'type at least 2 sides named a/b/c or angles named A/B/C',
            'usage: TBD','']

        try:
            if len(arg) < 2:
                error[0] = '\nempty command:'
                raise

            else:
                args = argify(arg, defaults)

                sides_angles = list(args.values())[2:8]

                outs = triangify(
                    sides_angles,
                    args['output'],
                    args['prec'],
                    args['scale'],
                    args['side'])

                print()
                prntDct(outs)
                print()  # <- easy blank lines

        except Exception:
            print(*error, sep='\n')
            return



if __name__ == '__main__': main()

I'm still quite an amateur with python programming, so I thank you in advance for your feedback!

\$\endgroup\$

2 Answers 2

4
\$\begingroup\$

General

  • Personally I would prefer putting the clauses if if and else statements on separate lines even if they are only one line long.
  • You should avoid using a generic except without specifying an exception or using a generic Exception. Instead, you should catch the specific exception you expect the code to throw. Otherwise, you may end up catching TypeError or some other exception that indicates a bug and that should be propagated instead of caught. From PEP 8:

    A good rule of thumb is to limit use of bare 'except' clauses to two cases:

    1. If the exception handler will be printing out or logging the traceback; at least the user will be aware that an error has occurred.
    2. If the code needs to do some cleanup work, but then lets the exception propagate upwards with raise. try...finally can be a better way to handle this case.
  • The main method should be at the end, just before the if __name__=="__main__" statement.

Triangle Solver

  • solvify should return a tuple instead of a list since each index has a specific meaning.
  • argify should not use eval; instead parse the input string directly.
  • Use the equality operator (side=='c') instead of the is keyword for comparing strings.
  • Use isinstance for type checks, as in if isinstance(prec,int) instead of if type(prec) is int

ScalarCmd

  • This should be refactored into a separate file to separate the UI and the backend.
  • try...raise inside do_solve has no exception to raise.
  • I am not sure if this is possible using termcolor but the cmd.Cmd.cmdloop() has an intro parameter that can be used for printing the intro banner.

For more information about Python style conventions PEP 8 is a good place to start.

\$\endgroup\$
2
  • \$\begingroup\$ what do you mean by "parse the input string directly"? \$\endgroup\$
    – Shui
    Apr 8, 2018 at 19:30
  • \$\begingroup\$ You should write some code to parse the string passed in via string_in instead of using eval. \$\endgroup\$
    – rlee827
    Apr 8, 2018 at 23:25
3
\$\begingroup\$

Your choice of variable names is strange. There is no reason to leave out the i in print and dict, but keep it in argify and solvify. Be consistent and keep them all, or, ltrntvly, rmv ll th vwls.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.