Данная статья написана для тех, кто только начал изучать Python. В ней я пошагово опишу создание простого счетчика слов из txt-файлов, применяя Tkinter. Исходный код написан под Python 2.7, в конце статьи я добавлю несколько комментариев относительно того, как перенести его под 3.6

С чего начинаем?


Никаких незаурядных лестниц в программе не будет, так что рекомендую писать в IDLE; конечно же, без проблем можно писать и в PyCharm'е, и в Эклипсе.

Время создать первую и последнюю функцию, которая, собственно, будет выполнять все подсчёты:

def counter():
    with open(filename) as file:    #открываем через менеджер контекста, filename определим позже
        text = file.read()    #считываем содержимое
    text = text.replace("\n", " ")
    text = text.replace(",", "").replace(".", "").replace("?", "").replace("!", "").replace("—", "")
    text = text.lower()    #убираем верхний регистр
    words = text.split()    #создаем в список, где каждый элемент — слово

Итак, что выполняет эта функция? Всё очевидно: открывает файл, считываем содержимое, убирает всякие символы и создает список из содержимого файла. Не стоит забывать убрать верхний регистр методом lower для корректной дальнейшей проверки на повторение слов. Дальше мы будем обрабатывать этот список. Фактически, считав длину этого списка функцией len, нам уже будет известно количество слов.

Количество уникальных слов


Чтобы посчитать кол-во уникальных слов, создадим ещё один список, в который будет через цикл заносить слова, которых ещё в нём не было:

nonrep_words = list()
    for word in words:
        if word in nonrep_words:    #проверка, "есть ли данный элемент уже в списке?"
            pass    #если есть, то ничего не делаем
        else:
            nonrep_words.append(word)    #если нет, то добавляем

Далее мы просто будет считывать длину списка nonrep_words.

Графическая оболочка


Для начала нам следует подключить соответствующие модули в нашу программу:

import Tkinter, Tkconstants, tkFileDialog
from Tkinter import *

Теперь после определения нашей главной функции напишем код, который будет отображать главное окно, название нашей программки, кнопку «Импортировать файл» и поле вывода:

root = Tk()    #создаем окно

frame = Frame(root)    #используем фрейм для позиционирования
frame.grid()    #используем упаковщик grid

title = Label(frame, text="Word counter")    #название программы
title.grid(row=1, column=1)    #упаковываем grid'ом, позицию элементов можно менять, изменяя параметры row и column

import_btn = Button(frame, text="Import file...", command=counter)    #создаём кнопку
import_btn.grid(row=2, column=1, pady=4)

output = Text(frame, width=45, height=3)    #создаём поле вывода
output.grid(row=4, columnspan=3)   

root.mainloop()    #запускаем цикл обработки событий

Вообще-то можно и не использовать фрейм для кнопки, лейбла и текстового поля, но если вы захотите на досуге добавить в дальнейшем ещё несколько каких-либо функций, то фрейм будет полезен для позиционирования.
При создании кнопки мы забиндили её на функцию counter.

Импорт файла и вывод


В начало определения функции counter добавим строчки:

output.delete("0.0","end")
filename = tkFileDialog.askopenfilename()

Здесь всё просто: чистим поле вывода методом delete, затем открываем файл методом askopenfilename и передаём его имя переменной filename.

Далее в конец функции добавим:

output.insert("end","Amount of words: %d\n" % len(words))
output.insert("end","Amount of nonrepeatable words: %d\n" % len(nonrep_words))

Здесь мы выводим длину двух списков: списка всех слов и списка уникальных слов.

Весь код программы:

import Tkinter, Tkconstants, tkFileDialog
from Tkinter import *

def counter():
    output.delete("0.0","end")
    filename = tkFileDialog.askopenfilename()
    with open(filename) as file:
        text = file.read()
    text = text.replace("\n", " ")
    text = text.replace(",", "").replace(".", "").replace("?", "").replace("!", "").replace("—", "")
    text = text.lower()
    words = text.split()
    nonrep_words = list()
    for word in words:
        if word in nonrep_words:
            pass
        else:
            nonrep_words.append(word)
    output.insert("end","Amount of words: %d\n" % len(words))
    output.insert("end","Amount of nonrepeatable words: %d\n" % len(nonrep_words))

root = Tk()

frame = Frame(root)
frame.grid()

title = Label(frame, text="Word counter")
title.grid(row=1, column=1)

import_btn = Button(frame, text="Import file...", command=counter)
import_btn.grid(row=2, column=1, pady=4)

output = Text(frame, width=45, height=3)
output.grid(row=4, columnspan=3)

root.mainloop()

Для версии 3.6


Для данной версии питона должны быть внесены следующие изменения:

  • При подключении модулей tkinter пишем с маленькой буквы; tkFileDialog убираем и пишем вместо него from tkinter import filedialog
  • Соответственно, при импорте файла tkFileDialog.askopenfilename заменяем на filedialog.askopenfilename
  • Возможно, потребуется дописать encoding='utf-8' при открытии файла через менеджер
    контекста

Комментарии (9)


  1. cool_grass
    21.10.2017 16:21

    А зачем писать велосипед? Замена символов на '' стоку с длинной 0 вам не поможет ибо. Привет.мир = Приветмир.
    Привет, мир = Приветмир.
    Используйте регулярные выражения, для точного поиска слов .


    words = re.findall('\w+', text)
    return list(Set(words))


  1. Shtucer
    21.10.2017 16:30

    Глава "Количество уникальных слов" сделало мне очень грустно. Будто бы нету в питоне такого типа как set. Или даже dict. Нет, мы лучше будем гонять разрастающийся список туда-сюда в цикле внутри цикла.


    1. Nautics889 Автор
      22.10.2017 00:38

      Хм, честно говоря, как раз-таки первоначально и сделал через словарь, либо set, однако потом подумал, пускай через список, раз статья для начинающих. Хотя, безусловно, реализация словарем или множеством делает это гораздо эстетичнее.
      да и к тому же не особо много переписывать придется


  1. Sly_tom_cat
    21.10.2017 20:16

    def counter():
    with open(filename) as file:
    text = file.read()

    Подаем на вход текст размером в пару сотен гигабайт и наблюдаем как наша программа радостно выжирает всю опреативку и подвешивает ОС.

    Если в Linux есть еще надежда на OOM Killer, то винду такое точно убьет.


    1. Sly_tom_cat
      21.10.2017 20:55

      Вот примерно так можно сделать

      from re import findall
      
      def counter(fileName):
        wc = set()
        with open(fileName ,'rt') as f:
          for line from f:
            wc.update(findall(r'\w+', line.lower()))
        return len(wc)
      


      1. Sly_tom_cat
        21.10.2017 21:16

        Ой только не 'for line from f:' а 'for line in f:'


  1. Cobolorum
    21.10.2017 20:59

    sed 's/ \{1,\}/ /g' a.txt | sed -e 's/ /\n/g' | sort | uniq -c | wc -l


  1. DmitryLeonov
    21.10.2017 21:17

    Двоеточий, точек с запятой, кавычек и т.п. в текстах, очевидно. не бывает. А словари (если уж действительно частотный анализ устраивать), множества и \w придумали лентяи.


  1. unanimity
    21.10.2017 21:41

    Изначально у Вас функция counter использовалась для только для подсчёта слов и это было хорошо.

    Чтобы функция counter не зависила от компонента реализующего GUI достаточно было:

    1. задавать имя файла как аргумент функции counter:

    def counter(filename):

    2. возвращать полученные значения в виде кортежа:
    return (len(words), len(nonrep_words))

    Когда Вы добавили в функцию counter часть функционала, связанного с работой GUI — стало хуже. Теперь логика программы привязана к варианту взаимодействия с пользователем.

    В целях самообразования рекомендую Вам:
    1) всё что касается работы с GUI вынести в отдельную функцию
    2) добавить функции для вывода результата в stdout и для вывода в файл
    3) с помощью стандартной библиотеки argparse реализовать различные варианты запуска вашей программы

    Например, при запуске с параметрами:
    --input file_in брать данные из file_in и выводить результат в stdout
    --input file_in --output file_out брать данные из file_in и выводить результат в file_out
    --gui запускать приложение с графическим интерфейсом

    В сочетании с
    if __name__ == '__main__':
        ...

    п. 1) позволит Вам как запускать данный скрипт на исполнение, так и экспортировать из него отдельные функции, например, counter

    п. 3) наглядно покажет преимущество отделения логики от представления.