Как выглядит сложный код на питоне
Перейти к содержимому

Как выглядит сложный код на питоне

  • автор:

Идеальный код на Python

Главная ошибка новичка: он пишет код и не думает о том, что его будет кто-то читать. Более того, он не берет в расчет, что сам будет вникать в эти строки спустя день, неделю или год. Новичок решает другую задачу: пишет код, который будет работать.

Проблема в том, что если работодатель видит код, но не может сразу его прочитать, остальное уже не важно. Сотрудник с таким стилем кодинга будет создавать проблемы для коллег — значит, компании он не нужен.

У нас уже вышла публикация о том, как написать код для собеседования и получить работу. Мы пошли дальше и задали вопросы об идеальном коде декану Python-факультета GeekUniversity Алексею Петренко. Он рассказал, что в коде выдает джуниора.

Отступы: все разные

В современных языках программирования существует правило: для выделения самостоятельных смысловых логических блоков используют отступы от начала строки. Python ушел дальше всех: отступ является непосредственным атрибутом этого языка программирования, это часть синтаксиса.

Что новички делают не так

  • Неправильно выставляют отступы — их код просто не работает.
  • Используют неодинаковое количество отступов: в одной функции — два пробела, в другой — все шесть.
  • Используют разные клавиши, чтобы создать отступ: в одной функции — клавишу табуляции, в другой — два пробела.

Из-за разных отступов код смотрится некрасиво, а Python ругает за неудачное сочетание пробелов и табуляций.

Решение

Хороший тон — делать отступ четырьмя нажатиями пробела.

Современные среды разработки — например, PyCharm — упрощают жизнь программиста. Нажав клавишу табуляции, автоматически получаете четыре пробела. Вуаля: код оформлен в общепризнанном стиле.

Для языка Python разработан официальный документ PEP-8, в котором прописаны правила красивого кода. Это соглашение всех программистов мира, которые пишут на Python, о том, какой код считать образцовым.

Переменные: непонятные

Рабочий код должен быть читаемым и ясным без личного присутствия автора. Потрудитесь дать понятные имена всем переменным, методам, функциям, которые используете в коде.

Что новички делают не так

  • Используют в качестве переменных обычные буквы алфавита: a, b, c, d, — не задумываясь о смысловой нагрузке.
  • Называют все переменные любимой буквой, добавляя цифры: а1, а2, а3. Если переменных двадцать, то будет и а20. Понять, какую информацию они хранят, очень сложно.
  • Используют строчную переменную l. Разработчики стараются избегать эту букву. Ее не принято использовать не только в Python, но и в других языках программирования, потому что при быстром чтении кода строчную l легко спутать с единицей. Аналогичная путаница возникает с O и 0.
  • Записывают переменные транслитом (massiv вместо array).
  • Придумывают новые переменные для общепринятых: для названия цикла (в цикле используют переменные i, j, k — именно в таком порядке, от внешнего i до вложенного k), для обозначения осей координат (x, y).

Решение

Очередное правило хорошего кода: каждая переменная должна говорить о том, что она хранит. Для записи информации о человеке логично создать переменную name. Для возраста — age. Заглядывайте в онлайн-словари, чтобы называть переменные правильными английскими словами, понятными большинству программистов в мире. Идеальная переменная понятна и легко читается:

  • Если название переменной состоит из нескольких слов, используйте в Python нижнее подчеркивание: переменная со значением максимального размера — max_size.
  • Если именуете класс, записывайте каждое слово с заглавной буквы: имя_переменной должно отличаться от ИмениКласса.

Особенность языка Python в том, что он интерпретируемый. Программа не отслеживает, какие имена дает программист переменным, и примет любое имя, даже название функции.

Из этого вытекает еще одна ошибка новичков: они называют переменные так же, как функции.

В Python есть около 70 функций «из коробки»: функция sum() вычисляет сумму всех переданных аргументов, max() ищет максимальное значение. Если программист назовет переменную sum, она будет работать. Но такой переменной он переопределит функцию суммирования — и она сломается.

Пустые строки: отсутствуют или в избытке

Как правило, код джуниора идет сплошным текстом: в каждой строке — очередная строка кода.

Что новички делают не так

  • Не разделяют строки кода пустыми строками.
  • Отделяют каждую строчку кода пустой строкой.

В обоих случаях код очень сложно прочитать.

Решение

Код принято делить на логические блоки.

К примеру, если запрашивают информацию о пользователе, пишут несколько строк кода и оставляют одну пустую после блока.

Далее идет блок обработки информации. Если он содержит несколько подблоков, их тоже следует отделить пустой строкой.

Перед выводом информации в конце также добавляют пустую строку.

Слишком сжатый код

Что новички делают не так

  • Пытаются написать код максимально сжато и для этого помещают несколько составных операций в одну строку.

Такой код сложно отлаживать в больших проектах: его трудно читать, приходится разбираться в том, как он работает.

Решение

Если есть возможность, распишите несколько составных действий в отдельные строки. Лучше потратить две минуты сейчас и сэкономить двадцать минут на чтение этого кода в будущем.

Советы декана начинающим разработчикам

  1. Ваш код будут читать другие программисты. Всегда думайте о том, как они его воспримут, насколько он будет понятен коллегам.
  2. Потратьте время на изучение синтаксиса, почитайте стандарты PEP8. Есть англоязычная версия и перевод. Работодатель будет встречать «по одежке»: ваш код должен выглядеть красиво.
  3. Если Python — не первый для вас язык программирования, повторяйте требования стиля. Когда разработчик пишет, не соблюдая их, возникает путаница: то ли перед нами человек, у которого в голове перемешались все языки программирования, то ли новичок, который вообще ничего не знает о Python.
  4. Воспользуйтесь командой import this. Это пасхалка от разработчиков: она выводит свод правил хорошего кода. Почаще перечитывайте этот Дзен Python — и ваш код будет идеален.

Придерживаясь этих советов, вы быстро научитесь писать код, который устроит вас, коллег и работодателя.

Примеры программ

Python 3 логотип

Решение задачи C4 из диагностической работы ЕГЭ 2015

Для большинства учеников самой сложной и нерешаемой задачей в ЕГЭ по информатике является задача C4.

Поэтому я разберу одну из них, и покажу, как такие задачи можно решать на языке Python.

Интерпретатор hq9+

Сегодня мы напишем интерпретатор ещё одного эзотерического языка программирования, под названием hq9+.

Задача про словарь

Сегодня я разберу одну из олимпиадных задач, довольно простую.

Суть задачи в том, чтобы из англо-латинского словаря сделать латино-английский.

Пишем блэкджек

Ну, точнее, не блэкджек, а его мини-вариант под названием очко.

Интерпретатор brainfuck

Сегодня я решил что-нибудь написать на python. Что-нибудь простенькое, но не очень. Решил остановиться на интерпретаторе brainfuck.

  • Книги о Python
  • GUI (графический интерфейс пользователя)
  • Курсы Python
  • Модули
  • Новости мира Python
  • NumPy
  • Обработка данных
  • Основы программирования
  • Примеры программ
  • Типы данных в Python
  • Видео
  • Python для Web
  • Работа для Python-программистов
  • Сделай свой вклад в развитие сайта!
  • Самоучитель Python
  • Карта сайта
  • Отзывы на книги по Python
  • Реклама на сайте

Примеры программ на языке Python

Данный пример чисто демонстрационный, так как его можно значительно улучшить.

from urllib import urlopen # из модуля urllib импортируем функцию urlopen u = urlopen("http://python.org") # открываем URL на чтение words = <> # связываем имя words с пустым словарём # (словарь — неупорядоченный [[ассоциативный массив]]) for line in u: # читаем u по строкам line = line.strip(" \n") # отбрасываем начальные и конечные пробелы for word in line.split(" "): # режем каждую строку на слова, ограниченные пробелами try: # блок обработки исключений words[word] += 1 # пытаемся увеличить words[word] на единицу except KeyError: # если не получилось (раньше words[word] не было) words[word] = 1 # присваиваем единицу # теперь словарь words содержит частоту встречаемости каждого слова. # Например, words может содержать pairs = words.items() # делаем из словаря список пар # pairs == [("яблоко",5), ("апельсин",12), ("груша",8)] pairs.sort(key=lambda x: x[1], reverse=True) # сортируем по убыванию второго элемента пары for p in pairs[:10]: # печатаем первые 10 элементов списка print(p[0], p[1]) 

Текст для версии 3.7.1

from urllib.request import urlopen # из модуля urllib импортируем функцию urlopen u = urlopen("http://python.org") # открываем URL на чтение words = <> # связываем имя words с пустым словарём # (словарь — неупорядоченный [[ассоциативный массив]]) for line in u: # читаем u по строкам line =line.decode("utf-8") # преобразуем байт-строку в строку line = line.strip(" \n") # отбрасываем начальные и конечные пробелы for word in line.split(" "): # режем каждую строку на слова, ограниченные пробелами try: # блок обработки исключений words[word] += 1 # пытаемся увеличить words[word] на единицу except KeyError: # если не получилось (раньше words[word] не было) words[word] = 1 # присваиваем единицу # теперь словарь words содержит частоту встречаемости каждого слова. # Например, words может содержать pairs = words.items() # делаем из словаря список пар # pairs == [("яблоко",5), ("апельсин",12), ("груша",8)] A= sorted (pairs, key=lambda x: x[1], reverse=True) # сортируем по убыванию второго элемента пары for p in A[:10]: # печатаем первые 10 элементов списка print(p[0], p[1]) 

Примеры работы с последовательностями править

Иллюстрируют особенности индексации элементов и срезов: при взятии среза нумеруются не сами элементы, а промежутки между ними.

>>> l = ['A', 'B', 'C', 'D', 'E'] # исходный список >>> # 0 1 2 3 4 5 # пронумерованные промежутки между элементами >>> # -5 -4 -3 -2 -1 # нумерация с конца >>> l[0:2] # срез от нулевого до второго промежутка ['A', 'B'] >>> l[1:-2] # срез от второго до второго с конца элемента ['B','C'] >>> l[1::2] # каждый второй элемент начиная с первого ['B', 'D'] >>> l[::-1] # все элементы в обратном порядке ['E', 'D', 'C', 'B', 'A'] 

Функции подобные range() поддерживают то же правило (для версий языка 2.x):

>>> range(2, 5) [2, 3, 4] >>> range(5) [0, 1, 2, 3, 4] 
>>> [0]*10 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] >>> [ i for i in range(5) if i%2==0] [0, 2, 4] 

import sys # для получения объектов из вышележащих фрэймов стека

class CannotResolve(Exception): # класс исключения для случая ненахождения функции

pass

class Resolver(object): # класс, реализующий разрешение на этапе исполнения

emess = "Can't found appropriate signature of func %s() for call with" + \ " params %r" # сообщение об ошибке def __init__(self,name): # конструктор self.function_map = <> # словарь, отображающий типы параметров на функции self.default = None # функция по умолчанию self.name = name # имя функции для вывода сообщений об ошибках def __call__(self,*dt): # имитируем функцию, принимающую любое количество # позиционных параметров cls = tuple(map(type,dt)) # создаем tuple из типов переданных аргументов # функция type возвращает тип своего параметра # map вызовет type для каждого элемента из dt try: x = self.function_map[cls] # пытаемся получить функцию из словаря except KeyError: # если подходящей нет, if self.default is not None: # используем функцию по умолчанию x = self.default else: # если её нет - возбуждаем исключение raise CannotResolve(self.emess % (self.name,cls)) return x(*dt) # вызываем функцию и возвращаем результат

def overload(*dt): # декоратор для перегрузки в качестве параметров

# принимает типы параметров def closure(func): name = func.__name__ # получаем имя функции fr = sys._getframe(1).f_locals.get(name,Resolver(name)) # опускаемся на один шаг вниз по стеку и находим # локальную переменную с именем функции # если же ее нет, то используем новый # Resolver-объект fr.function_map[dt] = func # добавляем новую функцию к словарю # разрешения вызовов return fr return closure

def overdef(func): # для создания функции по умолчанию

name = func.__name__ # аналогично как и в функции overload fr = sys._getframe(1).f_locals.get(name,Resolver(name)) fr.default = func return fr
  1. теперь воспользуемся полученными декораторами

@overdef # это будет функция по умолчанию def f(*dt,**mp):

print ("Default call") # если нет явного return, то вернется None

@overload(int) # единственный параметр — целое def f(x):

return x + 1

@overload(str) # единственный параметр — строка def f(x):

return x + "1"

@overload(str,int) # строка и целое def f(x,y):

return x + str(y)

print (f»1″) # напечатает : 2 print (f»1″) # напечатает : 11 f(2,2) # напечатает : Default call

Управление контекстом выполнения править

Следующий пример из PEP343 иллюстрирует применение оператора with для защиты блока кода от одновременного выполнения двумя потоками:

from __future__ import with_statement # задействует оператор with в коде from contextlib import contextmanager from threading import Lock # Описание менеджера контекста @contextmanager def locked(lock): lock.acquire() try: yield finally: lock.release() # Определение блокировки myLock = Lock() # Применение оператора with locked(myLock): # print "Охраняемый блок кода. Блокировка будет освобождена при любом выходе из этого блока." # 

Генератор чисел Фибоначчи править

Пример генератора чисел Фибоначчи и его использования:

def fibonacci(max): # генератор (а не функция, т.к. оператор return заменён на yield) a, b = 0, 1 while a  max: yield a # return a, + запоминаем место рестарта для следующего вызова a, b = b, a + b # параллельное присваивание, которое выполняется одновременно и параллельно for n in fibonacci(100): # используем генератор fibonacci() как итератор print(n) # печатаем все числа Фибоначчи меньшие 100 через пробел 

Альтернативный синтаксис доступа к элементам словаря править

Можно определить словарь, который в дополнение к обычному синтаксису доступа к значению по ключу d[key] может предоставлять синтаксически более наглядный доступ к атрибуту d.key в случае алфавитно-цифровых ключей:

class Entity(dict): # наследуем класс от __builtin__.dict def __getattr__(self, key): # этот метод будет вызван, если атрибут # с именем key не будет найден у экземпляра класса try: return self[key] # пытаемся вернуть элемент словаря except KeyError, k: # если такого элемента нет, то возбуждаем raise AttributeError, k # исключение AttributeError # по договоренности __getattr__ # не должно возбуждать других исключений def __setattr__(self, key, value): # этот метод будет вызван при присвоении self[key] = value # атрибуту key значения value def __delattr__(self, key): # а этот при удалении атрибута try: # с помощью del mydict.g del self[key] except KeyError, k: raise AttributeError, k def __repr__(self): # используется функцией repr return self.__class__.__name__ + "(" + dict.__repr__(self) + ")" d = Entity(a=1) d.b_100 = 100 assert d.a == d['a'] and d.b_100 == d['b_100'] 

Функтор с генерацией байтокода править

Пример эффективной реализации функтора, основанный на генерации байтокода во время исполнения. Этот пример демонстрирует следующие возможности/особенности Python:

  • Возможность реализации специфических средств функционального программирования наработками, уже имеющимися в языке
  • Работать с байтокодом в Python достаточно просто
  • Зачастую генерация байтокода способна значительно ускорить исполнение.

Это только пример, он реализует всего одну операцию — сложение и имеет несколько других ограничений.

#------------------------------------------------------------------------------- import byteplay # специальный модуль для удобной работы с Python-байтокодом import new # для создания функции во время исполнения import functools # для update_wrapper import inspect # для получения информации о параметрах, принимаемых функцией #------------------------------------------------------------------------------- class FastFunctor(object): def __init__(self,func,code = None): self.func = None # здесь будем хранить результирующую функцию self.ofunc = func # а здесь исходную(original) функцию if code is None: # конструируем байтокод для вызова функции self.code = [(byteplay.LOAD_CONST,func)] rparams = inspect.getargspec(func)[0] # получаем список параметров, принимаемых функцией self.code.extend((byteplay.LOAD_FAST,i) for i in rparams) self.code.append((byteplay.CALL_FUNCTION,len(rparams))) else: # если же функтор создан из другого функтора, # то только копируем переданный байтокод self.code = code # создаем новый объект кода self.ocode = bp.Code.from_code(func.func_code) def __add__(self,obj): # этот метод вызывается для операции '+' code = self.code[:] # копируем байтокод if isinstance(obj,FastFunctor): # если прибавляемый объект - функтор # просто дописываем его код к нашему # после своего исполнения он "оставит" в вершине стека результат code.extend(obj.code) else: # иначе загружаем объект в стек code.append((byteplay.LOAD_CONST,obj)) # дописываем байтокод, складывающий два верхних элемента в стеке code.append((byteplay.BINARY_ADD ,None )) # создаем новый функтор, с байтокодом получения суммы return self.__class__(self.ofunc,code = code) def __call__(self,*dt,**mp): # этот метод будет вызван для операции вызова object() return self.fast()(*dt,**mp) # конструируем и вызываем функцию def fast(self): # конструируем функцию из байтокода if self.func is None: # если функция не была создана раннее code = self.code + [(bp.RETURN_VALUE,None)] # добавляем байтокод возврата oc = self.ocode # создаем объект кода из байтокода и другой информации bin_code = byteplay.Code(code, oc.freevars, oc.args, oc.varargs, oc.varkwargs, oc.newlocals, "%s>" % id(self), "%s>" % id(self), 0, "auto_generated code") # конструируем новую функцию из объекта кода self.func = new.function(bin_code.to_code(),globals()) # после этой операции для всех средств интроспекции # созданная функция будет выглядеть как оригинальная self.func = functools.update_wrapper(self.func,self.ofunc) return self.func 
# Ниже представлено тестирование скорости объектов FastFunctor и SlowFunctor # (статья "Функциональное программирование на Python", см. сноску после блока кода) # из IPython (для удобства чтения лог немного изменен) # строки, начинающиеся с "In [XX]:" вводятся, остальные — вывод интерпретатора In [1]: import fastfunctor In [2]: func = lambda x : x + 1 # Создаем очень простую функцию In [3]: vl = 1 # Переменная, для предотвращения оптимизации In [4]: functor = fastfunctor.Functor(func) In [5]: %timeit (functor + functor + 1)(vl) # Тестируем "лобовой" способ 1000 loops, best of 3: 661 mks per loop # Очень медленно In [6]: functor2 = (functor + functor + 1) # Конструируем функтор один раз In [7]: %timeit functor2(vl) # и тестируем только непосредственно вызов 100000 loops, best of 3: 4.52 mks per loop # Значительно лучше In [8]: functor3 = (functor + functor + 1).fast() # Получаем результирующую функцию In [9]: %timeit functor3(vl) 1000000 loops, best of 3: 1.42 mks per loop In [10]: def of(vl): return x(vl) + x(vl) + 1 # Создаем функцию "вручную" In [11]: %timeit of(vl) 1000000 loops, best of 3: 1.42 mks per loop # Скорость полностью совпадает со # скоростью функтора In [12]: sfunctor = SlowFunctor(func) # Простая реализация функтора In [13]: sfunctor = sfunctor + sfunctor + 1 # In [14]: %timeit sfunctor(vl) # 100000 loops, best of 3: 12.6 mks per loop # Примерно в 9 раз медленнее, чем статический # вариант

Код SlowFunctor можно посмотреть здесь.
Приведенные значения времени следует рассматривать только в сравнении друг с другом.
ipython — расширение интерпретатора Python для интерактивной работы.

Используя эту технику, можно создать полноценный функтор, добавив функции для других операций ( __sub__, __div__ и другие) и расширив его на случай нескольких входных функций с разными аргументами.

Транспонирование матрицы править

Пример лаконичной реализации операции транспонирования матриц с использованием парадигмы функционального программирования.

from pprint import pprint # модуль pprint используется для удобного вывода на экран matrix = [[0.5, 0, 0, 0, 0], [ 1, 0.5, 0, 0, 0], [ 1, 1, 0.5, 0, 0], [ 1, 1, 1, 0.5, 0], [ 1, 1, 1, 1, 0.5]] matrix_t = list(zip(*matrix)) # непосредственно транспонирование pprint(matrix) pprint(matrix_t) 
[[0.5, 0, 0, 0, 0], [1, 0.5, 0, 0, 0], [1, 1, 0.5, 0, 0], [1, 1, 1, 0.5, 0], [1, 1, 1, 1, 0.5]] [[0.5, 1, 1, 1, 1], [0, 0.5, 1, 1, 1], [0, 0, 0.5, 1, 1], [0, 0, 0, 0.5, 1], [0, 0, 0, 0, 0.5]] 

Нахождение Факториала править

factorial = lambda x: factorial(x - 1) * x if x > 1 else 1 

Решение квадратного уравнения править

Простая программа для решения квадратных уравнений (то есть вида: ax 2 +bx+c=0). Даются небольшие пояснения, каким образом уравнение решается в том или ином случае (например, для неполных квадратных уравнений).

a = int(input('a = ', )) # запрашиваем первый коэффициент b = int(input('b = ', )) # запрашиваем второй коэффициент c = int(input('c = ', )) # запрашиваем третий коэффициент if a!= 0 and b % 2 == 0 and c!= 0: # решение по сокращенной формуле, т.к. b - четное k = b / 2 d1 = k ** 2 - a * c k1 = (-k + d1 ** 0.5) / a k2 = (-k - d1 ** 0.5) / a print('так как коэффициент b - четное число, решаем по сокращенной формуле') print(f'k1 = k1>') print(f'k2 = k2>') if a != 0 and b % 2 != 0 and c != 0: # решение полного уравнения d = b ** 2 - 4 * a * c if d > 0: k1 = (-b + d ** 0.5) / (2 * a) print(f'дискриминант равен: d>') print(f'первый корень равен: round(k1, 2)>') k2 = (-b - d ** 0.5) / (2 * a) print(f'второй корень равен: round(k2, 2)>') elif d  0: print(f'так как дискриминант меньше нуля и равен: d>') print('действительных корней нет') else: k = -b / (2 * a) print(f'уравнение имеет один корень: k>') if a != 0 and c != 0 and b == 0: # решение уравнения при b = 0 if (- c / a) >= 0: k1 = (-c / a) ** 0.5 print(f'первый корень равен: k1>') k2 = (-1) * ((-c / a) ** 0.5) print(f'второй корень равен: k2>') if (- c / a)  0: print(f' -c / a = : -c / a>, т.е. < 0, поэтому действительных корней нет') if a != 0 and c== 0 and b != 0: # решение уравнения при с = 0 print(f'корень уравнения равен либо нулю, либо -b / a>') if a != 0 and b== 0 and c == 0: # решение уравнения при b = 0 и c = 0 print(f'корни уравнения равны нулю, a*x**2 = 0') 

Что такое дробь править

cls @ECHO OFF title Folder Private if EXIST «HTG Locker» goto UNLOCK if NOT EXIST Private goto MDLOCKER

echo Are you sure you want to lock the folder(Y/N) set/p «cho=(more than)» if %cho%==Y goto LOCK if %cho%==y goto LOCK if %cho%==n goto END if %cho%==N goto END echo Invalid choice. goto CONFIRM

ren Private «HTG Locker» attrib +h +s «HTG Locker» echo Folder locked goto End

echo Enter password to unlock folder set/p «pass=(more than)» if NOT %pass%== [Ваш пароль] goto FAIL attrib -h -s «HTG Locker» ren «HTG Locker» Private echo Folder Unlocked successfully goto End

echo Invalid password goto end

md Private echo Private created successfully goto End

Вычисление числа Пи править

from decimal import * getcontext().prec = int(input('Введите начальную точность:')) # Десятичная точность. def Factorial(F): result = 1 for i in range(2, F + 1): result *= i return result b = int(input('Введите число слагаемых:')) # Число слагаемых. n = 0 k = 0 while k  b: getcontext().prec += 20 # Увеличивается точность. n += (Decimal(Factorial(4 * k) / (Factorial(k) ** Decimal(4))) * Decimal( (1103 + 26390 * k) / (Decimal((4 * 99) ** Decimal(4 * k))))) k += 1 N = n n = Decimal(9801) / (Decimal(Decimal(2 * (Decimal(2 ** Decimal(0.5))) * N))) print(Decimal(n)) input() 

Тренажёр для изучения координат править

Программа, интересная и как тренажёр для учебной работы с координатами (5-7 класс) и как пример несложной программы, которую может написать начинающий программист (8-9 класс)

from tkinter import * import random okno = Tk() okno.geometry('1500x700+0+0') koordinatkax=random.randint(-15,15) koordinatkay=random.randint(-6,6) holst = Canvas(okno, width=1500, height = 600, bg = "white") holst.create_line(0,300,1500,300,fill="black",width=3,arrow=LAST) holst.create_line(750,600,750,0,fill="black",width=3,arrow=LAST) metka = Label(okno,font=20,text="Привет всем! Найди точку с координатами x o">+ str(koordinatkax) + ", y o">+ str(koordinatkay)) metka.place(x = 0,y = 620) metkaX = Label(holst,font=20,text="X") metkaX.place(x = 1475,y =310) metkaY = Label(holst,font=20,text="Y") metkaY.place(x =760,y = 10) #положительные метки Y metka1Y = Label(holst,font=20,text="1") metka1Y.place(x =725,y = 240) #метка 0 metka0 = Label(holst,font=20,text="0") metka0.place(x =725,y = 305) #положительные метки X metka1X = Label(holst,font=20,text="1") metka1X.place(x =792,y = 308) #отрицательные метки Y metka1Y = Label(holst,font=20,text="-1") metka1Y.place(x =725,y = 340) #отрицательные метки X metka1X = Label(holst,font=20,text="-1") metka1X.place(x =692,y = 308) for i in range(14): holst.create_line(0,50*i,1500,50*i,fill="black",width=1) holst.create_line(745,50*i,755,50*i,fill="black",width=3) for i in range(30): holst.create_line(50*i,0,50*i,600,fill="black",width=1) holst.create_line(50*i,295,50*i,305,fill="black",width=3) holst.place(x=0,y=0) knopka = Button(okno) knopka.place(x = 50,y = 650) def gdelevyklik(event): xo = koordinatkax*50+750 yo = 300-koordinatkay*50 if int(((event.x-xo)**2+(event.y-yo)**2)**0.5)  5: okno.title("Молодец! Левая кнопка мыши нажата" + str(event.x) + " " + str(event.y)) else: okno.title("Не правильно! Левая кнопка мыши нажата" + str(event.x) + " " + str(event.y)) holst.bind('', gdelevyklik) 

Функции в программировании

Функция в программировании представляет собой обособленный участок кода, который можно вызывать, обратившись к нему по имени, которым он был назван. При вызове происходит выполнение команд тела функции.

Функции можно сравнить с небольшими программками, которые сами по себе, то есть автономно, не исполняются, а встраиваются в обычную программу. Нередко их так и называют – подпрограммы. Других ключевых отличий функций от программ нет. Функции также при необходимости могут получать и возвращать данные. Только обычно они их получают не с ввода (клавиатуры, файла и др.), а из вызывающей программы. Сюда же они возвращают результат своей работы.

Существует множество встроенных в язык программирования функций. С некоторыми такими в Python мы уже сталкивались. Это print() , input() , int() , float() , str() , type() . Код их тела нам не виден, он где-то «спрятан внутри языка». Нам же предоставляется только интерфейс – имя функции.

С другой стороны, программист всегда может определять свои функции. Их называют пользовательскими. В данном случае под «пользователем» понимают программиста, а не того, кто пользует программу. Разберемся, зачем нам эти функции, и как их создавать.

Предположим, надо три раза подряд запрашивать на ввод пару чисел и складывать их. С этой целью можно использовать цикл:

i = 0 while i  3: a = int(input()) b = int(input()) print(a + b) i += 1

Однако, что если перед каждым запросом чисел, надо выводить надпись, зачем они нужны, и каждый раз эта надпись разная. Мы не можем прервать цикл, а затем вернуться к тому же циклу обратно. Придется отказаться от него, и тогда получится длинный код, содержащий в разных местах одинаковые участки:

print("Сколько бананов и ананасов для обезьян?") a = int(input()) b = int(input()) print("Всего", a + b, "шт.") print("Сколько жуков и червей для ежей?") a = int(input()) b = int(input()) print("Всего", a + b, "шт.") print("Сколько рыб и моллюсков для выдр?") a = int(input()) b = int(input()) print("Всего", a + b, "шт.")

Пример исполнения программы:

Сколько бананов и ананасов для обезьян? 15 5 Всего 20 шт. Сколько жуков и червей для ежей? 50 12 Всего 62 шт. Сколько рыб и моллюсков для выдр? 16 8 Всего 24 шт.

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

Определение функции. Оператор def

В языке программирования Python функции определяются с помощью оператора def . Рассмотрим код:

def count_food(): a = int(input()) b = int(input()) print("Всего", a + b, "шт.")

Это пример определения функции. Как и другие сложные инструкции вроде условного оператора и циклов функция состоит из заголовка и тела. Заголовок оканчивается двоеточием и переходом на новую строку. Тело имеет отступ.

Ключевое слово def сообщает интерпретатору, что перед ним определение функции. За def следует имя функции. Оно может быть любым, также как и всякий идентификатор, например, переменная. В программировании весьма желательно давать всему осмысленные имена. Так в данном случае функция названа «посчитать_еду» в переводе на русский.

После имени функции ставятся скобки. В приведенном примере они пустые. Это значит, что функция не принимает никакие данные из вызывающей ее программы. Однако она могла бы их принимать, и тогда в скобках были бы указаны так называемые параметры.

После двоеточия следует тело, содержащее инструкции, которые выполняются при вызове функции. Следует различать определение функции и ее вызов. В программном коде они не рядом и не вместе. Можно определить функцию, но ни разу ее не вызвать. Нельзя вызвать функцию, которая не была определена. Определив функцию, но ни разу не вызвав ее, вы никогда не выполните ее тела.

Вызов функции

Рассмотрим полную версию программы с функцией:

def count_food(): a = int(input()) b = int(input()) print("Всего", a+b, "шт.") print("Сколько бананов и ананасов для обезьян?") count_food() print("Сколько жуков и червей для ежей?") count_food() print("Сколько рыб и моллюсков для выдр?") count_food()

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

Когда функция вызывается, поток выполнения программы переходит к ее определению и начинает исполнять ее тело. После того, как тело функции исполнено, поток выполнения возвращается в основной код в то место, где функция вызывалась. Далее исполняется следующее за вызовом выражение.

В языке Python определение функции должно предшествовать ее вызовам. Это связано с тем, что интерпретатор читает код строка за строкой и о том, что находится ниже по течению, ему еще неизвестно. Поэтому если вызов функции предшествует ее определению, то возникает ошибка (выбрасывается исключение NameError ):

print("Сколько бананов и ананасов для обезьян?") count_food() print("Сколько жуков и червей для ежей?") count_food() print("Сколько рыб и моллюсков для выдр?") count_food() def count_food(): a = int(input()) b = int(input()) print("Всего", a + b, "шт.")
Сколько бананов и ананасов для обезьян? Traceback (most recent call last): File "test.py", line 2, in count_food() NameError: name 'count_food' is not defined

Для многих компилируемых языков это не обязательное условие. Там можно определять и вызывать функцию в произвольных местах программы. Однако для удобочитаемости кода программисты даже в этом случае предпочитают соблюдать определенные правила.

Функции придают программе структуру

Польза функций не только в возможности многократного вызова одного и того же кода из разных мест программы. Не менее важно, что благодаря им программа обретает истинную структуру. Функции как бы разделяют ее на обособленные части, каждая из которых выполняет свою конкретную задачу.

Пусть надо написать программу, вычисляющую площади разных фигур. Пользователь указывает, площадь какой фигуры он хочет вычислить. После этого вводит исходные данные. Например, длину и ширину в случае прямоугольника. Чтобы разделить поток выполнения на несколько ветвей, следует использовать оператор if-elif-else:

figure = input("1-прямоугольник, 2-треугольник, 3-круг: ") if figure == '1': a = float(input("Ширина: ")) b = float(input("Высота: ")) print("Площадь: %.2f" % (a * b)) elif figure == '2': a = float(input("Основание: ")) h = float(input("Высота: ")) print("Площадь: %.2f" % (0.5 * a * h)) elif figure == '3': r = float(input("Радиус: ")) print("Площадь: %.2f" % (3.14 * r ** 2)) else: print("Ошибка ввода")

Здесь нет никаких функций, и все прекрасно. Но напишем вариант с функциями:

def rectangle(): a = float(input("Ширина: ")) b = float(input("Высота: ")) print("Площадь: %.2f" % (a * b)) def triangle(): a = float(input("Основание: ")) h = float(input("Высота: ")) print("Площадь: %.2f" % (0.5 * a * h)) def circle(): r = float(input("Радиус: ")) print("Площадь: %.2f" % (3.14 * r ** 2)) figure = input("1-прямоугольник, 2-треугольник, 3-круг: ") if figure == '1': rectangle() elif figure == '2': triangle() elif figure == '3': circle() else: print("Ошибка ввода")

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

Если нам когда-нибудь захочется вычислять площадь треугольника по формуле Герона, а не через высоту, то не придется искать код во всей программе (представьте, что она состоит из тысяч строк кода как реальные программы). Мы пойдем к месту определения функций и изменим тело одной из них.

Если понадобиться использовать эти функции в какой-нибудь другой программе, то мы сможем импортировать их туда, сославшись на данный файл с кодом (как это делается в Python, будет рассмотрено позже).

Практическая работа

В программировании можно из одной функции вызывать другую. Для иллюстрации этой возможности напишите программу по следующему описанию.

Основная ветка программы, не считая заголовков функций, состоит из одной строки кода. Это вызов функции test() . В ней запрашивается на ввод целое число. Если оно положительное, то вызывается функция positive() , тело которой содержит команду вывода на экран слова «Положительное». Если число отрицательное, то вызывается функция negative() , ее тело содержит выражение вывода на экран слова «Отрицательное».

Понятно, что вызов test() должен следовать после определения функций. Однако имеет ли значение порядок определения самих функций? То есть должны ли определения positive() и negative() предшествовать test() или могут следовать после него? Проверьте вашу гипотезу, поменяв объявления функций местами. Попробуйте объяснить результат.

Примеры решения и дополнительные уроки в pdf-версии курса

X Скрыть Наверх

Python. Введение в программирование

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *