Это помогает, если ваш базовый класс действительно расширяет Frame
. ;)
class BaseBlock(tk.Frame):
def __init__(self, master, width, height, txt):
tk.Frame.__init__(self, master)
На самом деле вы создаете слишком много слоев и странным образом все управляете. Ниже было бы лучше. Все это наследование на каждом этапе и функции создания слишком запутаны.
import tkinter as tk
from math import *
from dataclasses import asdict, dataclass
from typing import Callable
@dataclass
class Label_dc:
width: int = 20
height: int = 2
anchor: str = 'n'
justify: str = 'center'
text: str = ''
@dataclass
class Button_dc:
width: int = 4
height: int = 2
text: str = ''
command: Callable = None
@dataclass
class Text_dc:
width: int = 20
height: int = 2
state: str = 'normal'
#from classes_calculator import ActionBlock
class FrameBlock(tk.Frame):
def __init__(self, master, row, column, rowspan, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
self.grid(row=row, column=column, rowspan=rowspan)
class ButtonBlock(tk.Button):
def __init__(self, master, row, column, **kwargs):
tk.Button.__init__(self, master, **asdict(Button_dc(**kwargs)))
self.grid(row=row, column=column)
class LabelBlock(tk.Label):
def __init__(self, master, row, column, **kwargs):
tk.Label.__init__(self, master, **asdict(Label_dc(**kwargs)))
self.grid(row=row, column=column)
class TextBlock(tk.Text):
def __init__(self, master, row, column, text='', **kwargs):
tk.Text.__init__(self, master, **asdict(Text_dc(**kwargs)))
self.grid(row=row, column=column)
self.insert('1.end', text)
# Clear text
def clearText(self):
self.delete('1.0', 'end')
# Change text
def changeText(self, new_txt, clear=False):
self.config(state='normal')
if clear:
self.clearText()
self.insert(tk.END, new_txt)
self.config(state='disabled')
# Retrieve input from text box
def retrieveTextInput(self):
return self.get('1.0', 'end')
class App(tk.Tk):
WIDTH = 800
HEIGHT = 600
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# Create GUI labels
LabelBlock(self, 0, 0, text='Calculator')
LabelBlock(self, 1, 0, text='Enter:')
LabelBlock(self, 2, 0, text='Result:')
# Create GUI text
text_blocks = {
'entry' : TextBlock(self, 1, 1),
'result': TextBlock(self, 2, 1, state='disabled', text='0'),
}
#can't use ButtonBlock for this one ~ self.destroy wont pickle properly
tk.Button(self, text='Close', width=6, height=2, command=self.destroy).grid(row=3, column=0)
action = []
# Create frames
frame = FrameBlock(self, 1, 3, 2, width=30, height=30)
# Create GUI buttons
ButtonBlock(frame, 0, 0, text='+', command=lambda: self.store(*text_blocks, 1, action))
ButtonBlock(frame, 0, 1, text='-', command=lambda: self.store(*text_blocks, 2, action))
ButtonBlock(frame, 0, 2, text='*', command=lambda: self.store(*text_blocks, 2, action))
ButtonBlock(frame, 1, 0, text='/', command=lambda: self.store(*text_blocks, 4, action))
ButtonBlock(frame, 1, 1, text='1/x', command=lambda: self.store(*text_blocks, 5, action))
ButtonBlock(frame, 1, 2, text='x^2', command=lambda: self.store(*text_blocks, 6, action))
ButtonBlock(frame, 2, 0, text='=', command=lambda: self.calc(*text_blocks, action))
def store(self, entry, result, action, array):
pass #remove this line
numb = 0.0
try:
numb = float(entry.retrieveTextInput())
except:
print('Please enter a valid number')
return
calc_action = ActionBlock(numb, action)
array.append(calc_action)
entry.clearText()
num = calc_action.returnNumber()
act = calc_action.returnAction()
input_texts = dict([
(1, ' + ' + str(num)),
(2, ' - ' + str(num)),
(3, ' * ' + str(num)),
(4, ' / ' + str(num)),
(5, ' + 1/' + str(num)),
(6, ' + ' + str(num) + '^2')
])
result.changeText(input_texts[act])
# Calculate result
def calc(self, entry, result, array):
pass #remove this line
r = 0.0
for calc in array:
action = calc.returnAction()
num = calc.returnNumber()
if action == 1:
result += num
elif action == 2:
result -= num
elif action == 3:
result *= num
elif action == 4:
result /= num
elif action == 5:
result += 1.0 / num
elif action == 6:
result += num ** 2
entry.clearText()
result.changeText(str(r), True)
if __name__ == '__main__':
app = App()
app.title("Calculator")
app.geometry(f'{App.WIDTH}x{App.HEIGHT}')
app.mainloop()
Стоит изменить то, что у вас есть. Все станет намного чище и проще в управлении. Кроме того, я только что сделал для вас хороший кусок, и ваш способ никогда не сработает. Как только вы используете Frame
как super
для BaseBlock
, все ваши Button
, Label
и Text
сломаются. Извлеченный урок: не говорите, что несколько разных типов виджетов в конечном итоге расширят одно и то же.
Если вы абсолютно застряли на своем пути ~ вы можете сделать это вот так
class FrameBlock(InnerBlock):
def __init__(self, root, width, height, row, column, txt=None):
super().__init__(root, width, height, row, column, txt)
self.frame = tk.Frame(self.root, width=self.width, height=self.height)
self.frame.grid(row=self.g_row, column=self.g_column)
, а затем, когда вы хотите использовать его как master
для Button
, используйте action_frame.frame
в сторону
Ваш метод расчета результатов вообще не работает. Вы даже не учитываете приоритет операторов. Используйте eval()
. Чтобы показать вам, насколько вы далеки ... Вот что нужно, чтобы проанализировать все мыслимые математические выражения, которые поддерживает python. Даже если вы сократите его до того, что поддерживает ваш калькулятор, оно все равно будет больше, чем все ваше текущее приложение.
class Expression:
# Clean
__WHITE: str = '\\s'
__white: Pattern = re.compile(__WHITE)
__COMM: str = '#\\s.*$'
__comm: Pattern = re.compile(__COMM)
# Symbolic
__PARENS: str = '[\\)\\(]'
__parens: Pattern = re.compile(__PARENS)
__INFIX: str = '[%&+-]|[*/]{1,2}|<<|>>|\\||\\^'
__infix: Pattern = re.compile(__INFIX)
__TOKEN: str = 'STK([0-9]+)'
__token: Pattern = re.compile(__TOKEN)
__SYMBOLIC: str = f'{__PARENS}|{__INFIX}'
# Prefix
__INV: str = '([~]+|~u)?'
# Numeric
__HEX: str = '([-]?0x[0-9a-f]+)'
__hex: Pattern = re.compile(__HEX)
__IHEX: str = f'{__INV}{__HEX}'
__ihex: Pattern = re.compile(__IHEX)
__OHEX: str = f'^{__HEX}$'
__ohex: Pattern = re.compile(__OHEX)
__NUM: str = '([-]?[0-9]+(\\.[0-9]+)?)'
__num: Pattern = re.compile(__NUM)
__INUM: str = f'{__INV}{__NUM}'
__inum: Pattern = re.compile(__INUM)
__ONUM: str = f'^{__NUM}$'
__onum: Pattern = re.compile(__ONUM)
__NUMERIC: str = f'{__IHEX}|{__INUM}'
# Variable
__HYPER: str = 'acosh|asinh|atanh|cosh|sinh|tanh'
__TRIG: str = 'acos|asin|atan2|atan|cos|sin|tan|hypot|dist'
__THEORY: str = 'ceil|comb|fabs|factorial|floor|fmod|frexp|gcd|isqrt|ldexp|modf|perm|remainder|trunc'
__LOG: str = 'expm1|exp|log1p|log10|log2|log|pow|sqrt'
__ANGLE: str = 'degrees|radians'
__SPEC: str = 'erfc|erf|lgamma|gamma'
__FN: str = f'{__HYPER}|{__TRIG}|{__THEORY}|{__LOG}|{__ANGLE}|{__SPEC}'
__func: Pattern = re.compile(__FN)
__RAND: str = '(random|rand)'
__rand: Pattern = re.compile(__RAND)
__CONST: str = 'pi|e|tau|inf|' + __RAND
__const: Pattern = re.compile(__CONST)
__BITWISE: str = '<<|>>|\\||\\^|&'
__bitwise: Pattern = re.compile(__BITWISE)
__FN2: str = 'min|max|' + __RAND
__func2: Pattern = re.compile(__FN2)
__VARIABLE: str = f'{__FN}|{__FN2}|{__CONST}'
__SIMPLE: str = f'^({__INUM}+{__INFIX})+{__INUM}$'
__simple: Pattern = re.compile(__SIMPLE)
# Combo
__MATH: str = f'{__VARIABLE}|{__NUMERIC}|{__SYMBOLIC}|,|E|\\s'
__math: Pattern = re.compile(__MATH)
# Priorities
__P1: str = '[*/]{1,2}|%'
__P2: str = '[+-]'
__P3: str = '<<|>>|&'
__P4: str = '\\||\\^'
__priority: List[Pattern] = [re.compile(__P1), re.compile(__P2), re.compile(__P3), re.compile(__P4)]
def __init__(self):
self.value = math.nan
def evaluate(self, expr: str) -> float:
self.value = Expression.eval(expr)
return self.value
@staticmethod
def __hexrepl(m: Match[Union[str, bytes]]):
return str(int(m.group(0), 16))
@staticmethod
def eval(expr: str, fast: bool = False) -> float:
# Remove Whitespace, Comments, Convert Hash To Hex and Case To Lower
expr = Expression.__comm.sub("", expr)
expr = Expression.__white.sub("", expr)
expr = expr.replace('#', '0x').lower()
# Check If This Is Actual Math By Deleting Everything Math Related And Seeing If Anything Is Left
if len(re.sub(Expression.__math, "", expr)) > 0:
return math.nan
if fast:
return Expression.__fast(expr)
# Parse All Inversions Now ... invert(~) is the only "left side only" operator
expr = Expression.__parse_inversions(expr)
expr = Expression.__hex.sub(Expression.__hexrepl, expr)
# Check If This Is Solely A Number ~ If So, Parse Int And Return
if Expression.__onum.match(expr):
n = float(expr)
return int(n) if n % 1 == 0 else n
# We Got This Far. It Must Be Math
n = Expression.__parse(expr)
return int(n) if n % 1 == 0 else n
# Private Static Interfaces
@staticmethod
def __parse_inversions(expr: str) -> str:
match: Iterator[Match[Union[str, bytes]]] = Expression.__ihex.finditer(expr)
m: Match[Union[str, bytes]]
for m in match:
expr = Expression.__invert_expr(expr, m, 16)
match = Expression.__inum.finditer(expr)
for m in match:
expr = Expression.__invert_expr(expr, m, 10)
return expr
@staticmethod
def __invert_expr(expr: str, m: Match[Union[str, bytes]], b: int) -> str:
t1: str = m.group(1)
t2: str = m.group(2)
if t1:
if t1 == '~u':
n: int = Expression.__uinvert_num(int(t2, b))
else:
f: int = len(t1) % 2 == 1
n: int = -(int(t2, b) + 1) if f else int(t2, b)
expr = expr.replace(m.group(0), str(n))
return expr
@staticmethod
def __uinvert_num(num: float) -> int:
if num > 0:
x: int = int(math.log(num, 2.0) + 1)
i: int = 0
for i in range(0, x):
num = (num ^ (1 << i))
return num
@staticmethod
def __parse(expr: str) -> float:
exp_stack: List[str] = []
ops_stack: List[str] = []
res_stack: List[float] = []
tokens = Expression.__tokenize(expr)
# everything that can come before an operator
b1: str = f'{Expression.__HEX}|{Expression.__NUM}|{Expression.__CONST}|\\)'
c: Pattern = re.compile(b1)
# before an operator that is the rest of this expression
b2: str = f'{Expression.__NUM}E'
d: Pattern = re.compile(b2, re.I)
expr = tokens.expression[0::]
while len(expr):
m: Match[Union[str, bytes]] = Expression.__infix.search(expr)
if m:
op: str = m.group()
left: str = expr[0:m.span()[0]]
if re.search(c, left) and not re.search(d, left):
exp_stack.append(left)
ops_stack.append(op)
expr = expr.replace(f'{left}{op}', "")
else:
if len(left) == 0 or re.match(d, left):
right: str = expr[m.span()[1]::]
m = Expression.__infix.search(right)
if m:
left = f'{left}{op}'
op = m.group()
left = f'{left}{right[0:m.span()[0]]}'
exp_stack.append(left)
ops_stack.append(op)
expr = expr.replace(f'{left}{op}', "")
else:
exp_stack.append(expr)
expr = ""
else:
# Probably Not Even Possible In A Valid Math Expression
print("Expression.parse(expr:String): unexpected left side")
print("expression: ", expr)
print("left side: ", left)
print("operator: ", op)
print("exp_stack: ", exp_stack)
print("ops_stack: ", ops_stack)
else:
exp_stack.append(expr)
expr = ""
for r in range(len(exp_stack)):
m: Match[Union[str, bytes]] = Expression.__token.search(exp_stack[r])
inner: str = ""
if m:
i: int = int(m.group(1))
inner = tokens.stack[i]
res_stack.append(Expression.__parsetype(exp_stack[r], inner))
# Iterate Through Stacks Based On Priority and Do Assignments ~ ie... Calculate Everything
if len(ops_stack) > 0:
p: int = 0
for p in range(len(Expression.__priority)):
n: int = 0
while n < len(ops_stack) and len(ops_stack) > 0:
m: Match[Union[str, bytes]] = Expression.__priority[p].match(ops_stack[n])
if m is not None:
if not math.isnan(res_stack[n]) and not math.isnan(res_stack[n + 1]):
res_stack[n] = Expression.__value(res_stack[n], ops_stack[n], res_stack[n + 1])
res_stack.pop(n + 1)
ops_stack.pop(n)
else:
n += 1
return res_stack[0] if len(res_stack) == 1 else math.nan
@staticmethod
def __parsetype(expr: str, val: str = "") -> float:
fin: float = math.nan
if val != "":
tokens: Tokens_t = Expression.__tokenize(val)
csv: List[str] = tokens.expression.split(",")
a: float = 0
b: float = 0
f: str = ""
ln: int = len(csv)
if ln >= 1:
a = Expression.__parse(Expression.__detokenize(csv[0], tokens))
if ln == 2:
b = Expression.__parse(Expression.__detokenize(csv[1], tokens))
m: Match[Union[str, bytes]] = Expression.__func.match(expr)
m2: Match[Union[str, bytes]] = Expression.__func2.match(expr)
if m:
f = m.group()
fin = getattr(math, f)(a, b) if len(csv) == 2 else getattr(math, f)(a)
elif m2:
f = m2.group()
if ln == 2:
if f == 'min':
fin = min(a, b)
elif f == 'max':
fin = max(a, b)
elif ln == 1:
if Expression.__rand.match(f):
fin = random() * a
else:
fin = Expression.__parse(val)
else:
m: Match[Union[str, bytes]] = Expression.__const.match(expr)
c: Match[Union[str, bytes]] = Expression.__hex.match(expr)
if m:
cn: str = m.group()
fin = random() if Expression.__rand.match(cn) else getattr(math, cn)
elif c:
fin = int(c.group(), 16)
else:
fin = float(expr)
return fin
@staticmethod
def __tokenize(expr: str) -> Tokens_t:
c: int = 0
b: int = -1
e: int = -1
ex: str = expr[0::]
s: List[str] = []
m: Match[Union[str, bytes]]
p: Iterator[Match[Union[str, bytes]]] = Expression.__parens.finditer(ex)
for m in p:
if m.group() == "(":
c += 1
if b == -1:
b = m.span()[1]
elif m.group() == ")":
c -= 1
if c == 0 and b > -1:
e = m.span()[0]
if b != e:
s.append(expr[b:e])
ex = ex.replace(expr[b:e], f'STK{len(s) - 1}')
b = -1
return Tokens_t(ex, s) # Tokens_t ~ python equivalent to my c++ math parser
@staticmethod
def __detokenize(part: str, tokens: Tokens_t) -> str:
ex: str = part[0::]
m: Match[Union[str, bytes]]
p: Iterator[Match[Union[str, bytes]]] = Expression.__token.finditer(ex)
for m in p:
ex = ex.replace(m.group(0), tokens.stack[int(m.group(1))])
return ex
@staticmethod
def __fast(expr: str) -> float:
return eval(expr)
__ops: Dict[str, Callable] = {
'+': lambda x, y: x + y,
'-': lambda x, y: x - y,
'*': lambda x, y: x * y,
'/': lambda x, y: x / y,
'**': lambda x, y: x ** y,
'//': lambda x, y: x // y,
'>>': lambda x, y: x >> y,
'<<': lambda x, y: x << y,
'&': lambda x, y: x & y,
'|': lambda x, y: x | y,
'^': lambda x, y: x ^ y,
'%': lambda x, y: x % y,
}
@staticmethod
def __value(v1: float, oper: str, v2: float) -> float:
x: float = 0
try:
m: Match[Union[str, bytes]] = Expression.__bitwise.match(oper)
x = Expression.__ops[oper](v1, v2) if not m else Expression.__ops[oper](int(v1), int(v2))
except KeyError:
x = math.nan
return x