- 30 ноября 2002 г.
Руководство по интроспекции на Python
Предлагаем вашему вниманию очередную статью нашего нового знакомого Патрика
О'Брайена. В ней речь пойдет об одной очень интересной концепции
программирования - интроспекции, возможности извлекать из самого языка важную и
полезную информацию об объектах программы.
Как изучать объекты Python
Что такое интроспекция?
В повседневной жизни, интроспекция - это проявление самоанализа. Интроспекция основывается на изучении собственных мыслей, ощущений, мотивов и поступков. Великий философ Сократ провел большую часть своей жизни, занимаясь самоанализом и призывая своих сограждан-афинян следовать своему примеру. Он даже утверждал, что для него самого "непроанализированная жизнь не стоит существования" (за более подробной информацией о Сократе см. ссылки, приведенные в Ресурсах).
В случае программирования интроспекция означает возможность изучать что-либо, чтобы определить, что это такое, что оно умеет и что может делать. Интроспекция предоставляет программистам огромные гибкость и контроль. Если вы хоть раз работали с языками программирования, которые поддерживают интроспекцию, вы, возможно, испытываете схожие чувства: "непроанализированный объект не стоит воплощения".
В этой статье рассмотрены интроспективные возможности языка программирования Python. Питоновская поддержка интроспекции пронизывает язык вглубь и вширь. В действительности, Python трудно представить без его интроспективных возможностей. Прочитав эту статью, вы сможете с легкостью "проникнуть в самое сердце" своих объектов Python.
Мы начнем наше изучение Питоновской интроспекции в самом общем виде, в каком только возможно, а затем окунемся в более сложные технологии. Возможно, кто-нибудь даже заявит, что характеристики, которые являются нашей отправной точкой, не заслуживают звания "интроспективных". Согласимся, что, попадают ли они или нет под определение интроспекции, остается спорным моментом. Цель этой статьи и наша единственная задача - найти ответы на интересующие вопросы.
Давайте приступим к нашему исследованию, используя Python интерактивно. При
запуске Python из командной строки мы входим в оболочку Python, где можем
вводить код Python и получать немедленный ответ от интерпретатора Python.
(Команды, перечисленные в этой статье, выполнятся надлежащим образом для Python
2.2.2. Возможно, вы получите другие результаты и ошибки, если используете более
раннюю версию. Последнюю версию Python можно скачать с Web-сайта Python (см.
Ресурсы).)
Листинг 1. Запускаем интерпретатор Python в интерактивном
режиме
$ python
Python 2.2.2 (#1, Oct 28 2002, 17:22:19)
[GCC 3.2 (Mandrake Linux 9.0 3.2-1mdk)] on linux2
Type "help", "copyright", "credits" or "license" for more information.[1]
>>>
Запустив Python и посмотрев на его приглашение (>>>), вы, возможно, зададитесь вопросом, какие слова распознает Python. В большинстве языков программирования существуют зарезервированные, или ключевые слова, которые имеют специальное назначение в данном языке, и Python не является исключением. Возможно, вы также обратили внимание, что Python предлагает напечатать help для получения более подробной информации. Наверное, мы можем запросить у Python какую-нибудь справку о ключевых словах.
Интерактивная справочная утилита Python
Как предлагалось выше, давайте напечатаем help и посмотрим,
получим ли мы какую-нибудь информацию о ключевых словах:
Листинг 2. Запрашиваем у Python справочную информацию
>>> help
Type help() for interactive help, or help(object) for help about object.[2]
Поскольку мы не знаем, какой объект может содержать ключевые слова, давайте попробуем ввести help(), не указывая какой-то особый объект:
Листинг 3. Запускаем справочную утилиту
>>> help()
Welcome to Python 2.2! This is the online help utility.
If this is your first time using Python, you should definitely check out
the tutorial on the Internet at http://www.python.org/doc/tut/.
Enter the name of any module, keyword, or topic to get help on writing
Python programs and using Python modules. To quit this help utility and
return to the interpreter, just type "quit".
To get a list of available modules, keywords, or topics, type "modules",
"keywords", or "topics". Each module also comes with a one-line summary
of what it does; to list the modules whose summaries contain a given word
such as "spam", type "modules spam".[3]
help>
Похоже, что мы немного продвинулись. Давайте введем keywords в
приглашении справки:
Листинг 4. Запрашиваем справку о keywords
help> keywords
Here is a list of the Python keywords. Enter any keyword to get more help.[4a]
and elif global or
assert else if pass
break except import print
class exec in raise
continue finally is return
def for lambda try
del from not while
help> quit
You are now leaving help and returning to the Python interpreter.
If you want to ask for help on a particular object directly from the
interpreter, you can type "help(object)". Executing "help('string')"
has the same effect as typing a particular string at the help> prompt.[4b]
>>>
Когда мы напечатали help(), мы увидели приветствие и некоторые указания, а затем приглашение справки. В приглашении мы ввели keywords и получили список ключевых слов Python. Получив ответ на свой вопрос, мы вышли из справочной утилиты, увидев короткое прощальное сообщение, и вернулись к приглашению Python.
Как видно из этого примера, интерактивная справочная утилита Python отображает информацию на различные темы или об отдельном объекте. Эта справочная утилита довольно удобна и действительно использует интроспективные возможности Python. Однако буквальное использование справки не показывает, как она получает информацию. А поскольку задача этой статьи - раскрытие всех интроспективных секретов Python, нам необходимо незамедлительно выйти за рамки справочной утилиты.
Прежде чем выйти из справки, давайте воспользуемся ею, чтобы получить список
доступных модулей. Модули - это просто текстовые файлы, которые содержат код
Python и имена которых заканчиваются на .py. Если мы напечатаем в
приглашении Python help('modules') или введем modules в
приглашении справки, мы получим длинный список доступных модулей, который похож
на неполный список, приведенный ниже. Попытайтесь сами установить, какие модули
доступны на вашей системе, и понять, почему считается, что Python поставляется
"вместе с батарейками".
Листинг 5. Получаем неполный список доступных модулей
>>> help('modules')
Please wait a moment while I gather a list of all available modules...[5a]
BaseHTTPServer cgitb marshal sndhdr
Bastion chunk math socket
CDROM cmath md5 sre
CGIHTTPServer cmd mhlib sre_compile
Canvas code mimetools sre_constants
<...>
bisect macpath signal xreadlines
cPickle macurl2path site xxsubtype
cStringIO mailbox slgc (package) zipfile
calendar mailcap smtpd
cgi markupbase smtplib
Enter any module name to get more help. Or, type "modules spam" to search
for modules whose descriptions contain the word "spam".[5b]
>>>
Модуль sys
Один из модулей, предоставляющих внутреннюю информацию о самом Python, - это
модуль sys. Вы используете модуль, импортируя его и ссылаясь на его
содержимое (как, например, переменные, функции и классы) с помощью нотации
точка (.). Модуль sys содержит множество переменных и функций,
которые предоставляют интересную и подробную информацию о текущем
интерпретаторе Python. Давайте рассмотрим некоторые из них. И снова мы
собираемся, запустив Python интерактивно, вводить команды в приглашении Python.
Первое, что мы сделаем - это импортируем модуль sys. Затем введем
переменную sys.executable, которая содержит путь к интерпретатору
Python:
Листинг 6. Импортируем модуль sys
$ python
Python 2.2.2 (#1, Oct 28 2002, 17:22:19)
[GCC 3.2 (Mandrake Linux 9.0 3.2-1mdk)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.executable
'/usr/local/bin/python'
Если мы введем строку кода, который не содержит ничего кроме имени объекта, Python ответит, показывая представление этого объекта, которое - для простых объектов - как правило, есть значение этого объекта. В этом случае, поскольку выведенное значение заключено в кавычки, мы можем предположить, что sys.executable, вероятно, строковый объект. Позже мы изучим другие, более точные, способы определения типа объекта, однако просто ввод имени объекта в приглашении Python - это быстрый и легкий вид интроспекции.
Давайте рассмотрим некоторые другие полезные атрибуты модуля sys.
Переменная platform сообщает, в какой операционной системе мы
работаем:
Атрибут sys.platform
>>> sys.platform
'linux2'
Текущая версия Python доступна и в виде строки, и в виде кортежа (кортеж
содержит последовательность объектов):
Листинг 8. Атрибуты sys.version и sys.version_info
>>> sys.version
'2.2.2 (#1, Oct 28 2002, 17:22:19) \n[GCC 3.2 (Mandrake Linux 9.0 3.2-1mdk)]'
>>> sys.version_info
(2, 2, 2, 'final', 0)
Переменная maxint показывает наибольшее допустимое целое
значение:
Атрибут sys.maxint
>>> sys.maxint
2147483647
Переменная argv - это список, содержащий параметры командной
строки, если она была задана. Первый элемент, argv[0], это путь к
скрипту, который был запущен. Когда мы работаем с Python интерактивно, его
значением является пустая строка:
Листинг 10. Атрибут sys.argv
>>> sys.argv
['']
Если мы запустим другую оболочку Python, как, например, PyCrust (за более
подробной информацией о PyCrust см. ссылку, приведенную в
Ресурсах), то увидим что-нибудь вроде этого:
Листинг 11. Атрибут sys.argv при использовании PyCrust
>>> sys.argv[0]
'/home/pobrien/Code/PyCrust/PyCrustApp.py'
Переменная path - это путь поиска модуля, список каталогов, в
которых Python будет искать модули во время импорта. Пустая строка, ' ', в
первой позиции относится к текущему каталогу:
Листинг 12. Атрибут path
>>> sys.path
['', '/home/pobrien/Code',
'/usr/local/lib/python2.2',
'/usr/local/lib/python2.2/plat-linux2',
'/usr/local/lib/python2.2/lib-tk',
'/usr/local/lib/python2.2/lib-dynload',
'/usr/local/lib/python2.2/site-packages']
Переменная modules - это словарь, который отображает имена
модулей в объекты модулей для всех загруженных в текущий момент модулей. Как
можно видеть, Python загружает определенные модули по умолчанию:
Листинг 13. Атрибут sys.modules
>>> sys.modules
{'stat': <module 'stat' from '/usr/local/lib/python2.2/stat.pyc'>,
'__future__': <module '__future__' from '/usr/local/lib/python2.2/__future__.pyc'>,
'copy_reg': <module 'copy_reg' from '/usr/local/lib/python2.2/copy_reg.pyc'>,
'posixpath': <module 'posixpath' from '/usr/local/lib/python2.2/posixpath.pyc'>,
'UserDict': <module 'UserDict' from '/usr/local/lib/python2.2/UserDict.pyc'>,
'signal': <module 'signal' (built-in)>,
'site': <module 'site' from '/usr/local/lib/python2.2/site.pyc'>,
'__builtin__': <module '__builtin__' (built-in)>,
'sys': <module 'sys' (built-in)>,
'posix': <module 'posix' (built-in)>,
'types': <module 'types' from '/usr/local/lib/python2.2/types.pyc'>,
'__main__': <module '__main__' (built-in)>,
'exceptions': <module 'exceptions' (built-in)>,
'os': <module 'os' from '/usr/local/lib/python2.2/os.pyc'>,
'os.path': <module 'posixpath' from '/usr/local/lib/python2.2/posixpath.pyc'>}
Модуль keyword
Давайте вернемся к нашему вопросу о ключевых словах Python. Даже несмотря на
то, что справка предоставила нам список ключевых слов, оказывается, что часть
информации справки жестко закодирована. Список ключевых слов, получается,
жестко закодирован, что в конце концов не слишком интроспективно. Давайте
посмотрим, сможем ли мы получить такую информацию непосредственно из одного из
модулей в стандартной библиотеке Python. Если мы напечатаем help('modules
keywords') в приглашении Python, то увидим следующее:
Листинг 14. Запрашиваем справку о модулях с ключевыми
словами
>>> help('modules keywords')
Here is a list of matching modules. Enter any module name to get more help.[14]
keyword - Keywords (from "graminit.c")
Похоже, что модуль keyword содержит ключевые слова. Открыв файл
keyword.py в текстовом редакторе, можно увидеть, что Python
действительно создает список ключевых слов, явно доступных в виде атрибута
kwlist модуля keyword. В модуле keyword также
приводятся комментарии о том, что этот модуль генерируется автоматически на
основе исходного кода самого Python, гарантируя точность и полноту списка
ключевых слов:
Листинг 15. Список ключевых слов модуля keyword
>>> import keyword
>>> keyword.kwlist
['and', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else',
'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is',
'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'yield']
Функция dir()
Несмотря на то, что относительно легко найти и импортировать модуль, не так-то просто запомнить, что каждый модуль содержит. И вряд ли вам захочется всякий раз смотреть исходный код, чтобы это выяснить. К счастью, Python предоставляет способ определения содержимого модулей (и других объектов) с помощью встроенной функции dir().
Функция dir(), вероятно, наиболее известная из всех
интроспективных механизмов Python. Она возвращает отсортированный список имен
атрибутов для любого переданного в нее объекта. Если ни один объект не указан,
dir() возвращает имена в текущей области видимости. Давайте применим
dir() к нашему модулю keyword и посмотрим, что она
покажет:
Листинг 16. Атрибуты модуля keyword
>>> dir(keyword)
['__all__', '__builtins__', '__doc__', '__file__', '__name__',
'iskeyword', 'keyword', 'kwdict', 'kwlist', 'main']
А как насчет модуля sys, который мы рассматривали выше?
Листинг 17. Атрибуты модуля sys
>>> dir(sys)
['__displayhook__', '__doc__', '__excepthook__', '__name__', '__stderr__',
'__stdin__', '__stdout__', '_getframe', 'argv', 'builtin_module_names',
'byteorder', 'copyright', 'displayhook', 'exc_info', 'exc_type', 'excepthook',
'exec_prefix', 'executable', 'exit', 'getdefaultencoding', 'getdlopenflags',
'getrecursionlimit', 'getrefcount', 'hexversion', 'last_traceback',
'last_type', 'last_value', 'maxint', 'maxunicode', 'modules', 'path',
'platform', 'prefix', 'ps1', 'ps2', 'setcheckinterval', 'setdlopenflags',
'setprofile', 'setrecursionlimit', 'settrace', 'stderr', 'stdin', 'stdout',
'version', 'version_info', 'warnoptions']
Без указания аргумента dir() возвращает имена в текущей области
видимости. Заметьте, что keyword и sys присутствуют в
этом списке, поскольку мы импортировали их ранее. Импортирование модуля
добавляет имя этого модуля в текущую область видимости:
Листинг 18. Имена в текущей области
>>> dir()
['__builtins__', '__doc__', '__name__', 'keyword', 'sys']
Мы упоминали, что функция dir() является встроенной функцией, что
означает, что нам не нужно импортировать модуль, чтобы использовать эту
функцию. Python распознает встроенные функции без нашего участия. И теперь мы
видим это имя, __builtins__, возращенное обращением к
dir(). Возможно, здесь имеется какая-то связь. Давайте введем имя
__builtins__ в приглашение Python и посмотрим, скажет ли нам Python
о нем что-нибудь интересное:
Листинг 19. Что такое __builtins__?
>>> __builtins__
<module '__builtin__' (built-in)>
Итак, __builtins__, похоже, является в текущей области именем,
связанным с объектом модуля по имени __builtins__. (Поскольку модули
- это не простые объекты с одиночными значениями, Python показывает информацию
об этом модуле внутри угловых скобок.) Заметьте, что, если вы будете искать
файл __builtin__.py на диске, то вы ничего не найдете. Этот особый
объект модуля создается интерпретатором Python из ниоткуда, так как он содержит
элементы, которые всегда доступны интерпретатору. И, несмотря на то, что
физически файла не существует, мы все же можем применить нашу функцию
dir() к этому объекту, чтобы увидеть все встроенные функции, объекты
ошибок и несколько различных атрибутов, которые он содержит:
Листинг 20. Атрибуты модуля __builtins__
>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'DeprecationWarning',
'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False',
'FloatingPointError', 'IOError', 'ImportError', 'IndentationError',
'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError',
'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError',
'OverflowError', 'OverflowWarning', 'ReferenceError', 'RuntimeError',
'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError',
'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'TypeError',
'UnboundLocalError', 'UnicodeError', 'UserWarning', 'ValueError', 'Warning',
'ZeroDivisionError', '_', '__debug__', '__doc__', '__import__', '__name__',
'abs', 'apply', 'bool', 'buffer', 'callable', 'chr', 'classmethod', 'cmp',
'coerce', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict',
'dir', 'divmod', 'eval', 'execfile', 'exit', 'file', 'filter', 'float',
'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int',
'intern', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list',
'locals', 'long', 'map', 'max', 'min', 'object', 'oct', 'open', 'ord', 'pow',
'property', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'round',
'setattr', 'slice', 'staticmethod', 'str', 'super', 'tuple', 'type', 'unichr',
'unicode', 'vars', 'xrange', 'zip']
Функция dir() работает со всеми типами объектов, включая строки,
целые числа, списки, кортежи, словари, функции, экземпляры и методы классов и
классы, определенные пользователем. Давайте применим dir() к
строковому объекту и посмотрим, что возвратит Python. Как вы можете видеть,
даже простая строка Python имеет ряд атрибутов:
Листинг 21. Атрибуты строки
>>> dir('this is a string')
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__',
'__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__',
'__hash__', '__init__', '__le__', '__len__', '__lt__', '__mul__', '__ne__',
'__new__', '__reduce__', '__repr__', '__rmul__', '__setattr__', '__str__',
'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs',
'find', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace',
'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'replace', 'rfind',
'rindex', 'rjust', 'rstrip', 'split', 'splitlines', 'startswith', 'strip',
'swapcase', 'title', 'translate', 'upper', 'zfill']
Поэкспериментируйте со следующими примерами, чтобы выяснить, что они
возвратят. Заметьте, что символ # обозначает начало комментария. Все
от начала комментария и до конца строки игнорируется Python:
Листинг 22. Применяем dir() к другим объектам
dir(42) # Integer (and the meaning of life)
dir([]) # List (an empty list, actually)
dir(()) # Tuple (also empty)
dir({}) # Dictionary (ditto)
dir(dir) # Function (functions are also objects)[22]
Чтобы продемонстрировать динамическую сущность интроспективных возможностей
Python, давайте рассмотрим некоторые примеры, применяя dir() к
классу, определенному пользователем, и нескольким экземплярам класса. Определим
наш собственный класс интерактивно, создадим несколько экземпляров этого
класса, и, добавив уникальный атрибут только к одному из этих экземпляров,
посмотрим, сможет ли Python со всем этим разобраться. Ниже приведены
результаты:
Листинг 23. Применяем dir() к классам, определенным пользователем,
экземплярам класса и атрибутам
>>> class Person(object):
... """Person class."""
... def __init__(self, name, age):
... self.name = name
... self.age = age
... def intro(self):
... """Return an introduction."""
... return "Hello, my name is %s and I'm %s." % (self.name, self.age)
...
>>> bob = Person("Robert", 35) # Создать экземпляр Person
>>> joe = Person("Joseph", 17) # Создать еще один экземпляр
>>> joe.sport = "football" # Присвоить одному экземпляру новый атрибут
>>> dir(Person) # Атрибуты класса Person
['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__',
'__hash__', '__init__', '__module__', '__new__', '__reduce__', '__repr__',
'__setattr__', '__str__', '__weakref__', 'intro']
>>> dir(bob) # Атрибуты bob
['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__',
'__hash__', '__init__', '__module__', '__new__', '__reduce__', '__repr__',
'__setattr__', '__str__', '__weakref__', 'age', 'intro', 'name']
>>> dir(joe) # Заметьте, что joe имеет дополнительный атрибут
['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__',
'__hash__', '__init__', '__module__', '__new__', '__reduce__', '__repr__',
'__setattr__', '__str__', '__weakref__', 'age', 'intro', 'name', 'sport']
>>> bob.intro() # Вызов метода intro экземпляра bob
"Hello, my name is Robert and I'm 35."
>>> dir(bob.intro) # Атрибуты метода intro
['__call__', '__class__', '__cmp__', '__delattr__', '__doc__', '__get__',
'__getattribute__', '__hash__', '__init__', '__new__', '__reduce__',
'__repr__', '__setattr__', '__str__', 'im_class', 'im_func', 'im_self']
Строки документации
Возможно, вы заметили в наших многочисленных примерах с dir()
один атрибут среди множества других - __doc__. Этот атрибут -
строка, которая содержит комментарии, описывающие объект. Python называет ее
строкой документации, или docstring, и вот, как она работает. Если первая
директива определения модуля, класса, метода или функции - строка, то эта
строка оказывается связанной с объектом в качестве его атрибута
__doc__. Посмотрите, например, на строку документации для объекта
__builtins__. Воспользуемся Питоновской директивой print,
чтобы выходные данные было легче читать, поскольку строки документации часто
содержат встроенные разделители строк (\n):
Листинг 24. Строка документации модуля
>>> print __builtins__.__doc__ # Модуль докстроки
Built-in functions, exceptions, and other objects.
Noteworthy: None is the `nil' object; Ellipsis represents `...' in slices.[24]
И вновь, Python поддерживает даже строки документации для классов и методов,
которые определены интерактивно в оболочке Python. Давайте рассмотрим строки
документации для нашего класса Person и его метода
intro:
Листинг 25. Строки документации класса и метода
>>> Person.__doc__ # Докстрока класса
'Person class.'
>>> Person.intro.__doc__ # Докстрока метода класса
'Return an introduction.'
Поскольку строки документации предоставляют такую ценную информацию, многие
среды разработки Python включают способы автоматического отображения строк
документации для объектов. Давайте рассмотрим еще одну строку документации -
для функции dir():
Листинг 26. Строка документации функции
>>> print dir.__doc__ # Function docstring
dir([object]) -> list of strings
Return an alphabetized list of names comprising (some of) the attributes
of the given object, and of attributes reachable from it:
No argument: the names in the current scope.
Module object: the module attributes.
Type or class object: its attributes, and recursively the attributes of
its bases.
Otherwise: its attributes, its class's attributes, and recursively the
attributes of its class's base classes.[26]
Опрос объектов Python
Мы упоминали слово "объект" уже несколько раз, но так и не дали ему определения. Объект в среде программирования во многом подобен объекту реального мира. Реальный объект имеет определенную форму, размер, вес и другие характеристики. Реальный объект может реагировать на свое окружение, взаимодействовать с другими объектами или выполнять задачу. Компьютерные объекты пытаются моделировать объекты, которые окружают нас в реальном мире, включая абстрактные объекты, как, например, документы, каталоги и бизнес-процессы.
Как и объекты реального мира, некоторые компьютерные объекты могут обладать общими характеристиками, допуская при этом небольшие вариации. Представьте книги, которые продаются в книжном магазине. У любого экземпляра книги может не хватать нескольких страниц, или они могут быть испачканы, и каждая копия книги может включать уникальный идентификационный номер. И, хотя каждая книга -уникальный объект, каждая книга с одним и тем же названием -лишь экземпляр оригинального шаблона; она сохраняет большинство характеристик оригинала.
Сказанное справедливо и в отношении объектно-ориентированных классов и экземпляров классов. Например, каждая строка Python обладает атрибутами, которые, как мы видели, были показаны функцией dir(). В приведенном выше примере мы определили наш собственный класс Person, который действует как шаблон для создания отдельных экземпляров Python, у каждого из которых свои собственные значения имени и возраста, но общая для всех возможность представиться. Это и есть объектно-ориентированное программирование.
На языке программирования объекты, следовательно - это нечто, что имеет тождественность и значение, а также определенный тип, обладает некими характеристиками и определенным поведением. Объекты наследуют многие из своих атрибутов от одного или более родительских классов. Кроме ключевых слов и специальных символов (подобных таким операторам, как +, -, *, **, /, %, <, > и т. д.) все на Python является объектом. Python выпускается с широким набором типов объектов: строки, целые числа, числа с плавающей точкой, списки, кортежи, словари, функции, классы, экземпляры классов, модули, файлы и т.п.
Если у вас произвольный объект, возможно, тот, который был передан в качестве аргумента в функцию, вы, наверное, захотите что-нибудь узнать об этом объекте. В этом разделе мы рассмотрим, как объекты Python отвечают на следующие вопросы:
- Какое у тебя имя?
- Что ты за объект?
- Что ты знаешь?
- Что ты можешь?
- Кто твои предки?
Имя
Не все объекты имеют имя, но у тех, у которых оно есть, имя хранится в их
атрибуте __name__. Заметьте, что имя выводится из объекта, а не из
переменной, которая указывает на этот объект. Следующий пример подчеркивает это
различие:
Листинг 27. Что скрыто в имени
$ python
Python 2.2.2 (#1, Oct 28 2002, 17:22:19)
[GCC 3.2 (Mandrake Linux 9.0 3.2-1mdk)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> dir() # Функция dir()
['__builtins__', '__doc__', '__name__']
>>> directory = dir # Создать новую переменную
>>> directory() # Работает просто как первоначальный объект
['__builtins__', '__doc__', '__name__', 'directory']
>>> dir.__name__ # Как тебя зовут?
'dir'
>>> directory.__name__ # У меня такое же имя
'dir'
>>> __name__ # А теперь о чем-нибудь совершенно другом
'__main__'
Модули имеют имена, а сам интерпретатор Python считается модулем верхнего
уровня, или основным модулем. Когда вы запускаете Python интерактивно,
локальной переменной __name__ присваивается значение
'__main__'. Подобным образом, когда вы выполняете модуль Python из
командной строки, а не импортируете его в другой модуль, его атрибуту
__name__ присваивается значение '__main__', а не
действительное имя этого модуля. Так модули могут взглянуть на свое значение
__name__, чтобы определиться, используются ли они в качестве
поддержки для другой программы или как основное приложение, выполняемое из
командной строки. Следующая идиома весьма распространена в модулях Python:
Листинг 28. Определяем: выполнение или импорт
if __name__ == '__main__':
# Сделать здесь что-нибудь уместное, наподобие вызова
# функции main(), определенной где-то в этом модуле.
main()
else:
# Ничего не делать. Этот модуль был импортирован другим
# модулем, который хочет воспользоваться этой функцией,
# классом или другими полезными битами, которые он определил.
Функция type() помогает нам определить, является ли объект
строкой, целым числом или другим видом объекта. Для этого она возвращает объект
типа, который можно сравнивать с типами, определенными в модуле
types:
Листинг 29. Не твоего ли я типа?
>>> import types
>>> print types.__doc__
Define names for all type symbols known in the standard interpreter.
Types that are part of optional modules (e.g. array) are not listed.[29]
>>> dir(types)
['BufferType', 'BuiltinFunctionType', 'BuiltinMethodType', 'ClassType',
'CodeType', 'ComplexType', 'DictProxyType', 'DictType', 'DictionaryType',
'EllipsisType', 'FileType', 'FloatType', 'FrameType', 'FunctionType',
'GeneratorType', 'InstanceType', 'IntType', 'LambdaType', 'ListType',
'LongType', 'MethodType', 'ModuleType', 'NoneType', 'ObjectType', 'SliceType',
'StringType', 'StringTypes', 'TracebackType', 'TupleType', 'TypeType',
'UnboundMethodType', 'UnicodeType', 'XRangeType', '__builtins__', '__doc__',
'__file__', '__name__']
>>> s = 'a sample string'
>>> type(s)
<type 'str'>
>>> if type(s) is types.StringType: print "s is a string"
...
s is a string
>>> type(42)
<type 'int'>
>>> type([])
<type 'list'>
>>> type({})
<type 'dict'>
>>> type(dir)
<type 'builtin_function_or_method'>
Тождественность
Как было указано выше, каждый объект имеет тождественность, тип и значение.
Важно заметить, что на один и тот же объект может указывать более одной
переменной; с другой стороны, переменные могут ссылаться на объекты, которые
выглядят похожими (у них одинаковый тип и значение), но нетождественны. Понятие
тождественности объекта приобретает особо важное значение при внесении
изменений в объект, таких как добавление элемента в список, что показано в
приведенном ниже примере, в котором переменные blist и
clist указывают на один и тот же объект списка. Как вы можете
видеть, функция id() возвращает уникальный идентификатор для любого
заданного объекта:
Листинг 30. "Рубеж"
>>> print id.__doc__
id(object) -> integer
Return the identity of an object. This is guaranteed to be unique among
simultaneously existing objects. (Hint: it's the object's memory address.)[30]
>>> alist = [1, 2, 3]
>>> blist = [1, 2, 3]
>>> clist = blist
>>> clist
[1, 2, 3]
>>> blist
[1, 2, 3]
>>> alist
[1, 2, 3]
>>> id(alist)
145381412
>>> id(blist)
140406428
>>> id(clist)
140406428
>>> alist is blist # Возвращает 1 если True, 0 если False
0
>>> blist is clist # Аналогично
1
>>> clist.append(4) # Добавить элемент в конец списка
>>> clist
[1, 2, 3, 4]
>>> blist # То же самое, поскольку они обе указывают на один и тот же объект
[1, 2, 3, 4]
>>> alist # А этот исходно только выглядел таким же [1, 2, 3]
[1, 2, 3]
Атрибуты
Мы видели, что объекты имеют атрибуты, а функция dir() возвращает
список этих атрибутов. Иногда, однако, мы просто хотим определить наличие
одного или более атрибутов. И если у объекта есть интересующий нас атрибут, мы
часто хотим извлечь этот атрибут. Эти задачи решаются с помощью функций
hasattr() и getattr(), как показано в следующем
примере:
Листинг 31. Наличие атрибута; получение атрибута
>>> print hasattr.__doc__
hasattr(object, name) -> Boolean
Return whether the object has an attribute with the given name.
(This is done by calling getattr(object, name) and catching exceptions.)[31a]
>>> print getattr.__doc__
getattr(object, name[, default]) -> value
Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y.
When a default argument is given, it is returned when the attribute doesn't
exist; without it, an exception is raised in that case.[31b]
>>> hasattr(id, '__doc__')
1
>>> print getattr(id, '__doc__')
id(object) -> integer
Return the identity of an object. This is guaranteed to be unique among
simultaneously existing objects. (Hint: it's the object's memory address.)[31c]
Вызываемые структуры
Объекты, которые представляют потенциальное поведение (функции и методы),
могут быть запущены, или вызваны. С помощью функции callable() мы
можем установить, вызываемый ли это объект:
Листинг 32. Можешь что-нибудь для меня сделать?
>>> print callable.__doc__
callable(object) -> Boolean
Return whether the object is callable (i.e., some kind of function).
Note that classes are callable, as are instances with a __call__() method.[32]
>>> callable('a string')
0
>>> callable(dir)
1
Экземпляры
Хотя функция type() и выдает тип объекта, с помощью функции isinstance() мы также можем выяснить, является ли объект экземпляром определенного типа или определенного пользователем класса:
Листинг 33. Ты один из них?
>>> print isinstance.__doc__
isinstance(object, class-or-type-or-tuple) -> Boolean
Return whether an object is an instance of a class or of a subclass thereof.
With a type as second argument, return whether that is the object's type.
The form using a tuple, isinstance(x, (A, B, ...)), is a shortcut for
isinstance(x, A) or isinstance(x, B) or ... (etc.).[33]
>>> isinstance(42, str)
0
>>> isinstance('a string', int)
0
>>> isinstance(42, int)
1
>>> isinstance('a string', str)
1
Производные классы
Как было указано выше, экземпляры класса, определенного пользователем,
наследуют свои атрибуты от этого класса. На уровне класса, класс может быть
определен в терминах другого класса и будет схожим образом наследовать атрибуты
в иерархической форме. Python даже поддерживает множественное наследование, что
означает, что отдельный класс может быть определен в терминах более чем одного
- и наследоваться более чем от одного - родительского класса. Функция
issubclass() позволяет установить, наследуется ли один класс от
другого:
Листинг 34. Не ты ли мой предок?
>>> print issubclass.__doc__
issubclass(C, B) -> Boolean
Return whether class C is a subclass (i.e., a derived class) of class B.[34]
>>> class SuperHero(Person): # SuperHero наследуется из Person...
... def intro(self): # но с новым SuperHero intro
... """Return an introduction."""
... return "Hello, I'm SuperHero %s and I'm %s." % (self.name, self.age)
...
>>> issubclass(SuperHero, Person)
1
>>> issubclass(Person, SuperHero)
0
>>>
Время опроса
Давайте подведем итог, собрав вместе некоторые из интроспективных
технологий, которые мы рассмотрели в последнем разделе. Для этого определим
нашу собственную функцию interrogate(), которая выводит различную информацию о
любом объекте, передаваемом в нее. Ниже приведен код, который сопровождается
несколькими примерами его применения:
Листинг 35. Никто и не надеялся
>>> def interrogate(item):
... """Print useful information about item."""
... if hasattr(item, '__name__'):
... print "NAME: ", item.__name__
... if hasattr(item, '__class__'):
... print "CLASS: ", item.__class__.__name__
... print "ID: ", id(item)
... print "TYPE: ", type(item)
... print "VALUE: ", repr(item)
... print "CALLABLE:",
... if callable(item):
... print "Yes"
... else:
... print "No"
... if hasattr(item, '__doc__'):
... doc = getattr(item, '__doc__')
... doc = doc.strip() # Remove leading/trailing whitespace.
... firstline = doc.split('\n')[0]
... print "DOC: ", firstline
...
>>> interrogate('a string') # Строка
CLASS: str
ID: 141462040
TYPE: <type 'str'>
VALUE: 'a string'
CALLABLE: No
DOC: str(object) -> string
>>> interrogate(42) # Целое
CLASS: int
ID: 135447416
TYPE: <type 'int'>
VALUE: 42
CALLABLE: No
DOC: int(x[, base]) -> integer
>>> interrogate(interrogate) # Функция, определенная пользователем
NAME: interrogate
CLASS: function
ID: 141444892
TYPE: <type 'function'>
VALUE: <function interrogate at 0x86e471c>
CALLABLE: Yes
DOC: Print useful information about item.
Как следует из последнего примера, наша функция interrogate() работает даже сама с собой. Большей ретроспективности вы вряд ли сможете получить.
Заключение
Кто бы мог подумать, что интроспекция окажется столь простой и полезной? И все же, заканчивая, я должен предостеречь: не примите результаты интроспекции за знание в последней инстанции. Опытный программист Python знает, что всегда есть больше, чем дает интроспекция, и, следовательно, ее исход вовсе не исчерпывающее знание. Программирование порождает больше вопросов, чем ответов. Что исключительно хорошо в Python, как мы поняли из этой статьи, так это то, что он действительно отвечает на ваши вопросы. Что до меня, не считайте себя обязанными отплатить мне за то, что я помог вам понять, что предлагает Python. Программирование на Python само по себе награда. Все, что я прошу у своих друзей-Питонистов, это угощения за счет общественности.
Ресурсы
- Web-сайт Python - отправная точки для всего Питоновского, включая официальную информацию по Python.
- Конференция по Python, comp.lang.python, - это великолепный источник вопросов и ответов.
- На Web-сайте Orbtech находится список дополнительных ресурсов по Python.
- PyCrust, исключительно интроспективная оболочка Python, доступна на SourceForge.
- Wikipedia предоставляет краткую информацию о Сократе. Там вы также можете прочитать об "Суде над Сократом".
- Интервью об использовании Python и Perl с IBM DB2 - "Верблюд и змея или "Обмани пророчество": разработка с открытым исходным кодом с Python, Perl и IBM DB2" ("The Camel and the Snake, or 'Cheat the Prophet': Open Source Development with Perl, Python, and DB2").
- Ресурсы для разработчиков Linux в зоне Linux developerWorks.
Об авторе
Патрик О'Брайен - программист Python, консультант и преподаватель. Он автор PyCrust и разработчик проекта PythonCard. Совсем недавно Патрик руководил группой PyPerSyst, которая переносила Prevayler на Python. Сейчас он продолжает вести этот проект, но для новой интересной области. За более подробной информацией о Патрике и его работе обращайтесь на Web-сайт Orbtech или пишите ему на pobrien@orbtech.com.
[1] Чтобы получить более подробную информацию, напечатайте "help", "copyright", "credits" или "license".
[2] Напечатайте help() для получения интерактивной справки или help(object) для справки об объекте.
[3] Добро пожаловать в Python 2.2! Это интерактивная справочная утилита.
Если вы используете Python впервые, вам обязательно следует познакомиться с учебным пособием, доступным в Интернете по адресу: http://www.python.org/doc/tut/.
Введите имя любого модуля, ключевое слово или тему, чтобы получить справку о написании программ Python и использовании его модулей. Чтобы выйти из этой утилиты и вернуться в интерпретатор, напечатайте "quit".
Чтобы получить список доступных модулей, ключевых слов или тем, напечатайте "modules", "keywords" или "topics". Каждый модуль сопровождается краткой - не более одной строки - информацией о том, что он делает; чтобы получить список модулей, информация о которых содержит заданное слово, как, например, "spam", напечатайте "modules spam".
[4a] Ниже приведен список ключевых слов Python. Для получения более подробной справки введите любое ключевое слово.
[4b] Вы вышли из справочной утилиты и вернулись в интерпретатор Python. Если вам необходимо получить справку об отдельном объекте непосредственно из интерпретатора, напечатайте "help(object)". Выполнение "help('string')" имеет такой же результат, как ввод отдельной строки в приглашении справки.
[5a] Пожалуйста, подождите немного, пока я подготовлю список всех доступных модулей...
[5b] Чтобы получить более подробную справку, введите имя любого модуля. Или напечатайте "modules spam", чтобы найти модули, в описании которых присутствует слово " spam".
[14] Ниже приведен список соответствующих модулей. Чтобы получить более подробную информацию, введите имя любого модуля.
[22] dir(42) # Целое число (и смысл жизни)
dir([]) # Список (пустой список на самом деле)
dir(()) # Кортеж (также пустой)
dir({}) # Словарь (аналогично)
dir(dir) # Функция (функции также объекты)
[24] Встроенные функции, исключения и другие объекты.
Заметьте: None - объект 'nil'; Пропуск в срезах представляется как '... '.
[26] Возвращает список имен в алфавитном порядке, включающий (некоторые из) атрибутов заданного объекта, и атрибутов, которые из него доступны:
Без аргумента: имена в текущей области.
Объект модуля: атрибуты модуля.
Объект типа или класса: его атрибуты и рекурсивно атрибуты его баз.
Иначе: атрибуты объекта, атрибуты его класса и рекурсивно атрибуты базовых классов его класса.
[29] Определяет имена для всех типов, известных стандартному интерпретатору.
Типы, которые являются частью факультативных модулей (например, array) ниже не перечислены.
[30] Вернуть тождественность объекта. Она гарантировано уникальна среди одновременно существующих объектов. (Намек: это адрес памяти объекта.)
[31a] Вернуть, есть ли у объекта атрибут с заданным именем.
(Это выполняется посредством вызова getattr(object, name) и перехватыванием исключения.)
[31b] Получить упомянутый атрибут из объекта; getattr(x, 'y') эквивалентно x.y.
Если задается аргумент по умолчанию, он возвращается, если атрибут не существует; без этого аргумента в этом случае возбуждается исключение.
[31c] Вернуть тождественность объекта. Она гарантировано уникальна среди одновременно существующих объектов. (Намек: это адрес памяти объекта.)
[32] Вернуть, является ли объект вызываемым (например, какой-нибудь вид функции).
Заметьте, что классы являются вызываемыми, как и экземпляры с методом __call__().
[33] Вернуть, является ли объект экземпляром заданного класса или его подкласса.
Если тип в качестве второго аргумента, вывести, является ли это типом объекта.
Форма, использующая кортеж, isinstance(x, (A, B, ...)), - это сокращение для isinstance(x, A) or isinstance(x, B) or ... (и т. д.)
[34] Вернуть, является ли класс С производным классом класса В.
Автор: Патрик О'Брайен (Patrick O'Brien), программист Python, Orbtech