Исходный программный код PyCalc

ishodnyj programmnyj kod pycalc Текст и язык

Пример 19.20 содержит модуль с исходным программным кодом PyCalc, в котором эти идеи применены на практике в контексте графического интерфейса. Эта реализация состоит из одного файла (не считая импортированные и повторно использованные вспомогательные средства). Внимательно изучите этот программный код. Как обычно, ничто не может заменить попытки самостоятельной работы с программой, чтобы полнее понять ее функциональные возможности.

Обращайте особое внимание на комментарии, начинающиеся со слов «что сделать», где приводятся предложения по дальнейшему усовершенствованию. Подобно всем программным системам этот калькулятор может продолжать развиваться с течением времени (и фактически так и происходит с выходом каждого нового издания этой книги). Поскольку он написан на языке Python, подобные улучшения легко можно реализовать в будущем.

Пример 19.20. PP4E\Lang\Calculator\calculator.py

#!/usr/local/bin/python

############################################################################ PyCalc 3.0+: программа калькулятора и компонент графического интерфейса на Python/tkinter.

Вычисляет выражения по мере ввода, перехватывает нажатия клавиш на клавиатуре для ввода выражений; в версии 2.0 были добавлены диалоги для ввода произвольного программного кода, отображение истории вычислений, настройка шрифтов и цветов, вывод справочной информации о программе, предварительный импорт констант из модулей math/random и многое другое;

3.0+ (PP4E, номер версии сохранен):

  адаптирована для работы под управлением Python 3.X (только)

  убрана обработка клавиши L(тип long теперь отсутствует в языке)

3.0, изменения (PP3E):

  теперь для поля ввода вместо состояния disabledиспользуется состояние readonly‘, иначе оно окрашивается в серый цвет (исправлено в соответствии с изменениями в версии 2.3 библиотеки Tkinter);

  исключено расширенное отображение точности для чисел с плавающей точкой за счет использования str(), вместо x‘/repr() (исправлено в соответствии с изменениями в Python);

  настраивается шрифт в поле ввода, чтобы текст в нем выглядел крупнее;

  используется флаг justify=right для поля ввода, чтобы обеспечить выравнивание по правому краю, а не по левому;

  добавлены кнопки E+’ и E-‘ (и обработка клавиши E‘) для ввода чисел

в экспоненциальной форме; вслед за нажатием клавиши Eвообще должен следовать ввод цифр, а не знака + или -;

  убрана кнопка L(но нажатие клавиши Lвсе еще обрабатывается): теперь излишне, потому что Python автоматически преобразует числа, если они оказываются слишком большими (в прошлом кнопка Lвыполняла эту операцию принудительно);

  повсюду используются шрифты меньшего размера;

  автоматическая прокрутка в конец окна с историей вычислений

что сделать: добавить режим включения запятых (смотрите str.format и пример

в "Изучаем Python"); добавить поддержку оператора ‘**’; разрешить ввод ‘+’ и Jдля комплексных чисел; использовать новый тип Decimal для вещественных чисел с фиксированной точностью; сейчас для ввода и обработки комплексных чисел можно использовать диалог cmd‘, но такая возможность отсутствует в главном окне; предупреждение: точность представления чисел и некоторые особенности поведения PyCalc в настоящее время обусловлены особенностями работы функции str();

############################################################################

from tkinter import * # виджеты, константы

from PP4E.Gui.Tools.guimixin import GuiMixin # метод quit

from PP4E.Gui.Tools.widgets import label, entry, button, frame # конструкторы # виджетов

Fg, Bg, Font = ‘black’, ‘skyblue’, (‘courier’, 14, ‘bold’) # настр. по умолч.

debugme = True def trace(*args):

if debugme: print(args)

############################################################################

   Основной класс — работает с интерфейсом пользователя; расширенный Frame

   в новом Toplevel или встроенный в другой элемент-контейнер

############################################################################

class CalcGui(GuiMixin, Frame):

Operators = "+-*/=" # списки кнопок

Operands = ["abcd", "0123", "4567", "89()"] # настраиваемые def __init__(self, parent=None, fg=Fg, bg=Bg, font=Font):

Frame.__init__(self, parent)

self.pack(expand=YES, fill=BOTH) # все элементы растягиваются self.eval = Evaluator() # встроить обработчик стека

self.text = StringVar() # создать связанную перемен.

self.text.set("0")

self.erase = 1 # затем убрать текст "0"

self.makeWidgets(fg, bg, font) # построить граф. интерфейс

if not parent or not isinstance(parent, Frame):

self.master.title(‘PyCalc 3.0’) # заголов., если владеет окном

self.master.iconname("PyCalc") # то же для привязки клавиш self.master.bind(‘<KeyPress>’, self.onKeyboard)

self.entry.config(state=’readonly’) # 3.0: не ‘disabled’=серый else:

self.entry.config(state=’normal’)

self.entry.focus()

def makeWidgets(self, fg, bg, font): # 7 фреймов плюс поле ввода

self.entry = entry(self, TOP, self.text) # шрифт, цвет настраиваемые self.entry.config(font=font) # 3.0: make display larger

self.entry.config(justify=RIGHT) # 3.0: справа, не слева

for row in self.Operands:

frm = frame(self, TOP) for char in row:

button(frm, LEFT, char,

lambda op=char: self.onOperand(op), fg=fg, bg=bg, font=font)

frm = frame(self, TOP)

for char in self.Operators:

button(frm, LEFT, char,

lambda op=char: self.onOperator(op), fg=bg, bg=fg, font=font)

frm = frame(self, TOP)

button(frm, LEFT, ‘dot ‘, lambda: self.onOperand(‘.’))

button(frm, LEFT, E+ ‘, lambda: self.text.set(self.text.get()+’E+’))

button(frm, LEFT, E- ‘, lambda: self.text.set(self.text.get()+’E-‘)) button(frm, LEFT, ‘cmd ‘, self.onMakeCmdline) button(frm, LEFT, ‘help’, self.help)

button(frm, LEFT, ‘quit’, self.quit) # из guimixin

frm = frame(self, BOTTOM)

button(frm, LEFT, ‘eval ‘, self.onEval)

button(frm, LEFT, ‘hist ‘, self.onHist)

button(frm, LEFT, ‘clear’, self.onClear)

def onClear(self):

self.eval.clear()

self.text.set(‘0’)

self.erase = 1

def onEval(self):

self.eval.shiftOpnd(self.text.get()) # посл. или единств. операнд

self.eval.closeall() # применить все оставш. операторы

self.text.set(self.eval.popOpnd()) # вытолкнуть: след. оператор? self.erase = 1

def onOperand(self, char):

if char == ‘(‘:

self.eval.open() self.text.set(‘(‘) # очистить текст далее

self.erase = 1

elif char == ‘)’:

self.eval.shiftOpnd(self.text.get()) # послед. или единств.

# вложенный операнд

self.eval.close() # вытолкнуть: след. оператор?

self.text.set(self.eval.popOpnd())

self.erase = 1

else:

if self.erase:

self.text.set(char) # очистить последнее значение

else:

self.text.set(self.text.get() + char) # иначе добавить

# в операнд

self.erase = 0

подпись: втолкнуть лев. операнд вычислить выраж. слева?
втолкнуть оператор, показать операнд|результат
стереть при вводе след.
операнда или '('
новое окно верхнего уровня произвольный код python расширяется только entry
подпись: ent))def onOperator(self, char):

self.eval.shiftOpnd(self.text.get()) #

self.eval.shiftOptr(char) #

self.text.set(self.eval.topOpnd()) #

#

self.erase = 1 #

# def onMakeCmdline(self):

new = Toplevel() #

new.title(‘PyCalc command line’) #

frm = frame(new, TOP) #

label(frm, LEFT, ‘>>>’).pack(expand=NO)

var = StringVar()

ent = entry(frm, LEFT, var, width=40) onButton = (lambda: self.onCmdline(var, onReturn = (lambda event: self.onCmdline(var, ent)) button(frm, RIGHT, ‘Run’, onButton).pack(expand=NO) ent.bind(‘<Return>’, onReturn)

var.set(self.text.get())

def onCmdline(self, var, ent): # выполняет команду в окне

try:

value = self.eval.runstring(var.get())

var.set(‘OKAY‘) # выполняет в eval

if value != None: # с пространством имен в словаре

self.text.set(value) # выражение или инструкция

self.erase = 1

подпись: # результат - в поле ввода
# состояние в поле ввода окна
# позиция вставки после текста
# выделить сообщ., чтобы след.
# нажатие клавиши удалило его
# обраб. событий клавиатуры
# как если бы нажата клавиша
var.set(‘OKAY => ‘+ value) except:

var.set(‘ERROR’)

ent.icursor(END)

ent.select_range(0, END)

def onKeyboard(self, event):

pressed = event.char

if pressed != »:

if pressed in self.Operators:

self.onOperator(pressed)

else:

for row in self.Operands:

if pressed in row:

self.onOperand(pressed)

break

else: # 4E: убрана клавиша ‘Ll’

if pressed == ‘.’:

self.onOperand(pressed) # может быть

# началом операнда

if pressed in ‘Ee’: # 2e10, без +/-

self.text.set(self.text.get()+pressed) # нет: не удал.

elif pressed == ‘\r’: self.onEval() # Enter=eval

elif pressed == ‘ ‘:

self.onClear() # пробел=очистить

elif pressed == ‘\b’:

self.text.set(self.text.get()[:-1]) # забой elif pressed == ‘?’:

self.help()

def onHist(self):

# выводит окно с историей вычислений

from tkinter.scrolledtext import ScrolledText # или PP4E.Gui.Tour new = Toplevel() #создать новое окно

ok = Button(new, text="OK", command=new.destroy)

ok.pack(pady=1, side=BOTTOM) # добавл. первымусекается посл. text = ScrolledText(new, bg=’beige’) # добавить Text + полосу прокрут. text.insert(‘0.0’, self.eval.getHist()) # получить текст Evaluator text.see(END) # 3.0: прокрутить в конец

text.pack(expand=YES, fill=BOTH)

# новое окно закрывается нажатием кнопки ok или клавиши Enter new.title("PyCalc History")

new.bind("<Return>", (lambda event: new.destroy()))

ok.focus_set() # сделать новое окно модальным:

new.grab_set() # получить фокус ввода, захватить приложение

new.wait_window() # не вернется до вызова new.destroy

def help(self):

self.infobox(‘PyCalc’, ‘PyCalc 3.0+\n’

‘A Python/tkinter calculator\n’

‘Programming Python 4E\n’

‘May, 2010\n’

‘(3.0 2005, 2.0 1999, 1.0 1996)\n\n’

‘Use mouse or keyboard to\n’ ‘input numbers and operators,\n’ ‘or type code in cmd popup’)

############################################################################ # класс вычисления выражений встраивается в экземпляр CalcGui

# и используется им для вычисления выражений

############################################################################

class Evaluator:

def __init__(self):

self.names = {} # простр. имен для переменных

self.opnd, self.optr = [], [] # два пустых стека

self.hist = [] # журнал предыдущ. вычислений

self.runstring("from math import *") # предварит. импорт модулей

self.runstring("from random import *") # в простр. имен калькулятора

def clear(self):

self.opnd, self.optr = [], [] # оставить имена нетронутыми

if len(self.hist) > 64: # ограничить размер истории

self.hist = [‘clear’]

else:

подпись: def popopnd(self):
value = self.opnd[-1] self.opnd[-1:] = [] return value
self.hist.append(‘clear—‘)

# вытолк./вернуть верх.|послед. операнд

#   для отображ. и использования

#   или x.pop(), или del x[-1]

def topOpnd(self)

#   подпись: return self.opnd[-1]верхн. операнд (конец списка)

def open(self)

#   подпись: self.optr.append('(')трактовать ‘(‘ как оператор

подпись: по ')' вытолкнуть до первой '(' ok если пусто: остается пустым вытолкнуть, иначе снова будет добавлен оператором
остальное по 'eval'
послед. может быть именем перем
def close(self): #

self.shiftOptr(‘)’) #

self.optr[-2:] = [] #

#

def closeall(self):

while self.optr: #

self.reduce() #

try:

self.opnd[0] = self.runstring(self.opnd[0])

except:

self.opnd[0] = ‘*ERROR*’ # вытолкнуть, иначе снова добавится

afterMe = {‘*’: [‘+’, ‘-‘, ‘(‘, ‘=’], # член класса

‘/’: [‘+’, ‘-‘, ‘(‘, ‘=’], # не выталкивать операторы для клав.

‘+’: [‘(‘, ‘=’], # если это предыдущ. оператор: push

‘-‘: [‘(‘, ‘=’], # иначе: pop/eval предыдущ. оператор

‘)’: [‘(‘, ‘=’], # все левоассоциативные ‘=’: [‘(‘] }

def shiftOpnd(self, newopnd): # втолкнуть операнд для оператора,

self.opnd.append(newopnd) # ‘)’, eval

def shiftOptr(self, newoptr): # применить операторы с приорит. <=

while (self.optr and

self.optr[-1] not in self.afterMe[newoptr]):

self.reduce()

self.optr.append(newoptr) # втолкнуть этот оператор над результатом

# операторы предполаг. стирание след. операндом def reduce(self):

trace(self.optr, self.opnd)

try: # свернуть верхнее выражение

operator = self.optr[-1] # вытолк. верх. оператор (в конце)

[left, right] = self.opnd[-2:] # вытолк. 2 верх.

# операнда (в конце)

self.optr[-1:] = [] # удалить срез на месте

self.opnd[-2:] = []

result = self.runstring(left + operator + right) if result == None:

result = left # присваивание? клавиша имени перем.

self.opnd.append(result) # втолкнуть строку результ. обратно except:

self.opnd.append(‘*ERROR*’) # ошибка стека/числа/имени

def runstring(self, code):

try: # 3.0: not ‘x’/repr

result = str(eval(code, self.names, self.names)) # вычислить

self.hist.append(code + ‘ => ‘ + result) # добавить в журнал except:

exec(code, self.names, self.names) # инструкция: None

self.hist.append(code) result = None

return result

def getHist(self):

return ‘\n’.join(self.hist)

def getCalcArgs():

from sys import argv # получить арг. команд. строки в словаре

config = {} # пример: -bg black -fg red

for arg in argv[1:]: # шрифт пока не поддерживается

if arg in [‘-bg’, ‘-fg’]: # -bg red’ -> {‘bg’:’red’}

try:

config[arg[1:]] = argv[argv.index(arg) + 1]

except:

pass

return config

if __name__ == ‘__main__’:

CalcGui(**getCalcArgs()).mainloop() # по умолчанию окно верхнего уровня

Использованная литература:

Марк Лутц — Программирование на Python, 4-е издание, II том, 2011

Каталог сайтов Всего.ру
Оцените статью
Секреты программирования
Добавить комментарий