Введение
В этом практическом занятии (лабораторной работе) вы научитесь исследовать внутреннее устройство функций Python. Функции в Python являются объектами с собственными атрибутами и методами, и понимание этого позволяет получить более глубокое представление о том, как функции работают. Эти знания помогут вам писать более мощный и гибкий код.
Вы научитесь проверять атрибуты и свойства функций, использовать модуль inspect для изучения сигнатур функций и применять эти методы исследования для улучшения реализации класса. Файл, который вы будете изменять, - это structure.py.
Исследование атрибутов функций
В Python функции считаются объектами первого класса. Что это значит? Ну, это похоже на то, как в реальном мире у вас есть разные типы объектов, например, книга или ручка. В Python функции также являются объектами, и, как и другие объекты, они имеют свой набор атрибутов. Эти атрибуты могут дать нам много полезной информации о функции, например, ее имя, место определения и способ реализации.
Начнем наше исследование, открыв интерактивную оболочку Python. Эта оболочка похожа на игровую площадку, где мы можем сразу же писать и запускать код на Python. Для этого сначала перейдем в директорию проекта, а затем запустим интерпретатор Python. Вот команды, которые нужно выполнить в терминале:
cd ~/project
python3
Теперь, когда мы находимся в интерактивной оболочке Python, определим простую функцию. Эта функция будет принимать два числа и складывать их. Вот как мы можем определить ее:
def add(x, y):
'Adds two things'
return x + y
В этом коде мы создали функцию с именем add. Она принимает два параметра, x и y, и возвращает их сумму. Строка 'Adds two things' называется строкой документации (docstring), которая используется для описания того, что делает функция.
Использование функции dir() для проверки атрибутов функции
В Python функция dir() является удобным инструментом. Она может быть использована для получения списка всех атрибутов и методов, которые имеет объект. Используем ее, чтобы посмотреть, какие атрибуты имеет наша функция add. Запустите следующий код в интерактивной оболочке Python:
dir(add)
При запуске этого кода вы увидите длинный список атрибутов. Вот пример того, как может выглядеть вывод:
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
Этот список показывает все атрибуты и методы, связанные с функцией add.
Получение базовой информации о функции
Теперь давайте более подробно рассмотрим некоторые базовые атрибуты функции. Эти атрибуты могут сообщить нам важную информацию о функции. Запустите следующий код в интерактивной оболочке Python:
print(add.__name__)
print(add.__module__)
print(add.__doc__)
При запуске этого кода вы увидите следующий вывод:
add
__main__
Adds two things
Понять, что означают каждый из этих атрибутов:
__name__: Этот атрибут дает нам имя функции. В нашем случае функция называетсяadd.__module__: Он сообщает нам модуль, в котором функция определена. Когда мы запускаем код в интерактивной оболочке, модуль обычно__main__.__doc__: Это строка документации (docstring) функции. Она дает краткое описание того, что делает функция.
Изучение кода функции
Атрибут __code__ функции очень интересный. Он содержит информацию о том, как функция реализована, включая ее байт-код и другие детали. Давайте посмотрим, что мы можем узнать из него. Запустите следующий код в интерактивной оболочке Python:
print(add.__code__.co_varnames)
print(add.__code__.co_argcount)
Вывод будет следующим:
('x', 'y')
2
Вот, что эти атрибуты сообщают нам:
co_varnames: Это кортеж, который содержит имена всех локальных переменных, используемых функцией. В нашей функцииaddлокальными переменными являютсяxиy.co_argcount: Этот атрибут сообщает нам количество аргументов, которые ожидает функция. Наша функцияaddожидает два аргумента, поэтому значение равно 2.
Если вы захотите исследовать больше атрибутов объекта __code__, вы можете снова использовать функцию dir(). Запустите следующий код:
dir(add.__code__)
Это отобразит все атрибуты объекта кода, которые содержат низкоуровневые детали о том, как функция реализована.
Использование модуля inspect
В Python стандартная библиотека включает очень полезный модуль inspect. Этот модуль похож на инструмент детектива, который помогает нам собирать информацию о "живых" объектах в Python. "Живые" объекты могут быть, например, модулями, классами и функциями. Вместо того чтобы вручную просматривать атрибуты объекта в поисках информации, модуль inspect предоставляет более организованные и высокоуровневые способы для понимания свойств функций.
Продолжим использовать ту же интерактивную оболочку Python, чтобы исследовать, как работает этот модуль.
Сигнатуры функций
Функция inspect.signature() является удобным инструментом. Когда вы передаете ей функцию, она возвращает объект Signature. Этот объект содержит важные детали о параметрах функции.
Вот пример. Предположим, у нас есть функция с именем add. Мы можем использовать функцию inspect.signature(), чтобы получить ее сигнатуру:
import inspect
sig = inspect.signature(add)
print(sig)
При запуске этого кода вывод будет следующим:
(x, y)
Этот вывод показывает нам сигнатуру функции, которая сообщает, какие параметры функция может принимать.
Изучение деталей параметров
Мы можем пойти дальше и получить более глубокую информацию о каждом параметре функции.
print(sig.parameters)
Вывод этого кода будет:
OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y">)])
Параметры функции хранятся в упорядоченном словаре. Иногда нас могут интересовать только имена параметров. Мы можем преобразовать этот упорядоченный словарь в кортеж, чтобы извлечь только имена параметров.
param_names = tuple(sig.parameters)
print(param_names)
Вывод будет:
('x', 'y')
Изучение отдельных параметров
Мы также можем более подробно рассмотреть каждый отдельный параметр. Следующий код проходит по каждому параметру функции и выводит некоторые важные детали о нем.
for name, param in sig.parameters.items():
print(f"Parameter: {name}")
print(f" Kind: {param.kind}")
print(f" Default: {param.default if param.default is not param.empty else 'No default'}")
Этот код покажет нам детали о каждом параметре. Он сообщает нам тип параметра (является ли он позиционным параметром, параметром - ключевым словом и т.д.) и его значение по умолчанию, если оно есть.
Модуль inspect имеет много других полезных функций для интроспекции функций. Вот несколько примеров:
inspect.getdoc(obj): Эта функция извлекает строку документации для объекта. Строки документации похожи на заметки, которые программисты пишут, чтобы объяснить, что делает объект.inspect.getfile(obj): Она помогает нам узнать, в каком файле объект определен. Это может быть очень полезно, когда мы хотим найти исходный код объекта.inspect.getsource(obj): Эта функция получает исходный код объекта. Она позволяет нам увидеть, как именно объект реализован.
Применение инспекции функций в классах
Теперь мы применим то, что узнали о инспекции функций, чтобы улучшить реализацию класса. Инспекция функций позволяет нам заглянуть внутрь функций и понять их структуру, например, параметры, которые они принимают. В данном случае мы используем это, чтобы сделать наш код класса более эффективным и менее подверженным ошибкам. Мы модифицируем класс Structure так, чтобы он мог автоматически определять имена полей из сигнатуры метода __init__.
Понимание класса Structure
В файле structure.py содержится класс Structure. Этот класс является базовым классом, то есть другие классы могут наследовать от него, чтобы создавать структурированные объекты данных. В настоящее время, чтобы определить атрибуты объектов, созданных из классов, наследующих от Structure, нам нужно установить переменную класса _fields.
Откроем файл в редакторе. Используем следующую команду, чтобы перейти в директорию проекта:
cd ~/project
После выполнения этой команды вы можете найти и просмотреть существующий класс Structure в файле structure.py в WebIDE.
Создание класса Stock
Создадим класс Stock, который наследует от класса Structure. Наследование означает, что класс Stock получит все функции класса Structure и также может добавить свои собственные. Добавим следующий код в конец файла structure.py:
class Stock(Structure):
_fields = ('name', 'shares', 'price')
def __init__(self, name, shares, price):
self._init()
Однако у этого подхода есть проблема. Мы должны определить как кортеж _fields, так и метод __init__ с теми же именами параметров. Это избыточно, так как по сути мы записываем ту же информацию дважды. Если мы забудем обновить одно, когда меняем другое, это может привести к ошибкам.
Добавление методу класса set_fields
Чтобы исправить эту проблему, мы добавим метод класса set_fields в класс Structure. Этот метод автоматически определит имена полей из сигнатуры метода __init__. Вот код, который нужно добавить в класс Structure:
@classmethod
def set_fields(cls):
## Get the signature of the __init__ method
import inspect
sig = inspect.signature(cls.__init__)
## Get parameter names, skipping 'self'
params = list(sig.parameters.keys())[1:]
## Set _fields attribute on the class
cls._fields = tuple(params)
Этот метод использует модуль inspect, который является мощным инструментом в Python для получения информации о таких объектах, как функции и классы. Сначала он получает сигнатуру метода __init__. Затем он извлекает имена параметров, но пропускает параметр self, так как self является специальным параметром в классах Python, который ссылается на сам экземпляр. Наконец, он устанавливает переменную класса _fields с этими именами параметров.
Модификация класса Stock
Теперь, когда у нас есть метод set_fields, мы можем упростить наш класс Stock. Замените предыдущий код класса Stock следующим:
class Stock(Structure):
def __init__(self, name, shares, price):
self._init()
## Call set_fields to automatically set _fields from __init__
Stock.set_fields()
Таким образом, нам не нужно вручную определять кортеж _fields. Метод set_fields сделает это за нас.
Тестирование модифицированного класса
Чтобы убедиться, что наш модифицированный класс работает правильно, создадим простой тестовый скрипт. Создайте новый файл с именем test_structure.py и добавьте следующий код:
from structure import Stock
def test_stock():
## Create a Stock object
s = Stock(name='GOOG', shares=100, price=490.1)
## Test string representation
print(f"Stock representation: {s}")
## Test attribute access
print(f"Name: {s.name}")
print(f"Shares: {s.shares}")
print(f"Price: {s.price}")
## Test attribute modification
s.shares = 50
print(f"Updated shares: {s.shares}")
## Test attribute error
try:
s.share = 50 ## Misspelled attribute
print("Error: Did not raise AttributeError")
except AttributeError as e:
print(f"Correctly raised: {e}")
if __name__ == "__main__":
test_stock()
Этот тестовый скрипт создает объект Stock, тестирует его строковое представление, обращается к его атрибутам, изменяет атрибут и пытается обратиться к атрибуту с опечаткой, чтобы проверить, вызывает ли он правильную ошибку.
Чтобы запустить тестовый скрипт, используйте следующую команду:
python3 test_structure.py
Вы должны увидеть вывод, похожий на следующий:
Stock representation: Stock('GOOG',100,490.1)
Name: GOOG
Shares: 100
Price: 490.1
Updated shares: 50
Correctly raised: No attribute share
Как это работает
- Метод
set_fieldsиспользуетinspect.signature(), чтобы получить имена параметров из метода__init__. Эта функция дает нам подробную информацию о параметрах метода__init__. - Затем он автоматически устанавливает переменную класса
_fieldsна основе этих имен параметров. Таким образом, нам не нужно записывать одни и те же имена параметров в двух разных местах. - Это устраняет необходимость вручную определять как
_fields, так и__init__с совпадающими именами параметров. Это делает наш код более поддерживаемым, так как если мы изменим параметры в методе__init__,_fieldsбудут автоматически обновлены.
Этот подход использует инспекцию функций, чтобы сделать наш код более поддерживаемым и менее подверженным ошибкам. Это практическое применение возможностей интроспекции Python, которые позволяют нам исследовать и модифицировать объекты во время выполнения.
Резюме
В этом практическом занятии (лабораторной работе) вы узнали, как исследовать внутреннее устройство функций Python. Вы напрямую изучали атрибуты функций с использованием методов, таких как dir(), и обращались к специальным атрибутам, таким как __name__, __doc__ и __code__. Вы также использовали модуль inspect для получения структурированной информации о сигнатурах функций и параметрах.
Инспекция функций - это мощная возможность Python, которая позволяет писать более динамичный, гибкий и поддерживаемый код. Возможность изучать и манипулировать свойствами функций во время выполнения предоставляет возможности для метапрограммирования, создания самодокументирующегося кода и построения продвинутых фреймворков.