Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
 iakovlev.org 
 Books
  Краткое описание
 Linux
 W. R. Стивенс TCP 
 W. R. Стивенс IPC 
 A.Rubini-J.Corbet 
 K. Bauer 
 Gary V. Vaughan 
 Д Вилер 
 В. Сталлинг 
 Pramode C.E. 
 Steve Pate 
 William Gropp 
 K.A.Robbins 
 С Бекман 
 Р Стивенс 
 Ethereal 
 Cluster 
 Languages
 C
 Perl
 M.Pilgrim 
 А.Фролов 
 Mendel Cooper 
 М Перри 
 Kernel
 C.S. Rodriguez 
 Robert Love 
 Daniel Bovet 
 Д Джеф 
 Максвелл 
 G. Kroah-Hartman 
 B. Hansen 
NEWS
Последние статьи :
  Тренажёр 16.01   
  Эльбрус 05.12   
  Алгоритмы 12.04   
  Rust 07.11   
  Go 25.12   
  EXT4 10.11   
  FS benchmark 15.09   
  Сетунь 23.07   
  Trees 25.06   
  Apache 03.02   
 
TOP 20
 Linux Kernel 2.6...5170 
 Trees...940 
 Максвелл 3...871 
 Go Web ...823 
 William Gropp...803 
 Ethreal 3...787 
 Gary V.Vaughan-> Libtool...773 
 Ethreal 4...771 
 Rodriguez 6...765 
 Ext4 FS...755 
 Clickhouse...754 
 Steve Pate 1...754 
 Ethreal 1...742 
 Secure Programming for Li...732 
 C++ Patterns 3...716 
 Ulrich Drepper...697 
 Assembler...695 
 DevFS...662 
 Стивенс 9...649 
 MySQL & PosgreSQL...632 
 
  01.01.2024 : 3621733 посещений 

iakovlev.org
 

Вглубь языка Python - часть 2

2.1. В глубь

Эта глава описывает одну из самых сильных возможностей языка Python — самоанализ. Ка вы уже знаете, все в языке Python является объектами. Самоанализ — использование специального кода для просмотра в памяти модулей и функций как объектов, извлекая информацию о них и о том, как их использовать. По ходу мы будем определять функции без имени, передавать аргументы в неправильном порядке и использовать функции, имена которых до этого даже не знали.

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

Пример 2.1. apihelper.py


 def help(object, spacing=10, collapse=1): 1 2 3
     """Выводит методы и строки документации.
     
     В качестве аргумента может использоваться модуль, класс, список, словарь
     или строка."""
     methodList = [method for method in dir(object) if callable(getattr(object, method))]
     processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s)
     print "\n".join(["%s %s" %
                       (method.ljust(spacing),
                        processFunc(str(getattr(object, method).__doc__)))
                      for method in methodList])
 
 if __name__ == "__main__":                4 5
     print help.__doc__
1 Этот модуль определяет одну функцию — help. Она воспринимает три аргумента: object, spacing и collapse. Последние два аргумента, как мы скоро увидим, не являются обязательными.
2 Функция help имеет многострочную строку документации, кратко описывающую предназначение функции. Обратите внимание, что здесь не упомянуто возвращаемое значение — функция предназначениа для выполнения определенных действий а не возврата значения.
3 Тело функции выделено отступами.
4 Прием с if __name__ позволяет использовать модуль в качестве программы без каких-либо конфликтов. В данном случае программа просто выводит строку документации функции help.
5 В инструкции if для сравнения используется оператор ==. Заключать в скобки выражение совсем не обязательно.

Функция help предназначена для использования программистом при работе в Python IDE. В качестве аргумента можно использовать любой объект, имеющий функции (например, модуль) или методы (например, список). Эта функция выводит имена функций (или метододов) объекта и их строки документации.

Пример 2.2. Использование apihelper.py

>>> from apihelper import help
 >>> li = []
 >>> help(li)
 append     L.append(object) -- append object to end
 count      L.count(value) -> integer -- return number of occurrences of value
 extend     L.extend(list) -- extend list by appending list elements
 index      L.index(value) -> integer -- return index of first occurrence of value
 insert     L.insert(index, object) -- insert object before index
 pop        L.pop([index]) -> item -- remove and return item at index (default last)
 remove     L.remove(value) -- remove first occurrence of value
 reverse    L.reverse() -- reverse *IN PLACE*
 sort       L.sort([cmpfunc]) -- sort *IN PLACE*; if given, cmpfunc(x, y) -> -1, 0, 1

По умолчанию информация выводится в легко воспринимаемом виде. Многострочные строки документации преобразуются в одну длинную строку, но это особенность можно отключить, передав 0 в качестве аргумента collapse. Кроме того, если имена функций содержат 10 символов, вы можете установить большее значение аргумента spacing.

Пример 2.3. Дополнительные возможности apihelper.py

>>> import odbchelper
 >>> help(odbchelper)
 buildConnectionString Build a connection string from a dictionary Returns string.
 >>> help(odbchelper, 30)
 buildConnectionString          Build a connection string from a dictionary Returns string.
 >>> help(odbchelper, 30, 0)
 buildConnectionString          Build a connection string from a dictionary
 Returns string.
2.2. Необязательные и именованные аргументы

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

Пример 2.4. help — функция с двумя необязательными аргументами


 def help(object, spacing=10, collapse=1):

Аргументы spacing и collapse могут быть опущены при вызове, так как для них определены значения по умолчанию. Аргумент object не имеет значения по умолчанию, поэтому должен быть указан всегда. Если функция help вызывается только с одним аргументом, то spacing принимает значение 10 и collapse принимает значение 1. Если help вызывается с двумя аргументами, collapse также принимает значение 1.

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

Пример 2.5. Возможные способы вызова help

help(odbchelper)                    1
 help(odbchelper, 12)                2
 help(odbchelper, collapse=0)        3
 help(spacing=15, object=odbchelper) 4
1 С одним аргументом, spacing и collapse получают значения по умолчанию, 10 и 1 соответственно.
2 С двумя аргументами, collapse получает значение по умолчанию 1.
3 Здесь вы явно указываете имя аргумента, collapse, для которого передается значение. Аргумент spacing по-прежнему получает значение по умолчанию 10.
4 Даже обязательные аргументы (в данном случае — object), которые не имеют значения по умолчанию, могут быть переданы по имени, и именованные аргументы могут быть указаны в произвольном порядке.

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

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

Дополнительная литература

2.3. type, str, dir и другие встроенные функции

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

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

Пример 2.6. Функция type

>>> type(1)           1
 <type 'int'>
 >>> li = []
 >>> type(li)          2
 <type 'list'>
 >>> import odbchelper
 >>> type(odbchelper)  3
 <type 'module'>
 >>> import types      4
 >>> type(odbchelper) == types.ModuleType
 1
1 Функция type воспринимает объект произвольного типа и возвращает его тип. Аргумент действительно может быть произвольного типа: число, строка, список, словарь, кортеж, функция, класс, модуль и даже сам тип.
2 В качестве аргумента может использоваться переменная.
3 Функция type работает и c модулей.
4 Вы можете использовать константы, определенные в модуле types для сравнения типов объектов. Именно это и делает, как мы скоро увидим, функция help.

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

Пример 2.7. Функция str

>>> str(1)          1
 '1'
 >>> horsemen = ['war', 'pestilence', 'famine']
 >>> horsemen.append('Powerbuilder')
 >>> str(horsemen)   2
 "['war', 'pestilence', 'famine', 'Powerbuilder']"
 >>> str(odbchelper) 3
 "<module 'odbchelper' from 'c:\\docbook\\dip\\py\\odbchelper.py'>"
 >>> str(None)       4
 'None'
1 Вы, наверное, ожидали, что str будет работать с такими простыми типами данных, как целые числа, так как почти каждый язык содержит функцию для преобразования целого числа в строку.
2 Однако, str работает и с объектами других типов. В данном случае мы получаем строковое представление списка.
3 Функция str работает и с модулями. Обратите внимание, что строковое представление модуля содержит путь к модулю на диске, так что в вашем случае он будет отличаться.
4 Важно также, что str работает и с объектом None, “нулевым” значением в языке Python. В этом случае она возвращает строку 'None'. Мы воспользуемся этим в функции help.

Сердцем функции help является мощная функция dir. dir возвращает список атрибутов и методов произвольного объекта: модуля, функции, строки, списка, словаря… в общем, любого объекта.

Пример 2.8. Функция dir

>>> li = []
 >>> dir(li)           1
 ['append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
 >>> d = {}
 >>> dir(d)            2
 ['clear', 'copy', 'get', 'has_key', 'items', 'keys', 'setdefault', 'update', 'values']
 >>> import odbchelper
 >>> dir(odbchelper)   3
 ['__builtins__', '__doc__', '__file__', '__name__', 'buildConnectionString']
1 li является списком, так что dir(li) дает список имен всех методов списка. Обратите внимание, что возвращаемый список содержит имена методов в виде строк, а не сами методы.
2 d является словарем, поэтому dir(d) дает список имен методов словаря. Как минимум один из них, keys, вам уже знаком.
3 Здесь начинается самое интересное. odbchelper является модулем, так что dir(odbchelper) дает список всех имен, определенных в модуле, включая специальные атрибуты, такие как __name__ и __doc__. В данном случае, odbchelper содержит одну пользовательскую функцию, buildConnectionString, которую мы изучали в главе Знакомство с языком Python.

Наконец, функция callable возвращает 1, если аргумент может быть вызван, в противном случае возвращает 0. Вызов поддерживают такие объекты, как функции, методы и даже классы. (Более подробно о классах читайте в главе 3.)

Пример 2.9. Функция callable

>>> import string
 >>> string.punctuation           1
 '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
 >>> string.join                  2
 <function join at 00C55A7C>
 >>> callable(string.punctuation) 3
 0
 >>> callable(string.join)        4
 1
 >>> print string.join.__doc__    5
 join(list [,sep]) -> string
 
     Return a string composed of the words in list, with
     intervening occurrences of sep.  The default separator is a
     single space.
 
     (joinfields and join are synonymous)
1 На смену большинству функций модуля string пришли методов строк (хотя многие до сих пор используют функцию join), но модуль также содержит множество полезных констант. Например string.punctuation содержит все стандартные символы пунктуации.
2 Функция string.join объединяет строки списка в одну строку.
3 string.punctuation является строкой и вызов не поддерживает. (Строки имеют методы, которые можно вызвать, но вы не можете вызвать саму строку.)
4 string.join ялвляется функцией и поддерживает вызов с двумя аргументами.
5 Любой объект, поддерживающий вызов, может иметь строку документации. Применяя функцию callable к каждому атрибуту объекта, мы можем определить, какие атрибуты представляют для нас интерес (методы, функции, классы), и какие можно игнорировать (константы и т. д.), не имея каких-либо начальных знаний об объекте.

type, str, dir и другие втроенные функции языка Python сгруппированы в специальном модуле __builtin__ (обратите внимание на два символа подчеркивания в начале и два в конце). Встроенные функции доступны напрямую, как если бы интерпретатор при запуске автоматически выполнял from __builtin__ import *.

Вы можете получить информацию о встроенных функциях (и других объектах) как об отдельной группе исследуя модуль __builtin__. Для этих целей у нас есть функция help. Попробуйте проделать это сейчас сами. Позже мы более подробно рассмотрим наиболее важные встроенные функции (некоторые классы исключений, например AttributeError, вам должны быть уже знакомы.)

Пример 2.10. Втроенные функции и другие объекты

>>> from apihelper import help
 >>> import __builtin__
 >>> help(__builtin__, 20)
 ArithmeticError      Base class for arithmetic errors.
 AssertionError       Assertion failed.
 AttributeError       Attribute not found.
 EOFError             Read beyond end of file.
 EnvironmentError     Base class for I/O related errors.
 Exception            Common base class for all exceptions.
 FloatingPointError   Floating point operation failed.
 IOError              I/O operation failed.
 
 [... и т. д. ...]
2.4. Получение атрибутов с помощью getattr

Вы уже знаете, что функции в языке Python являются объектами. Но вы пока не знаете, что если имя функции становится известно только во время выполнения программы, то ее можно получить с помощью функции getattr.

Пример 2.11. Функция getattr

>>> li = ["Larry", "Curly"]
 >>> li.pop                       1
 <built-in method pop of list object at 010DF884>
 >>> getattr(li, "pop")           2
 <built-in method pop of list object at 010DF884>
 >>> getattr(li, "append")("Moe") 3
 >>> li
 ["Larry", "Curly", "Moe"]
 >>> getattr({}, "clear")         4
 <built-in method clear of dictionary object at 00F113D4>
 >>> getattr((), "pop")           5
 Traceback (innermost last):
   File "<interactive input>", line 1, in ?
 AttributeError: 'tuple' object has no attribute 'pop'
Замечание
Python распространяется вместе с прекрасной документацией. Вам следует внимательно прочитать, чтобы заиметь представления о предоставляемых им возможностях. Но, в то время как при использовании большинства других языков вам необходимо все время возвращать к чтению документации (страниц man или, не дай Бог, MSDN), чтобы вспомнить, как использовать тот или иной модуль, Python в основном содержит эту документацию в себе.
1 Таким образом вы получаете метод pop списка. Обратите внимание, что вы не вызываете его (для этого нужно выполнить li.pop()), а получаете в виде объекта.
2 Здесь мы также получаем метод pop, но теперь имя метода указано в виде строкового аргумента функции getattr. getattr — очень полезная функция, позволяющая получить любой атрибут любого объекта. В данном случае объектом является список, а его атрибутом — метод pop.
3 В случае, если вы еще не до конца осознали, насколько это может быть полезным, попробуйте выполнить этот код: значение, возвращаемое функцией getattr является методом, который можно вызвать, как если бы вы просто вызвали li.append("Moe"). Но вы не вызываете метод напрямую — вы указываете имя метода в виде строки.
4 getattr работает и для словарей.
5 Теоретически, getattr работает и для кортежей, но кортежи не имеют методов, так что getattr сгенерирует исключение независимо от имени атрибута, которое вы дадите.

getattr предназначен не только для встроенных типов данных. Он также работает и для модулей.

Пример 2.12. getattr в apihelper.py

>>> import odbchelper
 >>> odbchelper.buildConnectionString             1
 <function buildConnectionString at 00D18DD4>
 >>> getattr(odbchelper, "buildConnectionString") 2
 <function buildConnectionString at 00D18DD4>
 >>> object = odbchelper
 >>> method = "buildConnectionString"
 >>> getattr(object, method)                      3
 <function buildConnectionString at 00D18DD4>
 >>> type(getattr(object, method))                4
 <type 'function'>
 >>> import types
 >>> type(getattr(object, method)) == types.FunctionType
 1
 >>> callable(getattr(object, method))            5
 1
1 Так мы можем получить функцию buildConnectionString из модуля odbchelper, который сы изучали в разделе Знакомство с языком Python. (Шестнадцатиричный адрес характерен для моей машины, у вас он будет другим.)
2 Используя getattr, мы можем получить ту же самую функцию. В общем, getattr(object, "attribute") дает такой же результат, как object.attribute. Если object является модулем, attribute может быть любым объектом, определенном в этом модуле: функцией, классом или другой глобальной переменной.
3 На самом деле именно это и делает функция help. object передается функции в качестве аргумента; method является строкой с именем метода или функции.
4 В данном случае, method содержит имя функции, в чем мы можем убедиться получив ее тип.
5 Так как method является функцией, ее можно вызвать.
2.5. Фильтрование списков

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

Пример 2.13. Синтаксис фильтрования списков

[mapping-expression for element in source-list if filter-expression]

Это дополнительная возможность в расширенной записи списков, которую вы обязательно полюбите. Начало расширенной записи остается прежним, а в конце, начиная с if, добавляется условие, по которому будет производиться фильтрование. Условие может быть любым выражением, которое дает истину или ложь (в Python это может быть практически любым выражением). Любой элемент, для которого условие дает истину, будет участвовать в отображении. Все остальные элементы игнорируются, то есть в выражение отображения не подставляются и в результат не включаются.

Пример 2.14. Введение в фильтрование списков

>>> li = ["a", "mpilgrim", "foo", "b", "c", "b", "d", "d"]
 >>> [elem for elem in li if len(elem) > 1]       1
 ['mpilgrim', 'foo']
 >>> [elem for elem in li if elem != "b"]         2
 ['a', 'mpilgrim', 'foo', 'c', 'd', 'd']
 >>> [elem for elem in li if li.count(elem) == 1] 3
 ['a', 'mpilgrim', 'foo', 'c']
1 В данном случае выражение отображения совсем простое (значение каждого элемента), так что сосредоточьтесь на условии фильтра. Каждый элемент пробегаемого списка Python пропускает через фильтр. Если условие фильтра дает истину, элемент участвует в преобразовании и результат включается в возвращаемый список. В данном случае мы исключили все строки длиной в один символ.
2 Здесь мы исключаем элементы с одним определенным значением, "b". Обратите внимание, что фильтром отбрасываются все элементы со значением "b", так как во всех случаях выражение условия будет давать ложь.
3 Метод count списка возвращает количество вхождений элементов определенного значения в список. Вы можете подумать, что этот фильтр исключает все дубликаты, и возвращаемый список будет содержать по одному значению исходного списка. Но это не так, потому что значения, входящие в исходный список дважды (в данном случае это, "b" and "d") полностью исключаются. Существует множество способов исключить дубликаты из списка, но не так.

Пример 2.15. Фильтрование списка в apihelper.py

    methodList = [method for method in dir(object) if callable(getattr(object, method))]

Этот пример выглядит сложным, но основная структура остается прежней. Все выражение дает список, который присваивается переменной methodList. Выражение отображения простое: оно дает значение каждого элемента. Функция dir(object) возвращает список всех атрибутов и методов объекта object — это тот список, который мы преобразуем. И единственная новая часть — это условие фильтра после if.

Выражение фильтра выглядит жутко, но он таковым не является. Вы уже знаете о callable, getattr и in. Как вы могли видеть в предыдущем разделе, выражение getattr(object, method) дает объект-функцию, если object является модулем и method содержит имя функции из этого модуля.

Таким образом, мы берем объект object, получаем список имен его атрибутов, методов, функций, и затем фильтруем этот список, избавляясь от всего, что нас не интересует. Для того, чтобы избавиться от ненужного, мы берем имя каждого атрибута/метода/функции, с помощью функции getattr получаем настоящий объект. Затем мы проверяем, поддерживает ли объект вызов, таким образом подхватывая все функции и методы — как встроенные (например, метод pop списков), так и определенные пользователем (например, функция buildConnectionString в модуле odbchelper). Нас не интересуют другие атрибуты, такие как обязательный для всех моделей атрибут __name__.

Дополнительная литература

2.6. Особенности операторов and и or

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

Пример 2.16. Оператор and

>>> 'a' and 'b'         1
 'b'
 >>> '' and 'b'          2
 ''
 >>> 'a' and 'b' and 'c' 3
 'c'
1 При использовании оператора and, значения вычисляются в булевом контексте слева напрво. Значения 0, '', [], (), {} и None являются ложью, все остальное является истиной[3]. Если у and оба операнда являются истиной, результатом будет последнее значение. В данном случае вычисляется выражение 'a', которое является истиной, затем 'b', которое также является истиной, и возвращается 'b'.
2 Если какой-либо из операндов является ложью, результатом будет первое такое значение. В данном случает '' — первое значение, являющееся ложью.
3 Все значения являются истиной, так что в результате мы получаем последнее — 'c'.

Пример 2.17. Оператор or

>>> 'a' or 'b'          1
 'a'
 >>> '' or 'b'           2
 'b'
 >>> '' or [] or {}      3
 {}
 >>> def sidefx():
 ...     print "in sidefx()"
 ...     return 1
 >>> 'a' or sidefx()     4
 'a'
1 Как и для and опреранды or вычисляются в булевском контексте слева направо. Если операнд является истиной, or немедленно возвращает результат. В данном случае 'a' — первое истинное значение.
2 or вычисляет выражение '', которое является ложью, затем 'b', которое является истиной, и возвращает 'b'.
3 Если все значения являются ложью, or возвращает последнее. or вычисляет '' (ложь), [] (ложь), затем {} (ложь) и возвращает {}.
4 Обратите внимание, что or вычисляет операнды до тех пор, пока не найдет истинное значение, остальное игнорируется. Это имеет значение, когда вычисление операнда дает сторонние эффекты. В данном случае функция sidefx не вызывается, так как для получения результата выражения с опереатором or достаточно того, что первый операнд, 'a', является истиной.

Если вы используете C, то, наверное, знакомы с выражением bool ? a : b, которое дает a, если bool является истиной, b, если bool ложно. Благодаря особенностям работы операторов and и or в Python, вы можете достигнуть анологичного эффекта.

Пример 2.18. Прием с and-or

>>> a = "first"
 >>> b = "second"
 >>> 1 and a or b 1
 'first'
 >>> 0 and a or b 2
 'second'
 
1 Синтаксис bool ? a : b в языке Python выглядит похоже. Все выражение вычисляется слева направо, так что оператор and применяется первым. 1 and 'first' дает 'first', затем 'first' or 'second' дает 'first'.
2 0 and 'first' дает 0, тогда 0 or 'second' дает 'second'.

Однако, так как такое выражение выполняет обычные логические операции, а не является специальной конструкцией языка, существует очень важная разница между приемом с and-or в языке Python и конструкцией bool ? a : b в C. Если значение a является ложью, это прием не будет работать так как вы могли ожидать. (Вы можете сказать, что обожгись на этом? Более одного раза?)

Пример 2.19. Когда прием с and-or не работает

>>> a = ""
 >>> b = "second"
 >>> 1 and a or b 1
 'second'
1 Так как a — пустая строка, которую Python считает ложью в булевом контексте, 1 and '' дает '', а '' or 'second' дает 'second'. Ой! Это не то, что мы хотели получить.
Важно
Прием с and-or, bool and a or b, не будет работать так же, как конструкция bool ? a : b в C, если a является ложью.

Для безопасного использования приема с and-or необходимо сделать так, чтобы a всегда было истинным. Один из самых распространенных способо это сделать — заменить a на [a] и b на [b], тогда первый элемент получаемого списка будет либо a, либо b.

Пример 2.20. Безопасное использование приема с and-or

>>> a = ""
 >>> b = "second"
 >>> (1 and [a] or [b])[0] 1
 ''
1 Так как выражение [a] является непустым списком, оно никогда не будет ложно. Даже если a равно 0, '' или другому значению, являющемуся ложью, список [a] всегда будет являться истиной, потому что содержит один элемент.

Кажется, этот прием не стоит того, чтобы его использовать. В конце концов вы всегда можете воспользоваться интсрукцией if. Тогда зачем вся эта суета? Конечно, в большинстве случаев выбирая между двумя константами вы можете использовать простой синтаксис и не беспокоиться, потому что значение a всегда будет истинным. И даже если придется использовать более сложный синтакс, на это могут найтись весские причины: в некоторых случаях в языке Python использование инструкции if не допускается, например в lambda-функциях.

Дополнительная литература

Footnotes

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

2.7. Использование lambda-функций

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

Пример 2.21. lambda-функции

>>> def f(x):
 ...     return x*2
 ...     
 >>> f(3)
 6
 >>> g = lambda x: x*2  1
 >>> g(3)
 6
 >>> (lambda x: x*2)(3) 2
 6
1 Эта lambda-функция делает то же самое, что и обычная функция, определенная выше. Обратите внимание на сокращенный синтаксис: список аргументов записывается без скобок и ключевое слово return отсутствует (оно подразумевается, так как тело функции может содержать только одно выражение). Кроме того, функция не имеет имени, но может быть вызвана через переменную, которой она присвоена.
2 Вы можете использовать lambda-функцию даже не присваивая ее переменной. Это не самый полезный пример, но он показывает, что lambda-функция может быть определена прямо в месте ее использования.

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

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

Пример 2.22. lambda-функции в apihelper.py

    processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s)

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

Во-вторых, мы используем метод split без аргументов. Вы уже видели его использование с одним и двумя аргументами, без аргументов метод split разбивает строку по символам пропуска (пробел, табуляция, возврат коретки, переход на новую строку).

Пример 2.23. split без аргументов

>>> s = "this   is\na\ttest"  1
 >>> print s
 this   is
 a	test
 >>> print s.split()           2
 ['this', 'is', 'a', 'test']
 >>> print " ".join(s.split()) 3
 'this is a test'
1 Это строка, которая содержит символ переход на новую строку, записанный в виде специальной последовательности (такие строки могут быть также записаны с использованием утроенных кавычек). \n — переход на новую строку, \t — символ горизонтальной табуляции.
2 Метод split без аргументов разбивает строку по символам пропуска. В данном случае три пробела, переход на новую строку и табуляция воспринимаются одинаково.
3 Вы можете нормализовать пропуски разбив строку, а затем снова объединив ее, используя один пробул в качестве разделителя. Именно это делает функция help для того, чтобы свернуть строку документации.

Так что же на самом деле делает функция help с этими lambda-функциями, методами split и приемом с and-or?

Пример 2.24. Присваивание функции переменной

    processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s)

processFunc теперь ссылается на функцию, но на какую — это зависит от значения переменной collapse. Если collapse является истиной, processFunc(string) будет сворачивать символы пропуска, в противном случае processFunc(string) будет возвращать аргумент без изменений.

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

Дополнительная литература

2.8. Объединяем все вместе

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

Пример 2.25. Основная часть apihelper.py

    print "\n".join(["%s %s" %
                       (method.ljust(spacing),
                        processFunc(str(getattr(object, method).__doc__)))
                      for method in methodList])

Обратите внимание, что это одна команда разбитая на несколько строк без явного использования признака продолжения (“\”). Помните, я говорил, что некоторые выражения могут быть записаны в несколько строк без использования обратного слэша? Расширенная запись списков — один из таких случаев, так как все выражение заключено в квадратные скобки.

Давайте разберем его с конца. Фраза


 for method in methodList

говорит о том, ято мы имеем дело с расширенной записью списков. Как вы уже знаете, methodList является списком всех интересующих нас методов объекта object. Таким образом мы пробегаем по списку методов и каждый метод доступен через переменную method.

Пример 2.26. Динамическое извлечение строки документации

>>> import odbchelper
 >>> object = odbchelper                   1
 >>> method = 'buildConnectionString'      2
 >>> getattr(object, method)               3
 <function buildConnectionString at 010D6D74>
 >>> print getattr(object, method).__doc__ 4
 Создает и возвращает строку соединения из словаря параметров.
1 В функцию help object — объект, для которого мы хотим получить помощь — передается в качестве аргумента.
2 По ходу того, как мы пробегаем по списку methodList, method содержит имя текущего метода.
3 С помощью функции getattr мы получаем сам метод method объекта object.
4 Теперь осталось самое простое — распечатать строку документации метода.

Следующий кирпичик — использование функции str. Как вы уже знаете, str — встроенная функция, перобразующая объект к строке. Но строка документации уже является строкой, так зачем же суетиться с использованием функции str? На самом деле не каждая функция имеет строку документации, и если строки документации нет, ее атрибут __doc__ равен None.

Пример 2.27. Зачем использовать str для строк документации?

>>> {}.keys.__doc__         1
 >>> {}.keys.__doc__ == None 2
 1
 >>> str({}.keys.__doc__)    3
 'None'
 
1 Метод keys словаря не имеет строки документации, так что его атрибут __doc__ равен None. Если вы набираете выражение для атрибута __doc__ непосредственно в интерактивном режиме, интерпретатор Python ничего не выводит, что может сбить с толку (такое поведение интерпретатора действительно удобно, если впомнить о процедурах, которые в языке Python возвращают None).
2 Вы можете убедиться, что атрибут __doc__ действительно равен None простым сравнением.
3 Функция str возвращает строковое представление объекта, в данном случае 'None'.
Замечание
В SQL вы должны использовать IS NULL вместо = NULL для сравнения с пустым значением. В языке Python вы можете использовать как == None, так и is None, но второй вариант работает быстрее.

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

Далее мы снова используем форматирование, чтобы соединить значение, возвращаемое функцией processFunc, со результатом применения метода ljust к строке method. Это новый метод строковых объектов, который мы раньше не видели.

Пример 2.28. Метод ljust

>>> s = 'buildConnectionString'
 >>> s.ljust(30) 1
 'buildConnectionString         '
 >>> s.ljust(20) 2
 'buildConnectionString'
1 ljust дополняет строку пробелами до указанной длины. Именно это делает функция help при выводе в две колонки для выравнивания строк документации во второй колонке.
2 Если требуемая длина меньше, чем исходная длина строки, ljust вернет строку без изменений. Этот метод никогда не обрезает строку.

Ну вот и почти все сделано. Имея метод ljust, дополняющий имя метода пробелами до нужной длины, и строку документации (возможно со свернутыми символами пропуска), которую возвращает processFunc, мы объединяем их и получаем одну строку. Так как мы пробегаем по списку methodList, то получим список строк. Используя метод join строки "\n", мы объединяем строки в одну с символом перехода на новую строку в качестве разделителя и печатаем результат.

Пример 2.29. Печать списка

>>> li = ['a', 'b', 'c']
 >>> print "\n".join(li) 1
 a
 b
 c
1 Этот прием также будет будет полезен при отладке для вывода списков. А в языке Python вы будете работать со списками постоянно.

Теперь код обрел смысл.

Пример 2.30. Основная часть apihelper.py, пересмотренная

    print "\n".join(["%s %s" %
                       (method.ljust(spacing),
                        processFunc(str(getattr(object, method).__doc__)))
                      for method in methodList])
2.9. Заключение

Теперь код программы apihelper.py обрел смысл.

Пример 2.31. apihelper.py


 def help(object, spacing=10, collapse=1):
     """Выводит методы и строки документации.
     
     В качестве аргумента может использоваться модуль, класс, список, словарь
     или строка."""
     methodList = [method for method in dir(object) if callable(getattr(object, method))]
     processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s)
     print "\n".join(["%s %s" %
                       (method.ljust(spacing),
                        processFunc(str(getattr(object, method).__doc__)))
                      for method in methodList])
 
 if __name__ == "__main__":
     print help.__doc__

Пример 2.32. Вывод apihelper.py

>>> from apihelper import help
 >>> li = []
 >>> help(li)
 append     L.append(object) -- append object to end
 count      L.count(value) -> integer -- return number of occurrences of value
 extend     L.extend(list) -- extend list by appending list elements
 index      L.index(value) -> integer -- return index of first occurrence of value
 insert     L.insert(index, object) -- insert object before index
 pop        L.pop([index]) -> item -- remove and return item at index (default last)
 remove     L.remove(value) -- remove first occurrence of value
 reverse    L.reverse() -- reverse *IN PLACE*
 sort       L.sort([cmpfunc]) -- sort *IN PLACE*; if given, cmpfunc(x, y) -> -1, 0, 1

Перед тем как перейти к следующей главе убедитесь, что вы овладели следующими навыками:

Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье