# Python Trig Calculator

It's big, it's ugly, but it should work. My question is: how could I have implemented the "typeselection" function so it is less repetitive? Any tips on other improvements in coding style are welcomed as well.

import math
import turtle
class Triang:

#MATHS FUNCTIONS; THESE GET CALLED WHEN PROGRAM HAS DECIDED ON TRIANGLE TYPE

def dass(self, arg, arg1, arg2, ans, args, args1, args2):
arg2=math.radians(arg2)
arg1=math.radians(arg1)
print (ans, "= ( ", args, "* sin (", args1, ") / ( sin (", args2, ") )")
a=(arg*math.sin(arg1))/math.sin(arg2)
print (ans, "=", a)
return a

def sss1(self, arg, arg1, arg2, ans, args, args1, args2):
print (ans, "= acos ( (", args, "^ 2 +", args1, "^ 2 -", args2, "^ 2 ) / ( 2 *", args, "*", args1, ") )")
a=math.degrees(math.acos((arg**2+arg1**2-arg2**2)/(2*arg*arg1)))
print (ans, "=", a)
return a

def sas(self, arg, arg1, arg2, ans, args, args1, args2):
arg2=math.radians(arg2)
print (ans, "^ 2 =", args, "^ 2 +", args1, "^ 2 - 2 *", args1, "* cos ( ", args2, ")")
a=(arg**2+arg1**2-2*arg1*arg1*math.cos(arg2))
print (ans, "^ 2 =", a)
if a < 0:
self.nottrue()
else:
a=math.sqrt(a)
print (ans, "=", a)
return (a)

def ssa_acute(self, arg, arg1, arg2, ans, args, args1, args2) :
print ("TRIANGLE MAY HAVE TWO POSSIBLE SOLUTIONS")
print ("ATTEMPTING TO CALCULATE BOTH")

print (" ")
arg=math.radians(arg)
print ("ACUTE SIN-1 ATTEMPT..")
print (ans, "= asin ( sin (", args, ") *", args1, "/", args2, ")")
try :
a=math.degrees(math.asin(math.sin(arg)*arg1/arg2))

except ValueError:
print ("ERROR, ANGLE CANNOT BE ACUTE")
a=False
else:
if a < 0:
a=False
print ("ERROR, ANGLE CANNOT BE ACUTE")
else:
print ("POSSIBLE TRIANGLE!")
print (ans, "=", a)

print (" ")
print ("OBTUSE SIN-1 ATTEMPT..")
print ("OBTUSE POSSIBLILITY =")
print (ans, "= 180 - asin ( sin (", args, ") *", args1, "/", args2, ")")
try :
a_obtuse=180-math.degrees(math.asin(math.sin(arg)*arg1/arg2))
except ValueError:
print ("ERROR TRIANGLE CANNOT BE OBTUSE")
a_obtuse=False
else:
if a_obtuse < 0:
a_obtuse=False
print ("ERROR TRIANGLE CANNOT BE OBTUSE")
else:
print (ans, "=", a_obtuse)
if a_obtuse==a:
a_obtuse=False
print ("BOTH ANSWERS WERE THE SAME")

else:
self.obtusesel=True

print (" ")
return (a, a_obtuse)

#OTHER FUNCTIONS

def nottrue(self):
print ("INVALID TRIANGLE")
a=input()

def faktri(self):
print ("INVALID TRIANGLE")
self.new=Triang()

def printall(self):
print (" ")
print (" ")
print ("RESULTS")
print ("IF A SIDE/ANGLE IS FALSE OR NEGATIVE, DISCARD AS A REAL TRIANGLE")
print (" ")
print (" ")
namlist=["a", "b", "c", "ab", "ac", "bc"]
loclist=[self.a, self.b, self.c, self.ab, self.ac, self.bc]
locnum=0
for k in namlist:
x=loclist[locnum]
print (k, "=", x)
locnum = locnum + 1

if self.obtusesel==True:
locnum=0
print (" ")
print (" ")
namlist=["O a", "O b", "O c", "O ab", "O ac", "O bc"]
loclist=[self.oa, self.ob, self.oc, self.oab, self.oac, self.obc]
for k in namlist:
x=loclist[locnum]
print (k, "=", x)
locnum = locnum + 1

if self.a and self.b and self.c and self.ab and self.ac and self.bc:
graphit(self.a, self.b, self.c, self.ab, self.ac, self.bc)

if self.oa and self.ob and self.oc and self.oab and self.oac and self.obc:
graphit(self.oa, self.ob, self.oc, self.oab, self.oac, self.obc)

self.new=Triang()

# THIS IS THE COLLECTION SECTION
# ALSO PERFORMS a + b + c = 180
def __init__(self):
self.obtusesel=False
self.determin=0
self.oa = None
self.ob = None
self.oc = None
self.oab = None
self.oac = None
self.obc = None
self.invalid=0
self.known=0
print ("WELCOME TO THE SECOND EDITION OF THE LEWIS TRIG CALC")
print (" ")
print ("""THE PROGRAM IS SIMPLE TO USE. SIMPLY ENTER THE VALUES YOU KNOW
AND IT WILL COMPUTE THE UNKNOWN VALUES. THIS REQUIRES THREE VALUES
AT A MINIMUM AND THREE ANGLES WILL NOT WORK. TO ENTER SIMPLY ENTER THE VALUE
FOR THE GIVEN SIDEN/ANGLE (NO UNITS). IF THE SIDE HAS AN UNKNOWN VALUE HIT X""")
print (" ")
print ("FIRST THE ANGLES")
self.angles()

def deter(self):
print ("TYPE DETERMINED!")
print ("PERFORMING APPROPRIATE MATHS METHODS...")
self.determin=1

def nvalid(self):
print (" ")
print ("INVALID ENTRY!")
print (" ")
if self.invalid >= 3 :
self.sides()
else :
self.angles()

def obtusecol(self):
self.oa = self.a if self.a else False
self.ob = self.b if self.b else False
self.oc = self.c if self.c else False
self.oab = self.ab if self.ab else False
self.oac = self.ac if self.ac else False
self.obc = self.bc if self.bc else False

def collector(self, arg, arg1, arg2="A"):
print (" ")
print ("Enter the value for,", arg1, arg)
if arg1=="side":
print ("Some people would call that side", arg2)

a=input(">>")
try:
a=float(a)
except ValueError:
if a=="x" or a=="X":
a=False

else:
self.nvalid()
else:
self.known=self.known + 1
return (a)

def angles(self):
if self.invalid==0:
self.a=self.collector("a", "angle")
self.invalid = 1
if self.invalid==1:
self.b=self.collector("b", "angle")
self.invalid = 2
if self.invalid==2:
self.c=self.collector("c", "angle")
self.invalid = 3

if self.a and self.b and self.c:
pass
else:
if self.a and self.b:
print (" ")
print ("3a = 180 DEGREE MATH")
self.c=180-self.a-self.b
print ("c = 180 - a - b")
print ("c =", self.c)
self.known=self.known + 1
elif self.a and self.c:
print (" ")
print ("3a = 180 DEGREE MATH")
self.b=180-self.a-self.c
print ("b = 180 - a - c")
print ("b =", self.b)
self.known=self.known + 1
elif self.c and self.b:
print (" ")
print ("3a = 180 DEGREE MATH")
self.a=180-self.c-self.b
print ("a = 180 - c - b")
print ("a =", self.a)
self.known=self.known + 1
print (" ")
print ("Now on to the sides")
self.sides()

def sides(self):
if self.invalid==3:
self.ab=self.collector("ab", "side", "C")
self.invalid = 4
if self.invalid==4:
self.ac=self.collector("ac", "side", "B")
self.invalid = 5
if self.invalid==5:
self.bc=self.collector("bc", "side", "A")
del self.invalid
self.typeselection()

def typeselection(self):
#DECIDES TYPE OF TRIANGLE AND THEN CALLS THE CORRECT MATHS METHOD
print ("TESTING TRIANGLE TYPE...")

#AAS/ASA TRIANGLES

if self.a and self.b and self.c:
if self.ab and not self.bc and not self.ac:
self.deter()
self.ac=self.dass(self.ab, self.b, self.c, "ac", "ab", "b", "c")
self.bc=self.dass(self.ac, self.a, self.b, "bc", "ac", "a", "b")
elif self.bc and not self.ab and not self.ac:
self.deter()
self.ab=self.dass(self.bc, self.c, self.a, "ab", "bc", "c", "a")
self.ac=self.dass(self.bc, self.b, self.a, "ac", "bc", "b", "a")
elif self.ac and not self.bc and not self.ab:
self.deter()
self.ab=self.dass(self.ac, self.c, self.b, "ab", "ac", "c", "b")
self.bc=self.dass(self.ac, self.a, self.b, "bc", "ac", "a", "b")
elif self.ab and self.bc and not self.ac:
self.deter()
self.ac=self.dass(self.bc, self.b, self.a, "ac", "bc", "b", "a")
elif self.ab and self.ac and not self.bc:
self.deter()
self.bc=self.dass(self.ab, self.a, self.c, "bc", "ab", "a", "c")
elif self.bc and self.ac and not self.ab:
self.deter()
self.ab=self.dass(self.ac, self.c, self.b, "ab", "ac", "c", "b")

#SSS TRIANGLES

if self.ab and self.ac and self.bc:
if not self.a and not self.b and not self.c:
self.deter()
self.a=self.sss1(self.ac, self.ab, self.bc, "a", "ac", "ab", "bc")
self.b=self.sss1(self.bc, self.ab, self.ac, "b", "bc", "ab", "ac")
self.c=180-self.b-self.a
print ("c = 180 - b - a")
print ("c =", self.c)
elif self.a and not self.b and not self.c:
self.deter()
self.b=self.sss1(self.bc, self.ab, self.ac, "b", "bc", "ab", "ac")
self.c=180-self.b-self.a
print ("c = 180 - b - a")
print ("c =", self.c)
elif self.b and not self.a and not self.c:
self.deter()
self.a=self.sss1(self.ac, self.ab, self.bc, "a", "ac", "ab", "bc")
self.c=180-self.b-self.a
print ("c = 180 - b - a")
print ("c =", self.c)
elif self.c and not self.a and not self.b:
self.deter()
self.a=self.sss1(self.ac, self.ab, self.bc, "a", "ac", "ab", "bc")
self.b=180-self.a-self.c
print ("b = 180 - c - a")
print ("b =", self.b)

#SAS TRIANGLES
if self.a and not self.b and not self.c and self.ab and self.ac and not self.bc:
self.deter()
self.bc=self.sas(self.ac, self.ab, self.a, "bc", "ac", "ab", "a")
self.b=self.sss1(self.bc, self.ab, self.ac, "b", "bc", "ab", "ac")
self.c=180-self.b-self.a
print ("c = 180 - b - a")
print ("c =", self.c)

elif not self.a and self.b and not self.c and self.ab and not self.ac and self.bc:
self.deter()
self.ac=self.sas(self.bc, self.ab, self.b, "ac", "bc", "ab", "b")
self.a=self.sss1(self.ac, self.ab, self.bc, "a", "ac", "ab", "bc")
self.c=180-self.b-self.a
print ("c = 180 - b - a")
print ("c =", self.c)

elif not self.a and not self.b and self.c and not self.ab and self.ac and self.bc:
self.deter()
self.ab=self.sas(self.bc, self.ac, self.c, "ab", "bc", "ac", "c")
self.a=self.sss1(self.ac, self.ab, self.bc, "a", "ac", "ab", "bc")
self.b=180-self.c-self.a
print ("b = 180 - c - a")
print ("b =", self.b)

#SSA TRIANGLES

elif self.a and not self.b and not self.c and self.ab and self.bc and not self.ac :
self.deter()
self.obtusecol()
self.c=self.ssa_acute(self.a, self.ab, self.bc, "c", "a", "ab", "bc")
self.oc=self.c[1]
self.c=self.c[0]
if self.c:
try:
self.b=180-self.a-self.c
print ("b = 180 - a - c")
print ("b =", self.b)

self.ac=self.dass(self.bc, self.b, self.a, "ac", "bc", "b", "a")

except ValueError:
print ("ERROR ANGLE CANNOT BE ACUTE")
self.determin=0

if self.oc:
try:
print (" ")
print ("OBTUSE MATH")
self.ob=180-self.oa-self.oc
print ("b = 180 - a - c")
print ("b =", self.ob)
self.oac=self.dass(self.obc, self.ob, self.oa, "ac", "bc", "b", "a")

except ValueError:
print ("ERROR ANGLE CANNOT BE OBTUSE")
self.obtusesel=False

elif self.a and not self.b and not self.c and not self.ab and self.ac and self.bc :
self.deter()
self.obtusecol()
self.b=self.ssa_acute(self.a, self.ac, self.bc, "b", "a", "ac", "bc")
self.ob=self.b[1]
self.b=self.b[0]
if self.b:
try:
self.c=180-self.a-self.b
print ("ACUTE MATH")
print ("c = 180 - a - b")
print ("c =", self.c)
self.ab=self.dass(self.bc, self.c, self.a, "ab", "bc", "c", "a")

except ValueError:
print ("ERROR ANGLE CANNOT BE ACUTE")
self.determin=0

if self.ob:
try:
print (" ")
print ("OBTUSE MATH")
self.oc=180-self.oa-self.ob
print ("c = 180 - a - b")
print ("c =", self.oc)
self.oab=self.dass(self.obc, self.oc, self.oa, "ab", "bc", "c", "a")

except ValueError:
print ("ERROR ANGLE CANNOT BE OBTUSE")
self.obtusesel=False

elif not self.a and self.b and not self.c and self.ab and self.ac and not self.bc :
self.deter()
self.obtusecol()
self.c=self.ssa_acute(self.b, self.ab, self.ac, "c", "b", "ab", "ac")
self.oc=self.c[1]
self.c=self.c[0]
if self.c:
try:
self.a=180-self.c-self.b
print ("ACUTE MATH")
print ("a = 180 - c - b")
print ("a =", self.a)
self.bc=self.dass(self.ac, self.a, self.b, "bc", "ac", "a", "b")

except ValueError:
print ("ERROR ANGLE CANNOT BE ACUTE")
self.determin=0

if self.oc:
try:
print (" ")
print ("OBTUSE MATH")
self.oa=180-self.oc-self.ob
print ("a = 180 - c - b")
print ("a =", self.oa)
self.obc=self.dass(self.oac, self.oa, self.ob, "bc", "ac", "a", "b")

except ValueError:
print ("ERROR ANGLE CANNOT BE OBTUSE")
self.obtusesel=False

elif not self.a and self.b and not self.c and not self.ab and self.ac and self.bc:
self.deter()
self.obtusecol()
self.a=self.ssa_acute(self.b, self.bc, self.ac, "a", "b", "bc", "ac")
self.oa=self.a[1]
self.a=self.a[0]
if self.a:
try:
self.c=180-self.a-self.b
print ("ACUTE MATH")
print ("c = 180 - a - b")
print ("c =", self.c)
self.ab=self.dass(self.ac, self.c, self.b, "ab", "ac", "c", "b")

except ValueError:
print ("ERROR ANGLE CANNOT BE ACUTE")
self.determin=0

if self.oa:
try:
print (" ")
print ("OBTUSE MATH")
self.oc=180-self.oa-self.ob
print ("c = 180 - a - b")
print ("c =", self.oc)
self.oab=self.dass(self.oac, self.oc, self.ob, "ac", "ac", "c", "b")

except ValueError:
print ("ERROR ANGLE CANNOT BE OBTUSE")
self.obtusesel=False

elif not self.a and not self.b and self.c and self.ab and self.ac and not self.bc:
self.deter()
self.obtusecol()
self.b=self.ssa_acute(self.c, self.ac, self.ab, "b", "c", "ac", "ab")
self.ob=self.b[1]
self.b=self.b[0]
if self.b:
try:
self.a=180-self.c-self.b
print ("ACUTE MATH")
print ("a = 180 - c - b")
print ("a =", self.a)
self.bc=self.dass(self.ab, self.a, self.c, "bc", "ab", "a", "c")

except ValueError:
print ("ERROR ANGLE CANNOT BE ACUTE")
self.determin=0

if self.ob:
try:
print (" ")
print ("OBTUSE MATH")
self.oa=180-self.oc-self.ob
print ("a = 180 - c - b")
print ("a =", self.oa)
self.obc=self.dass(self.oab, self.oa, self.oc, "bc", "ab", "a", "c")

except ValueError:
print ("ERROR ANGLE CANNOT BE OBTUSE")
self.obtusesel=False

elif not self.a and not self.b and self.c and self.ab and self.bc and not self.ac:
self.deter()
self.obtusecol()
self.a=self.ssa_acute(self.c, self.bc, self.ab, "a", "c", "bc", "ab")
self.oa=self.a[1]
self.a=self.a[0]
if self.a:
try:
self.b=180-self.c-self.a
print ("ACUTE MATH")
print ("b = 180 - c - a")
print ("b =", self.b)
self.ac=self.dass(self.ab, self.b, self.c, "ac", "ab", "b", "c")

except ValueError:
print ("ERROR ANGLE CANNOT BE ACUTE")
self.determin=0

if self.oa:
try:
print (" ")
print ("OBTUSE MATH")
self.ob=180-self.oc-self.oa
print ("b = 180 - c - a")
print ("b =", self.ob)
self.oac=self.dass(self.oab, self.ob, self.oc, "ac", "ab", "b", "c")

except ValueError:
print ("ERROR ANGLE CANNOT BE OBTUSE")
self.obtusesel=False

if self.determin==1:
self.printall()

else:
print (" ")
print ("Unable to compute triangle")
print (" ")
self.new=Triang()

#Turtle graphics section
def graphit(anga, angb, angc, sidab, sidac, sidbc):
print ("ENTERING DRAWING MODE (BETA)")
print ("PRESS ANY KEY TO CONTINUE")
pause=input()
rt=turtle
rt.clearscreen()
rt.screensize(2000, 1500)
rt.ht()
numbers=[sidab, sidac, sidbc]
newnumb=numbers

newnumb.sort()
bigest=newnumb[2]
if bigest is sidab:
coll=ratiosizer(sidab, sidac, sidbc)
if sidac!=sidab:
sidac=coll[1]
else:
sidac=300
if sidbc!=sidab:
sidbc=coll[2]
else:
sidbc=300
sidab=coll[0]
elif bigest is sidac:
coll=ratiosizer(sidac, sidab, sidbc)
if sidab !=sidac:
sidab=coll[1]
else:
sidab=300
if  sidbc!=sidac:
sidbc=coll[2]
else:
sidbc=300
sidac=coll[0]
elif bigest is sidbc:
coll=ratiosizer(sidbc, sidac, sidab)
if sidab != sidbc:
sidab = coll[2]
else:
sidab = 300

if sidac != sidbc:
sidac = coll[1]
else:
sidac = 300
sidbc=coll[0]
rt.color("black", "red")
rt.begin_fill()
acor=t_mover(rt, anga, sidab)
bcor=t_mover(rt, angb, sidbc)
ccor=t_mover(rt, angc, sidac)
rt.end_fill()
print ("PRESS ANY KEY TO CONTINUE")
pause=input()
return True

#Moves turtle, and resets it (spins 180 degrees) for next move
def t_mover(self, angle, distance):
pripos=self.pos()
self.left(angle)
self.forward(distance)
self.right(180)
return pripos

#Resizes triangles
def ratiosizer(arg, arg1, arg2):
rat=300/arg
frarg=arg1*rat
secarg=arg2*rat
return [300, frarg, secarg]

cat=Triang()

• Can you simply sort the knowns and the unknowns - in essence, renaming "a,b,c" so "c" is always the unknown. Then you only need one calculation, and "unsort" at the end. – Floris May 6 '15 at 14:29

## Just general style

What the other comment said is true, the style can be improved here.

• meaningful names (Calling arguments arg is not so helpful :) For the mathematical stuff, there are often already established names for the parameters to functions which you can use.)
• logging is ok with print, but I would not introduce additional parameters for it. (The code calling this can't observe the difference between whether the "ab" or "ac" string is passed -- this makes little sense for that caller.)
• the user interaction should be separated from the math, ideally outside the class, so that you can focus on either math or UI, and have a smaller piece of code to look at for each.

## Taking apart these cases

In the typeselection method, you can still simplify many of your cases by thinking about the math.

For example, take this code. In this case, we already know that the angles a, b, c are given.

if self.a and self.b and self.c:
if self.ab and not self.bc and not self.ac:  # Case 1
self.deter()
self.ac=self.dass(self.ab, self.b, self.c, "ac", "ab", "b", "c")
self.bc=self.dass(self.ac, self.a, self.b, "bc", "ac", "a", "b")
elif self.bc and not self.ab and not self.ac:  # Case 2
self.deter()
self.ab=self.dass(self.bc, self.c, self.a, "ab", "bc", "c", "a")
self.ac=self.dass(self.bc, self.b, self.a, "ac", "bc", "b", "a")
elif self.ac and not self.bc and not self.ab:  # Case 3
self.deter()
self.ab=self.dass(self.ac, self.c, self.b, "ab", "ac", "c", "b")
self.bc=self.dass(self.ac, self.a, self.b, "bc", "ac", "a", "b")
elif self.ab and self.bc and not self.ac:  # Case 4
self.deter()
self.ac=self.dass(self.bc, self.b, self.a, "ac", "bc", "b", "a")
elif self.ab and self.ac and not self.bc:  # Case 5
self.deter()
self.bc=self.dass(self.ab, self.a, self.c, "bc", "ab", "a", "c")
elif self.bc and self.ac and not self.ab:  # Case 6
self.deter()
self.ab=self.dass(self.ac, self.c, self.b, "ab", "ac", "c", "b")


In cases 4, 5 and 6, you already have all angles and two lengths of the triangle given. These may conflict already with each other. You may collapse this with one of the other cases above if you want to ignore that, or you check for the conflict. So we are getting a bit tangled up in all the different cases here... :)

### Another approach: Loop until you can't apply any of the rules any more

Look at what you need for each individual calculation:

self.ac=self.dass(self.ab, self.b, self.c, "ac", "ab", "b", "c")


This just needs ab, b and c to work, and assumes that ac is not known. But the code is also guarded by other assumptions right now. You could try to only guard it by the things you really need (ab, b and c are there, ac is not), and try out all the formulas until you can't do any changes any more. :)

while True:
if not self.ac:
if self.ab and self.b and self.c:
self.ac = self.dass(self.ab, self.b, self.c)
continue  # Next loop iteration
elif self.bc and self.b and self.a:
self.ac = self.dass(self.bc, self.b, self.a)
continue  # Next loop iteration
# etc.
# etc.; make sure to get all cases
break  # Break out of loop


Then each way to calculate ac would only need to be mentioned once. You can also take the whole part within if not self.ac and extract it into a single method where the names of the corner points are exchanged. (Replace self.xxx with the parameters of that function.)

## Constraint propagation, if you want to get fancy

There is also a way to calculate this "lazily" without a giant while True loop, but that's much more difficult. I wouldn't recommend it to you now, but I'll still mention it because it's fancy, and you may want to revisit in the future. It takes the "keep track of constraints between things" problem to another level of abstraction. :) http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-22.html#%_sec_3.3.5

This is basically a graph of present or non-present values which are connected to each other through mathematical transformations. For instance, you might have the graph nodes X, Y and Z with the connecting transformation that says "X * Y = Z". Now the transformation is represented as an object and gets notified whenever one of the values is suddenly available. Once there are two values available, the transformation will calculate the third value and set it. For instance, when we know that Y=3 and Z=15, it will calculate and set X to 5 (but it will do division or multiplication, depending on which inputs it gets! :)). Then when it sets X to 5, there may be other transformations in the network that now get triggered because they were waiting for a value for X. Another nice upside to this is that the network can detect when there are conflicting inputs in the system.

A few pointers:

• Use meaningful variable, function and method names. dass(self, arg, arg1, arg2, ans, args, args1, args2): gives me no information about what parameters the method expects or what it does with them, and as you haven't included docstrings I have to read through the code and figure it out. Defining domain-specific terms like AAS/ASA/SSS/etc. and guidance notes (e.g. mentioning that all angle inputs are expected in degrees) in a class/module docstring would be handy, too.

• Use docstrings rather than comments; e.g. t_mover (another not-great name) should have """Moves turtle, and resets it (spins 180 degrees) for next move""" inside the function, rather than #Moves turtle, and resets it (spins 180 degrees) for next move outside it.

• Separate the visualisation more completely from the calculation; this would allow you to import the functionality elsewhere without having to worry about dealing with all of the printing. At the very least, an optional verbose parameter would let the user turn it off.

• Also, separate the user input from the actual business of the class. The snippet below shows one way to approach this.

• Follow the style guide for things like space around = (one space each side, except when defining default parameter values) and other operators. Note that it also recommends testing for None by identity, not truthiness (i.e. if self.a is not None: rather than if self.a:).

• You don't need to pack things into a class unless they share some state. Several of your methods don't actually use any self instance or class attributes, so why are they in the class? Make methods @classmethod or @staticmethod scoped where possible, or just use standalone functions.

• You should also distinguish between the public interface of your class (methods users would call) and the internal implementation (methods only other methods call, usually indicated by a name with a leading underscore _).

If you want to use OOP, I would suggest something like:

class Triangle:

def __init__(self, side_A=None, side_B=None, side_C=None,
angle_a=None, angle_b=None, angle_c=None, verbose=False):
"""Calculate unknowns or reject unsolvable inputs."""
raise NotImplementedError

def __str__(self):
"""User-friendly representation of the class."""
raise NotImplementedError

@classmethod
def from_input(cls, verbose=False):
"""Create a triangle from user-input sides/angles."""
raise NotImplementedError


Your turtle functionality would now take a single Triangle object and draw it, rather than requiring multiple inputs:

def draw_triangle(triangle):
"""Draw the triangle using a turtle."""
raise NotImplementedError