Что есть функция в Python?
Эта статья предназначена только для тех людей, которым хочется узнать, чем на самом деле является функция в python. НО! Предупреждаю, я не буду лезть в сурсы питона. Эта статья была создана только для обычных вроде меня программистов.
Статья будет состоять из 4 частей:
- Как осуществляется поиск атрибутов в классах.
- Что есть метод и как он вызывается.
- Что есть функция и как она вызывается.
- Вывод
1. Как осуществляется поиск атрибутов в классах
Мы знаем, что при обращении к атрибуту вызывается дандер-метод __getattribute__, который в свою очередь пытается возвратить наш атрибут, но! Если он его не находит атрибут, то он вызывает исключение AttributeError, если __getattr__ не определен в нашем классе. Это понятно, но мы не знаем, что происходит под капотом. И поэтому я решил создать свою интерпретацию поиска атрибутов.

Как видите, тут не так все просто как нам казалось) Срабатывается куча проверок, чтобы наконец-то либо возвратить наш атрибут или вызвать исключение.
Давайте с помощью этой блок-схемы, реализуем свой прототип __getattribute__ и посмотрим, будет ли он работать так же как и оригинал.
Вот что вышло:
def __getattribute__(self, item): ''' Прототип дандер-метода __getattribute__.''' for cls in Class.__mro__: if item in cls.__dict__: # есть ли атрибут в пространстве имен класса. item_class_dict = type(cls.__dict__[item]).__dict__ # Пространства имен класса нашего атрибута if "__get__" in item_class_dict: # Является ли атрибут, дескриптором. return cls.__dict__[item].__get__(self, cls) return cls.__dict__[item] else: if item in self.__dict__: # есть ли атрибут в пространстве имен экземпляра класса return self.__dict__[item] if "__getattr__" in self.__class__.__dict__: # Есть ли у класса, дандер-метод __getattr__ return self.__getattr__(item) raise AttributeError
И давайте наконец его испробуем!
Создадим класс Pet, который наследуется от класса Animal, где первый атрибут будет объектом дескриптора данных и 2 обычных переменных экземпляра класса это animal и age:
class Animal: eyes = 2 legs = 4 def eat(self, food): return f"Yum, yum, yum ()" class Descriptor: def __set_name__(self, owner, name): self.name = name def __get__(self, instance, owner): return instance.__dict__[self.name] def __set__(self, instance, value): if isinstance(value, str): instance.__dict__[self.name] = value else: raise TypeError("Название, должно быть строкой!") class Pet(Animal): name = Descriptor() def __init__(self, name, animal, age=1): self.name = name self.animal = animal self.age = age def __getattribute__(self, item): ''' Прототип дандер-метода __getattribute__.''' for cls in Pet.__mro__: if item in cls.__dict__: # есть ли атрибут в пространстве имен класса. item_class_dict = type(cls.__dict__[item]).__dict__ # Пространства имен класса нашего атрибута if "__get__" in item_class_dict: # Является ли атрибут, дескриптором. return cls.__dict__[item].__get__(self, cls) return cls.__dict__[item] else: if item in self.__dict__: # есть ли атрибут в пространстве имен экземпляра класса return self.__dict__[item] if "__getattr__" in self.__class__.__dict__: # Есть ли у класса, дандер-метод __getattr__ return self.__getattr__(item) raise AttributeError def meow(self): return "Meowwww. " if self.animal.lower() == "cat" else "You're pet isn't cat!" cat_kit = Pet("Kit", "Cat", 17) print(cat_kit.name) # Kit print(cat_kit.animal) # Cat print(cat_kit.meow()) # Meowwww! print(cat_kit.meow) # > print(cat_kit.eyes) # 2 print(cat_kit.eat("Kitekat")) # "Yum, yum, yum (Kitekat)" print(cat_kit.eat) # > cat_kit.dog
И как вы видите, у нас все сработало!) Наш прототип __getattribute__ отлично возвращает методы, дескрипторы данных, дескрипторы не-данных, локальные атрибуты (если так можно назвать), атрибуты класса.) (Небольшое уточнение: под возвращением атрибутов, я имею ввиду возвращения их через экземпляр класса, т.к через класс все атрибуты возвращаются с помощью вызова __getattribute__ у метакласса (type))
Давайте на этом примере я объясню как все происходит. К примеру, второй вызов нашего прототипа для возвращения локального атрибута animal. Тут все очень просто. Происходит 3 итерации нашего цикла, где мы проверяем, является ли animal чьим-то атрибутом класса. Конечно же нет, потому у нас выполняется конструкция в else, где мы проверяем есть ли animal в пространстве имен экземпляра класса. Есть. И мы его возвращаем.
А теперь давайте перейдем к дескриптору данных, то есть к атрибуту name. Происходит 1 итерация, где мы проверяем, есть ли он в пространстве имен класса Pet. Да, есть. Дальше, является ли он дескриптором данных. Является. Потому мы с помощью дандер метода __get__ возвращаем его.
Так же не забываем, что наш прототип проходит по всему MRO класса, как у оригинала, он может возвращать все методы род.класса и так же его атрибуты (как показано на примере). Но некоторые из вас спросят, как вызвался атрибут eyes класса Animal к примеру? Для начала узнаем как выглядит MRO у класса Pet.
Вот так:
[main.Pet’>, main.A’>, ]
В первую очередь происходит первая итерация нашего цикла в прототипе __getattribute__, где cls это класс Pet. Он не проходит проверку if item in cls.__dict__, потому что у Pet нет такого атрибута. Потому, происходит вторая итерация, уже Animal и при той проверке, она возвращает True поэтому мы возвращаем атрибут класса Animal.
Теперь, вы уже знаете как примерно происходит поиск атрибутов, как под капотом вызываются дескриптора данных и дескрипторы не-данных, локальные атрибуты, атрибуты класса и т.д. Вы даже можете создать свой прототип __getattribute__. Поэтому, давайте перейдем ко второй части статьи.
2. Что такое метод и как он вызывается
Вы уже наверно подумали, почему же я не объяснил про вызов метода meow с помощью нашего прототипа, точнее. как он вызвался с помощью него?
Давайте по порядку и издалека. Какие свойства имеют методы? Ну, они возвращают объект bound method при обращении через экземпляр класса, а так же первым аргументом всегда у них является опять-таки экземпляр класса. Отлично! С этим мы разобрались.
Теперь. Давайте, чтобы доказать вам, что метод является дескриптором не-данных я вам объясню почему метод не вызывается как обычный атрибут класса (Class.__dict__[item]) или как обычный локальный атрибут объекта класса (obj.__dict__[item])
Во первых, если бы методы вызывались как обычный атрибут класса, мы бы точно не смогли бы возвращать объект bound method через экземпляр класса при обращений к ним, потому что метод возвращал бы объект функций (как и класс). И тут вы зададите вопрос, а почему? Ну потому что в любом случае он бы под капотом вызывался бы как атрибут класса (Class.__dict__[item]), если бы мы даже обращались к нему с помощью объекта класса. Вот небольшой пример:
class Foo: def get_one(self): return 1 def __getattribute__(self, item): return Foo.__dict__[item] a = Foo() print(Foo.get_one) # print(a.get_one) # , а должен был возвратить > print(Foo.get_one(a)) # 1 print(a.get_one(a)) # 1 print(a.get_one()) # TypeError: Foo.get_one() missing 1 required positional argument: 'self' print(Foo.get_one is a.get_one) # True
А теперь, давайте попробуем второй вариант. Запустим такой код:
class Email: def __init__(self): self.lst_packages = [] def add_package(self, package): if isinstance(package, dict) is False: raise TypeError("Предмет должен иметь адрес и товар. ") if package not in self.lst_packages: self.lst_packages.append(package) return "Посылка была добавлена!" return "В нашей почте такая посылка уже есть!" def get_packages(self): return self.lst_packages def __getattribute__(self, item): if item == "__dict__": return Email.__dict__[item].__get__(self, Email) return self.__dict__[item] mail_ru = Email() print(mail_ru.lst_packages) # [] print(mail_ru.get_packages()) # KeyError: 'get_packages'
И к сожалению мы получаем исключение KeyError.
Тут мне кажется и так было понятно что вариант с объектом класса не сработает, только из-за того что методы находятся в пространстве имен класса, а не в экземпляре естественно. И прежде чем я отвечу на вопрос что вообще делает тут такое:
if item == "__dict__": return Email.__dict__[item].__get__(self, Email)
Давайте вернемся к результатам:
- Метод не вызывается как обычный атрибут класса, т.к происходит иное поведение (возвращение объекта функций, а не bound method даже при обращении к методу с помощью объекта класса), во вторых обращение экземпляра класса к методам такое же,как и у класса, т.к под капотом он все равно вызывался бы как через класс. Потому, такой вариант не является истинным.
- Метод не вызывается как обычный локальный атрибут объекта класса, т.к методы находятся в пространстве имен класса, а не в объекте класса.
И что мы получаем тогда? То что метод является — дескриптором. А точнее: Метод — это дескриптор не-данных.
Если вы мне не поверили, то:
print("__get__" in type(cat_kit.meow).__dict__) # True print("__set__" in type(cat_kit.meow).__dict__) # False print("__delete__" in type(cat_kit.meow).__dict__) # False
Кстати, если уж мы вспомнили о классе Pet и его методе meow, то давайте заодно попробуем вызвать его как дескриптор:
class Descriptor: def __set_name__(self, owner, name): self.name = name def __get__(self, instance, owner): return 1 class Pet: foobar = Descriptor() def __getattribute__(self, item): ''' Прототип дандер-метода __getattribute__. Возвращает''' for cls in Pet.__mro__: if item in cls.__dict__: # есть ли атрибут в пространстве имен класса. item_class_dict = type(cls.__dict__[item]).__dict__ # Пространства имен класса нашего атрибута if "__get__" in item_class_dict: # Является ли атрибут, дескриптором. print(f"THE DESCRIPTOR. ") return cls.__dict__[item].__get__(self, cls) return cls.__dict__[item] else: if item in self.__dict__: # есть ли атрибут в пространстве имен экземпляра класса return self.__dict__[item] if "__getattr__" in self.__class__.__dict__: # Есть ли у класса, дандер-метод __getattr__ return self.__getattr__(item) raise AttributeError def meow(self): return "Meowwww. " cat_kit = Pet() print(cat_kit.meow()) # THE DESCRIPTOR. Meowwww. print(cat_kit.foobar) # THE DESCRIPTOR. 1 # cls.__dict__[item].__get__(instance, owner) print(Pet.__dict__["meow"].__get__(cat_kit, Pet).__call__()) # Meowwww. print(Pet.__dict__["meow"].__get__(cat_kit, Pet)) # > print(Pet.__dict__["foobar"].__get__(cat_kit, Pet)) # 1
Как видите, все сработало на ура!
Теперь, давайте вернемся к той части кода в классе Email:
def __getattribute__(self, item): if item == "__dict__": return Email.__dict__[item].__get__(self, Email) return self.__dict__[item]
Почему тут присутствует проверка item на значение __dict__ и почему я вызываю у него метод __get__? Ну-у, скорее всего потому что он является — дескриптором :). И кстати дескриптором данных, он имеет и __get__ и __set__ и __delete__.
И вы наверно в ответ скажите, а когда мы вообще к нему обращаемся? Вот здесь self.__dict__ опять вызывается __getattribute__,в котором параметр item имеет значение __dict__.
И узнав что __dict__ является дескриптором, вы одновременно узнали как он возвращается у объектов.
print(Pet.__dict__["__dict__"].__get__(cat_kit, Pet)) # print(Pet.__dict__["__dict__"].__get__(dog, Pet)) #
И кстати, у классов же атрибут __dict__ не является дескриптором.
3. Что такое функция и как она вызывается
Теперь, мы можем приступить к самой главной части статье.
Мы знаем что функция и методы (здесь, под методами я подразумеваю именно функцию класса, а не объект класса method) являются объектами класса function.
Потому не сложно понять, что функция так же является дескриптором не-данных и еще не забываем, что метод по сути является функцией класса, грубо говоря. И тут задается вопрос, а может на самом деле функции вызывается как метод? Не совсем. Но мы можем ее так вызывать:
class Foo: pass f = Foo() def get_one(self): return 1 print(get_one.__get__(f, Foo)) # > print(get_one.__get__(f, Foo).__call__()) # 1 print("get_one" in Foo.__dict__) # False
Тут удивляться нечему. Потому что вместо того, чтобы обращаться к методу через пространство имен класса (потому что по другому мы не смогли бы), мы попросту прямо обращаемся к нашей обычной функции и возвращаем ее как связанный метод.
Но все равно, на самом деле функции так не вызываются. Давайте опять вспомним, что функция является объектом класса function, где тело функций содержится в дандер-методе __call__ который принадлежит естественно к классу функций. И чтобы вызвать этот метод. Что нужно сделать?) Правильно! Обратиться к дандер-методу __get__ где экземпляром класса будет сама наша функция, а класс — класс функций. Давайте же это реализуем!
def foo(): return 1 Function = foo.__class__ print(Function.__dict__["__call__"].__get__(foo, Function) == foo.__call__) # True print(Function.__dict__["__call__"].__get__(foo, Function).__call__()) # 1 print(foo.__call__()) # 1
И вот так, мы узнали как на самом деле «вызываются» функции) Но у некоторых может возникнуть еще один вопрос, «а почему у функций есть метод __get__, если мы его по сути не используем для вызова?» Ответ очень прост: на случае если мы обратимся к функции как к атрибуту.
4. Вывод
Мы узнали как примерно происходит поиск атрибутов в классах, что функция и метод являются дескрипторами не-данных. Мы узнали как на самом деле вызываются методы:
cls.__dict__["method"].__get__(instance, cls)()
Как на самом деле вызываются функции:
cls_function.__dict__["__call__"].__get__(func, cls_function)()
И. Все! Моя статья уже подходит к концу! Потому. напоследок я хочу дать вам домашнее задания:
Добавить одну важную, но одновременно маленькую деталь, которую я не успел вписать в мою блок-схему и в прототип. И конечно же, скинуть улучшенный прототип в комментариях!
И так же, всем огромное спасибо за прочтение моей первой статьи! Огромная благодарность Павлу за помощь в ее написании и спасибо Бензу! Я очень надеюсь что я хоть как-то дал вам ответ!
- Python
- Программирование
Чем отличаются понятия функции, процедуры и метода в программировании?
Периодически вижу все 3 определения. Это одно и то же или же что-то разное, и какое из понятий правильнее всего употреблять?
- Вопрос задан более трёх лет назад
- 79968 просмотров
Комментировать
Решения вопроса 2
Функция — подпрограмма, выполняющая какие-либо операции и возвращающая значение.
Процедура — подпрограмма, которая только выполняет операции, без возврата значения.
Метод — это функция или процедура, которая принадлежит классу или экземпляру класса.
Ответ написан более трёх лет назад
Нравится 30 5 комментариев
Забыли сказать, что у функции, процедуры и метода должно быть имя и параметры (или ничего, но скобки нужны в любом случае)
MaxLich, Ничего я не забыл. Это не имеет отношения к вопросу. Вопрос был: чем отличаются. А то что функция/процедура/метод имеет имя и список рациональных параметров это и так очевидно. Скобки — это вообще деталь реализации.

«Процедура», имхо, устаревшее понятие. Просто функция, которая не возвращает ничего (void). Это еще во времена Delphi процедура имела отдельное ключевое слово.
D3lphi, Ну я-то про определения, которые Вы дали. Это не просто подпрограмма, а подпрограмма, имеющая имя. Инициализатор (в джаве), например, тоже блок кода, подпрограмма, но имени не имеет, и параметров тоже, поэтому это не функция/метод.
MaxLich, это не определения, а лишь то, чем эти понятия отличаются друг от друга.
Функция/процедура не обязана иметь имя — анонимные функции, как пример.

Всё это можно свести к понятию — подпрограмма. Бывает, читаешь книгу, там автор пишет о своём куске кода как о методе, потом его начинает называть функцией.
Ответ написан более трёх лет назад
Комментировать
Нравится 6 Комментировать
Ответы на вопрос 4

# @mindtester
Функция — подпрограмма, выполняющая какие-либо операции и возвращающая значение.
Процедура — подпрограмма, которая только выполняет операции, без возврата значения.
Метод — это функция или процедура, которая принадлежит классу или экземпляру класса.
как бы да, но. только на самом начальном этапе, что бы угомонить хаос в голове новичка ))
в дальнейшем, все интереснее все эти понятия контекстно зависимые, контекстом является парадигма программирования и/или конкретный язык
1 — в контексте парадигм, из данных понятий уникально одно Метод, как уже было сказано D3lphi, это нечто принадлежащее классу. класс, в свою очередь, это фундаментальное понятие ООП основанного на классах (шарм ситуации в том, что ООП бывает тоже разное ;))
в этом случае чаще принято уточнять что метод — это один из видов челнов класса (бывают еще поля, свойства, интерфейсы но это уже контекст конкретного языка) .. и как верно заметил Griboks — он реализуется функцией или процедурой
но .. есть много языков, где понятия метод нет вообще
а еще есть функциональное программирование .. эта парадигма частично присутствует во многих современных языках, однако есть языки, где любой код только функция
2 — из контекста языков:
понятие процедура в явном виде, чаще всего употребляют преподаватели, которые сами учились на языках типа Fortran, Pascal или родственных, и либо не имели другого опыта вообще, либо иной опыт был на много скромнее
сейчас доминируют языки, основывающиеся на Си синтаксисе, даже java и js в данном вопросе станут родственниками классического Си
а в нем нет понятия процедуры, только функции.. а на случай, когда функция не обязана возвращать какую либо величину, просто указывается тип возвращаемого значения void
смешение всего этого на примере C# — в этом языке, все есть объект. а любой исполняемый код это метод, и методы реализуются только функциями (в тч void функциями)
Введение в Python — функции и методы¶
Этот урок направлен на развитие некоторых ключевых навыков на Python и введение в него фундаментального компонента для написания хорошего кода — функции.
Функции¶
Часто в Python необходимо выполнять одни и те же или похожие вычисления несколько раз. Вместо того, чтобы каждый раз просто записывать это вычисление полностью, проще один раз записать его как функцию, а затем каждый раз, когда вычисление должно произойти, просто вызывайте функцию.
Есть несколько вещей, которые являются требованиями или лучшими сценариями для написания функции. Обобщенная структура функции показана ниже:
def a_function(input_1,input_2): '''Short string to describe what the function does''' #Stuff happens inside the function return output_1,output_2
Так что же здесь произошло тогда? Прежде всего, мы использовали def для определения имени функции, в приведенном выше примере как «a_function». озже в коде запись a_function (. ) вызовет функцию, так же как вызов функции print() сообщает Python, что нужно что-то распечатать при запуске кода. В скобках (input_1,input_2) указаны любые начальные данные, известные как аргументы, которые функция должна знать для выполнения вычислений. Не всегда обязательно иметь какие-либо аргументы в функции, и в этом случае ее можно просто определить как a_function() .
После определения имени функции и любых аргументов часто бывает полезно написать хотя бы одну строку, описывающую, что делает функция. Это помогает улучшить читаемость функции, будь то для кого-то, кто еще не написал функцию, или для вас, если прошло какое-то время и вам нужно снова взглянуть на функцию.
Основная часть функциональных вычислений происходит после этого с использованием любых аргументов, определенных во время ввода функции, и аргументов, определенных во время вывода функции. Последняя ключевая часть функции — это оператор return в конце — это позволяет распечатать все, что было вычислено в функции, которую вы хотите использовать в остальной части кода, или присвоить другой переменной.
Здесь нужно было понять довольно много, поэтому было бы легче увидеть, как выглядит реальная функция. Ниже приведена функция, позволяющая получить квадратный корень из любого числа. Запустив приведенную ниже функцию, мы можем загрузить ее в память ноутбука, чтобы использовать ее позже, используя Shift + Enter .
def square_root(number): 'A function which can obtain the square root of a number' root_number = number**(1/2) return root_number
Основы Python — Функции и Объекты
До сих пор мы видели, как мы можем использовать переменные в Python для хранения различных типов данных, и как мы можем использовать структуры «управления потоком», такие как условия и циклы, чтобы изменить порядок или способ выполнения строк кода. С помощью только этих инструментов мы уже можем начать выражать некоторые довольно сложные логические схемы. Однако, имея только наши текущие инструменты, любой достаточно сложный скрипт начинал бы становиться очень длинным, так как каждый раз, когда мы хотели выполнить определенный процесс, нам приходилось бы переписывать весь его код.
Вот тут-то и появляются функции и классы . Функции позволяют нам инкапсулировать строки кода для создания пользовательских процессов, которые могут быть повторно использованы в любом месте скрипта. Объекты делают эту инкапсуляцию еще одним шагом вперед и заключают в себе не только один процесс, но и несколько связанных процессов, а также локальные переменные, которые могут отслеживать состояние этого объекта.
Функции
Мы уже видели и использовали некоторые функции, такие как type() , str() , .append() , .keys() и range() . Но что такое функции на самом деле?
Как и в математике, функция-это базовая структура, которая может принимать входные данные, выполнять некоторую обработку этих входных данных и возвращать результат. Давайте создадим базовую функцию, которая добавит два к заданному числу и вернет нам результат:
result = inputNumber + 2
Сам по себе этот код будет только определять, что делает функция, но на самом деле не будет выполнять никакого кода. Чтобы выполнить код внутри функции, вы должны вызвать его где-то внутри скрипта и передать ему соответствующие входные данные:
Определение (Definition) функции начинается с ключевого слова def . После этого следует имя функции,которое следует тем же соглашениям об именовании, что и переменные. Внутри круглой скобки после имени функции можно поместить любое количество входных переменных, которые будут переданы функции при ее вызове и доступны в теле функции. При вызове функции можно либо непосредственно передавать значения, либо передавать переменные, внутри которых хранятся значения. Например, этот код вызовет функцию таким же образом:
Здесь значение переменной var , которое в данном случае равно 2 , передается функции addFunction , а затем доступно внутри этой функции через переменную inputNumber . Обратите внимание, что имена двух переменных var и inputNumber не обязательно должны совпадать. Когда значение передается функции, оно образует прямую связь между двумя наборами скобок, которые несут данные.
В этом случае var — это глобальная переменная, которая хранит значение 2 в основном скрипте, в то время как inputNumber — это локальная переменная, которая хранит это значение только на время выполнения этой функции. Таким образом, функции «сворачивают» конкретные задачи и все данные, необходимые для выполнения этой задачи, чтобы ограничить количество глобальных переменных, необходимых в основной функции.
Первая строка, объявляющая функцию и ее входные данные, заканчивается двоеточием , которое должно быть уже знакомо, с остальной частью тела функции, вставленной из первой строки. При необходимости, если вы хотите вернуть значение из функции обратно в основной скрипт, вы можете завершить функцию с помощью ключевого слова return , за которым следует значение или переменная, которую вы хотите вернуть. Как только функция попадет в оператор return , она пропустит остальную часть тела и вернет связанное значение. Это может быть использовано для создания более сложного поведения внутри функции:
return ‘Number must be positive!’
result = inputNumber + 2
Вы можете видеть, что в этом случае, если входные данные меньше нуля, условное условие будет выполнено, что приводит к запуску первого оператора return, пропуская остальную часть кода в функции.
Вы можете передать в функцию любое количество входных данных, но количество входных данных всегда должно совпадать между тем, что определено в функции, и тем, что передается в нее при вызове функции. Например, мы можем расширить нашу простую функцию сложения, чтобы принять два числа, которые будут добавлены:
def addTwoNumbers(inputNumber1, inputNumber2):
result = inputNumber1 + inputNumber2
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 называется классом, но эти два слова часто используются взаимозаменяемо. Вы можете представить себе класс как структуру, которая инкапсулирует набор связанных функций (функции, принадлежащие определенным объектам, часто называются «методами» этого объекта) с набором локальных переменных, которые отслеживают состояние этого класса. Вместе эти переменные и методы определяют «поведение» объекта и определяют, как он взаимодействует с другими объектами в программной «среде».
Давайте подумаем об этом в повседневной жизни. Для животного примером метода может быть «бег». Многие вещи могут работать, поэтому определение работы как функции было бы общим и не обязательно относилось бы к тому, кто выполняет работу. С другой стороны, примером класса может быть «собака», которая будет иметь экземпляр метода «бег», а также другие методы, связанные с тем, чтобы быть собакой, такие как «еда» и «лай». Он также будет иметь набор переменных для хранения информации о данной собаке, такой как ее возраст, порода или вес. Другим классом может быть «человек», который будет хранить различные переменные и будет иметь свою собственную версию методов, таких как «бег» и «еда» (но, надеюсь, не «лай»).
Давайте определим очень простой класс, чтобы увидеть, как он работает. Мы будем использовать пример счетчика, который будет хранить значение и увеличивать это значение на основе запросов пользователей:
def addToCounter(self, inputValue):
Обратите внимание, что мы снова используем сокращение += для увеличения значения переменной count объекта на входное значение. Чтобы использовать этот класс, нам сначала нужно создать его экземпляр, который мы будем хранить в переменной так же, как и любой другой фрагмент данных:
Как только мы создадим экземпляр класса (называемый «instantiation»), мы сможем запустить методы этого экземпляра и запросить его переменные. Обратите внимание, что общее определение класса — это только конструкция. Все переменные внутри класса применяются только к определенному экземпляру, и методы могут выполняться только в том случае, если они связаны с этим экземпляром. Например:
Сразу же вы заметите некоторые различия между тем, как мы определяем функции и классы. Во-первых, никакие переменные не передаются в первой строке определения, так как ключевое слово class определяет только общую структуру класса. После первой строки вы найдете список переменных, которые являются локальными переменными этого класса, и будете отслеживать данные для отдельных экземпляров. После этого у вас будет коллекция локальных методов (помните, что «методы» — это просто функции, принадлежащие определенному классу), которые определяют функциональность класса. Эти методы определяются так же, как и раньше, за исключением того, что вы видите, что первым вводом всегда является ключевое слово self . Это представляет экземпляр объекта и всегда передается как первый входной сигнал в каждый метод в классе. Это позволяет вам запрашивать локальные переменные экземпляра, как вы можете видеть, что мы делаем с переменной count .
Чтобы вызвать метод внутри класса, вы используете имя переменной, хранящей экземпляр, и используете точечную нотацию » . » для вызова метода. Точка — это в основном ваш путь в экземпляр, а также все его данные и функциональность. Мы уже видели эту точку раньше, например, когда вызывали функцию .append() в списке. Это потому, что список на самом деле является классом сам по себе! Когда вы определяете список, вы фактически создаете экземпляр класса list , который наследует все функциональные возможности этого класса (сумасшествие, правда?). На самом деле в Python существует только небольшая коллекция примитивных типов данных (ints, floats, booleans и некоторые другие), а все остальное определяется как классы в рамках ООП. Даже строки — это специальные классы, которые хранят коллекцию символов.
Кстати, можно также использовать синтаксис » . » для запроса локальных переменных экземпляра класса. Например, если мы хотим найти значение переменной count myCounter , мы можем просто задать его, набрав:
Однако это не рекомендуется, поскольку оно раскрывает конечному пользователю истинное имя локальных переменных. В производственной среде это создало бы серьезные риски для безопасности, но считается плохой практикой даже в частном использовании. Вместо этого рекомендуется создать специальные методы accessor для извлечения значений переменных из экземпляра, как это было сделано с помощью метода getCount() в нашем примере. Еще одно преимущество этой практики (которая называется инкапсуляцией) заключается в том, что код легче поддерживать. Вы можете вносить любые изменения в определение класса, включая изменение имен локальных переменных и того, что они делают. Пока вы поддерживаете функции доступа и они возвращают ожидаемый результат, вам не нужно ничего обновлять в основном коде.
Что касается именования классов, то вы можете следовать тому же правилу, что и именование переменных или функций, однако стандартная практика заключается в том, чтобы прописывать каждое слово, включая первое.
Наконец, в приведенном выше примере каждый экземпляр, который мы делаем из CounterClass , будет запускать счетчик в 0 . Однако что делать, если мы хотим указать, каким должно быть это число, когда мы создаем экземпляр класса? Для этого мы можем реализовать метод __init__() (это два подчеркивания на каждой стороне ‘init’):
def __init__(self, inputValue):
def addToCounter(self, inputValue):
