Основы Python — Функции и Объекты

Автор: Danil Nagy. Перевод: Дмитрий Булка. Оригинал: https://medium.com/generative-design/fundamentals-of-python-functions-and-objects-bf8a4953068a

До сих пор мы видели, как мы можем использовать переменные в Python для хранения различных типов данных, и как мы можем использовать структуры "управления потоком", такие как условия и циклы, чтобы изменить порядок или способ выполнения строк кода. С помощью только этих инструментов мы уже можем начать выражать некоторые довольно сложные логические схемы. Однако, имея только наши текущие инструменты, любой достаточно сложный скрипт начинал бы становиться очень длинным, так как каждый раз, когда мы хотели выполнить определенный процесс, нам приходилось бы переписывать весь его код.

Вот тут-то и появляются функции и классы. Функции позволяют нам инкапсулировать строки кода для создания пользовательских процессов, которые могут быть повторно использованы в любом месте скрипта. Объекты делают эту инкапсуляцию еще одним шагом вперед и заключают в себе не только один процесс, но и несколько связанных процессов, а также локальные переменные, которые могут отслеживать состояние этого объекта.


Функции

Мы уже видели и использовали некоторые функции, такие как type(), str(), .append(), .keys() и range(). Но что такое функции на самом деле?

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

def addFunction(inputNumber):

     result = inputNumber + 2

     return result

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

print addFunction(2)

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

var = 2

print addFunction(var)

Здесь значение переменной var, которое в данном случае равно 2, передается функции addFunction, а затем доступно внутри этой функции через переменную inputNumber. Обратите внимание, что имена двух переменных var и inputNumber не обязательно должны совпадать. Когда значение передается функции, оно образует прямую связь между двумя наборами скобок, которые несут данные.

В этом случае var — это глобальная переменная, которая хранит значение 2 в основном скрипте, в то время как inputNumber — это локальная переменная, которая хранит это значение только на время выполнения этой функции. Таким образом, функции "сворачивают" конкретные задачи и все данные, необходимые для выполнения этой задачи, чтобы ограничить количество глобальных переменных, необходимых в основной функции.

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

def addFunction(inputNumber):

     if inputNumber < 0:

          return 'Number must be positive!'

     result = inputNumber + 2

     return result


print addFunction(-2)

print addFunction(2)

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

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

def addTwoNumbers(inputNumber1, inputNumber2):

     result = inputNumber1 + inputNumber2

     return result


print addTwoNumbers(2, 3)

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

def twoNumbers(inputNumber1, inputNumber2):

     addition = inputNumber1 + inputNumber2

     multiplication = inputNumber1 * inputNumber2

     return [addition, multiplication]


result = twoNumbers(2, 3)

print 'addition: ' + str(result[0])

print 'multiplication: ' + str(result[1])

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


Объекты (Классы)

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

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

Давайте подумаем об этом в повседневной жизни. Для животного примером метода может быть "бег". Многие вещи могут работать, поэтому определение работы как функции было бы общим и не обязательно относилось бы к тому, кто выполняет работу. С другой стороны, примером класса может быть "собака", которая будет иметь экземпляр метода "бег", а также другие методы, связанные с тем, чтобы быть собакой, такие как "еда" и "лай". Он также будет иметь набор переменных для хранения информации о данной собаке, такой как ее возраст, порода или вес. Другим классом может быть "человек", который будет хранить различные переменные и будет иметь свою собственную версию методов, таких как "бег" и "еда" (но, надеюсь, не "лай").

Давайте определим очень простой класс, чтобы увидеть, как он работает. Мы будем использовать пример счетчика, который будет хранить значение и увеличивать это значение на основе запросов пользователей:

class CounterClass:


count = 0


def addToCounter(self, inputValue):

     self.count += inputValue


def getCount(self):

     return self.count

Обратите внимание, что мы снова используем сокращение += для увеличения значения переменной count объекта на входное значение. Чтобы использовать этот класс, нам сначала нужно создать его экземпляр, который мы будем хранить в переменной так же, как и любой другой фрагмент данных:

myCounter = CounterClass()

Как только мы создадим экземпляр класса (называемый "instantiation"), мы сможем запустить методы этого экземпляра и запросить его переменные. Обратите внимание, что общее определение класса — это только конструкция. Все переменные внутри класса применяются только к определенному экземпляру, и методы могут выполняться только в том случае, если они связаны с этим экземпляром. Например:

myCounter.addToCounter(2)

print myCounter.getCount()

Сразу же вы заметите некоторые различия между тем, как мы определяем функции и классы. Во-первых, никакие переменные не передаются в первой строке определения, так как ключевое слово class определяет только общую структуру класса. После первой строки вы найдете список переменных, которые являются локальными переменными этого класса, и будете отслеживать данные для отдельных экземпляров. После этого у вас будет коллекция локальных методов (помните, что "методы" - это просто функции, принадлежащие определенному классу), которые определяют функциональность класса. Эти методы определяются так же, как и раньше, за исключением того, что вы видите, что первым вводом всегда является ключевое слово self. Это представляет экземпляр объекта и всегда передается как первый входной сигнал в каждый метод в классе. Это позволяет вам запрашивать локальные переменные экземпляра, как вы можете видеть, что мы делаем с переменной count.

Чтобы вызвать метод внутри класса, вы используете имя переменной, хранящей экземпляр, и используете точечную нотацию "." для вызова метода. Точка — это в основном ваш путь в экземпляр, а также все его данные и функциональность. Мы уже видели эту точку раньше, например, когда вызывали функцию .append() в списке. Это потому, что список на самом деле является классом сам по себе! Когда вы определяете список, вы фактически создаете экземпляр класса list, который наследует все функциональные возможности этого класса (сумасшествие, правда?). На самом деле в Python существует только небольшая коллекция примитивных типов данных (ints, floats, booleans и некоторые другие), а все остальное определяется как классы в рамках ООП. Даже строки — это специальные классы, которые хранят коллекцию символов.

Кстати, можно также использовать синтаксис "." для запроса локальных переменных экземпляра класса. Например, если мы хотим найти значение переменной count myCounter, мы можем просто задать его, набрав:

myCounter.count

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

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

Наконец, в приведенном выше примере каждый экземпляр, который мы делаем из CounterClass, будет запускать счетчик в 0. Однако что делать, если мы хотим указать, каким должно быть это число, когда мы создаем экземпляр класса? Для этого мы можем реализовать метод __init__() (это два подчеркивания на каждой стороне ‘init’):

class CounterClass:


count = 0


def __init__(self, inputValue):

     self.count = inputValue


def addToCounter(self, inputValue):

     self.count += inputValue


def getCount(self):

     return self.count


Теперь мы можем создать новый экземпляр счетчика, но на этот раз передать начальное значение для подсчета.

myNewCounter = CounterClass(10)

myNewCounter.addToCounter(2)


#теперь это должно возвращать 12

print myNewCounter.getCount()

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

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