Готовя на Python : Семь изысканных рецептов для программистов.

Обычно Python прост, но имеется несколько неожиданных исключений.

Отвечает: Робин Парма

Проблема

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

Решение

# try/except, как правило, наилучший подход к решению подобных проблем.

def IsInt(str):
"""Is the given string an integer?"""
try:int(str)
except ValueError:return 0
else:return 1

Обсуждение

Используйте исключения, чтобы выполнять простые тесты, которые в противоположном случае утомительны. Если вы хотите знать, представляет ли содержимое строки целое, просто попробуйте преобразовать ее. Именно это и делает IsInt(). Алгоритм try/except перехватывает исключение, которое возбуждается, когда строка не может быть преобразована в целое, и превращает ее в безвредный возврат 0. Оператор else, который выполняется только тогда, когда ни одно исключение не возбуждается в операторе try, возвращает 1, если со строкой все в порядке.

Не заблуждайтесь относительно слова "исключение" или того, что в других языках считается хорошим стилем программирования. Использование исключений и try/except - полезная идиома Python.

2. Константы в Python

Свободный Python позволит вам присвоить новое значение любой переменной; все же, существует экземпляр, в котором вы можете защитить свои переменные.

Отвечает: Алекс Мартелли

Проблема

Вам необходимо определить переменные на уровне модуля, как, например, "именованные константы" ("named constants"), которым клиентский код не может случайно присвоить новые значения.

Решение

# Требуется Python 2.1 или выше. Вставьте в const.py следующее:

class _Constants:
class ConstError(TypeError): pass
def __setattr__(self, name, value):
if self.__dict__.has_key(name):
raise self.ConstError, "Can't rebind const(%s)"%name
self.__dict__[name ] = value
def __delattr__(self, name):
if self.__dict__.has_key(name):
raise self.ConstError, "Can't unbind const(%s)"%name
raise NameError, name
import sys
sys.modules[__name__] = _Constants()

# now any client-code can:
import const
# and bind an attribute ONCE:
const.magic =23
# but NOT re-bind it:
# const.magic =88 # would raise const.ConstError

Обсуждение

По желанию на Python любой переменной может быть присвоено новое значение. Модули не позволяют вам определять специальные методы, как, например, __setattr__ экземпляра, чтобы предотвратить переприсвоение атрибута. Легкое решение (на Python 2.1 и выше) - использовать экземпляр в качестве "модуля".

Python 2.1 и выше больше не требует, чтобы элементы в sys.modules были объектами модуля. Вы можете установить там объект экземпляра и воспользоваться специальными методами доступа к атрибуту, в то время как клиентский код по-прежнему будет добираться к нему с import somename. Возможно, вам покажется, что это скорее относится к "питоновской" идиоме Singleton (однако, см. также "Singleton? Да не нужен нам этот жалкий Singleton: метод Борга"). Заметьте, что этот "рецепт" обеспечивает постоянное связывание с данным именем, но не неизменяемость объекта, что совершенное другой вопрос.

3. Singleton? Да не нужен нам этот жалкий Singleton: метод Борга (Borg)

Начинающие программисты неизменно спрашивают, как писать Singleton на Python. Страница, созданная Jon Bentley, Martelli, предлагает нам этот великолепный образец программирования на Python.

Отвечает: Алекс Мартелли

Проблема

Вы подумали, что хотите гарантировать, что создается только один экземпляр класса, но затем осознали, что на самом деле вам не столько важно id() экземпляров, сколько их состояние и поведение.

Решение

Класс Борг:

__shared_state = {}
def __init__(self):
self.__dict__ = self.__shared_state
# and whatever else you want in your class-that's all!

Обсуждение

У паттерного проектирования (design pattern, DP) Singleton притягательное имя, но неверный фокус: на индивидуальность, а не состояние. Метод Борга вместо этого предполагает, что все экземпляры разделяют общее состояние, а Python позволяет добиться этого без малейших усилий.

Заметьте, что __getattr__ и __setattr__ не задействованы - они могут быть определены независимо, с какой бы то ни было иной целью вам не захотелось, либо оставлены неопределенными (а сам __setattr__, если он определен, не вызывается для переприсваивания __dict__). Это работает только с "классическими классами" (Python 2.1 или ниже, или для классов, не наследующихся от встроенных типов Python 2.2 или выше), поскольку их экземпляры хранят все их поэкземплярное состояние в классах self__dict. Классы -2.2 с self. __slots__ не поддерживает эту идиому столь же гладко (при желании вы можете использовать функции установки/получения значений (getters/setters) для таких расширенных, неклассических классов, чтобы управиться с этим вопросом, но придерживаться "классических классов" для этих потребностей может быть проще всего).

Паттерное проектирование Singleton в основном гарантирует, что создается только один экземпляр определенного класса. Оно получило притягательное имя (как и общее признание за то, что пытается управиться с часто присутствующими смыслами), и поэтому оно чрезвычайно популярно. Однако, судя по моему опыту, очень часто такое проектирование не лучшее решение для тех смыслов - оно отображает различные типы проблем в различных объектах - моделей. Что мы действительно хотели бы - и это характерно - так это возможность создания стольких экземпляров, сколько необходимо, но всех с общим состоянием. Кого волнует индивидуальность и поведение? Состояние - вот что нас обычно беспокоит. Этот альтернативный Шаблон (Pattern) был назван Моносостоянием (Monostate). В связи с этим я хотел бы назвать Singleton "Горцем" ("Highlander"), поскольку "может быть только один".

Существует множество способов реализации Моносостояния на Python, но подход Борга зачастую лучше всего. Его главная сила - простота. Поскольку self.__dict__ любого экземпляра (на Python 2.1 или ниже, или для классических классов на Python 2.2) может быть присвоено новое значение, просто переприсвойте его в __init__ словарю класса-атрибута - и все! Теперь любая ссылка или присвоение атрибута экземпляра будет действовать одинаково на все экземпляры - "мы все едины", и всё такое прочее. Дэвид Эшер (David Ascher) предложил для этого подхода очень подходящее имя - "Борг". Это не шаблон, поскольку во время опубликования у него не было общеизвестных пользователей. Два или более отдельных известных пользователей - необходимые предварительные условия паттерного проектирования!

Называть этот "рецепт" "Единичный предметом" ("a Singleton") также глупо, как считать навес зонтиком. Они оба могут использоваться с одинаковой целью (вы можете прогуливаться, не мокнув под дождем) - разрешать схожие смыслы, выражаясь языком паттерного проектирования - но поскольку они делают это совершенно по-разному, они не являются экземплярами одного и того же шаблона. Во всяком случае, как упоминалось выше, Борг подобен альтернативному паттерному проектированию Моносостояния Singleton (но Моносостояние - это паттерное проектирование, а Борг им не является, и, в самом деле, Моносостояние Python может просто прекрасно существовать и не являясь Боргом).

По непонятной для меня причине люди часто объединяют проблемы, относящееся к Боргу и "Горцу" (Highlander), с другими вопросами, которые являются ортогональными, как, например, управление доступом и - особенно - доступ из многочисленных нитей. Если вам нужно контролировать обращение к объекту, для этой задачи все равно, имеется ли один или двадцать три экземпляра класса этого объекта, как и то, ли разделяют эти многочисленные экземпляры общее состояние или нет. "Разделяй и властвуй" - вот эффективный подход к решению проблем - чтобы упростить проблемы, "раскладывайте" их на составляющие. Примером же подхода "объединяй и мучайся" является соединение нескольких аспектов, приводящее к усложнению проблем.

4. Параметры функции, связанные с curry

Что за поваренная книга без рецепта приготовления блюда, приправленного карри, острой индийской приправой? Эта распространенная идиома программирования придает вашим программам аромат ясности.

Отвечают: Скотт Дэвид Дэниэлз, Бен Уолфсон, Ник Перкинз, Алекс Мартелли

Проблема

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

Решение

Класс curry:

def __init__(self, fun, *args, **kwargs):
self.fun = fun
self.pending = args[:]
self.kwargs = kwargs.copy()
def __call__(self, *args, **kwargs):
if kwargs and self.kwargs:
kw = self.kwargs.copy()
kw.update(kwargs)
else:
kw = kwargs or self.kwargs
return self.fun(*(self.pending + args), **kw)

Обсуждение

Карринг (currying) - это метод, названый в честь великого математика Хаскелла Карри (Haskell Curry), который позволяет связывать аргументы с функцией и ожидать, пока остальные аргументы не появятся позже. Вы "приправляете" ("curry in") функцию несколькими первыми параметрами, и у вас появляется функция, которая принимает последующие параметры в качестве аргументов, а затем вызывает исходные со всеми этими параметрами. Этот "рецепт" использует экземпляр класса для того, чтобы держать каррированые аргументы, пока они не будут нужны для использования. Например:

double = curry(operator.mul,2)
triple = curry(operator.mul,3)

Карринг часто выполняется с lambda формами, но выделенный класс яснее и более удобочитаем. Типичное использование curry - конструирование функций обратного вызова (callback functions) для операций GUI. Когда операция в действительности не заслуживает нового имени функции, curry может быть полезен при создании таких маленьких функций. Это может быть верно для кнопок Tkinter, например:

self.button=Button(frame,text='A',
command=curry(transcript.append,'A'))

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

Connect = curry(ODBC.Connect, dsn='MyDataSet')
Another example of curry in debugging is wrapping methods:
def report(originalFunction, name, *args, **kw):
print "%s(%s)"%(name, ','.join(map(repr,args) +
[k+'='+repr(kw [k]) for k in kw.keys()] ))
result = originalFunction(*args, **kw)
if result is not None: print name, '==>', result
return result
class Sink:
def write(self, text): pass

dest = Sink()
dest.write = curry(report, dest.write, 'write')
print >>dest, 'this', 'is', 1, 'test'

Если вы создаете функцию для постоянного использования, и есть возможность выбора имени, форма def fun(... определения функции часто более удобочитаема и легко расширяется. Curry следует использовать тогда, когда вы чувствуете, что с ним код будет более ясным, чем без него. Обычно это подчеркивает, что вы предоставляете параметры только в "обычно используемую" (в этом приложении) функцию, а не предоставляете отдельную обработку.

Вы можете передавать конструктор класса, чтобы создать иллюзию производного класса:

BlueWindow =curry(Window,background="blue")

Разумеется, BlueWindow().__class__ по-прежнему Windows, а не производный класс, но если вы меняете только параметры по умолчанию, а не поведение, в любом случае карринг более уместен, чем задание производного класса. Кроме того, вы можете передавать дополнительные параметры в каррированый конструктор.

5. Факториал с lambda

Этот "рецепт" - всего лишь шутка. Он несколько искусный, и мы сомневались, показывать ли его вам, но не смогли удержаться: lambda, рекурсия, трехместный оператор (ternary operator) - все в одной строке, определенно, от этого закружится голова любого преподавателя курса "Основы программирования".

Отвечает: Анураг Уния

Проблема

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

Решение

f = lambda n: n-1 +abs(n-1) and f(n-1)*n or 1

Этот "рецепт" реализует рекурсивное определение функции факториала в виде lambda формы. Поскольку lambda формы должны быть выражениями, это несколько мудрено. Директива if/else не допускается внутри выражения. Все же, закороченная форма идиомы Python для "условного (трехместного) оператора" справляется с этим (другие способы моделирования трехместного оператора, как закороченного, так и нет, см. в "Моделирование трехместного оператора" в "Справочном руководстве по Python").

Реальная проблема, разумеется, состоит в том, что раз сильная сторона lambda в создании безымянных функций, то, как же мы выполняем рекурсию? Именно благодаря этому моменту данного "рецепта" вы однозначно можете выиграть выпивку у своих друзей и знакомых, которые используют Python, но опрометчиво не прочитали от корки до корки это "Справочное руководство по Python". Разумеется, не забудьте сказать, что в условиях пари упоминается только lambda и не говорится, что результирующая функция должны быть оставлена безымянной! Некоторые решат, что вы жульничаете, но мы, "питоновцы", предпочитаем считать себя группой прагматиков.

6. Бросание игральных костей

Мы не уверены, понравится ли этот "рецепт" за его оригинальность, или же мы возненавидим его за его неясность! Он может быть запутанным, но если ваш цикл for только суммирует объекты, подумайте, как упростить этот "рецепт".

Проблема

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

Решение

import random
def dice(num,sides):
    return reduce(lambda x,y,s=sides:x +random.randrange(s),
        range(num+1))+num

Обсуждение

Этот "рецепт" представляет простую, но изящную функцию, позволяющую генерировать случайные числа с помощью симуляции игры в кости. Число костей и число сторон каждой кости - параметры этой функции. Для того, чтобы бросить 4d6 (четыре кости с шестью сторонами) вам потребовалось бы вызвать функцию dice(4,6). Симуляция бросания кости очень хороший способ генерации случайного числа с ожидаемым "биномиальным" профилем. Например, 4d6 будет генерировать кривую (правда, дискретную) вероятности колоколообразной формы со средним 10,5.

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

7. Выполните вызов метода для RPC (удаленного вызова процедуры), основанного на XML

Вам необходимо выполнить вызов метода на сервере RPC, основанного на XML.

Решение

#needs Python 2.2 or xmlrpclib
from xmlrpclib import Server
server = Server("http://www.oreillynet.com/meerkat/xml-rpc/server.php")
class MeerkatQuery:
def __init__(self, search, num_items=5, descriptions=0):
self.search = search
self.num_items = num_items
self.descriptions = descriptions
q = MeerkatQuery("[Pp ]ython")
print server.meerkat.getItems(q)

Обсуждение

RPC, основанный на XML - простой и легкий подход к распределенной обработке библиотеки xmlrpclib, которая позволяет легко писать на Python клиентов RPC на XML и является частью базовой библиотеки Python начиная с Python 2.2, хотя вы можете найти ее для более "древних" версий Python на:

pythonware.com/products/xmlrpc/.

Этот "рецепт" использует сервис Meerkat от O'Reilly, предназначенный для объединения содержимого, как, например, новостей и объявлений о продуктах. А именно, "рецепт" запрашивает у Meerkat пять самых последних элементов, упоминая либо "Python", либо "python". Если вы попробуете это, имейте в виду, что время ответа от Meerkat может быть очень различным в зависимости от качества соединения, времени суток и уровня трафика через Интернет: если требуется много времени для ответа на этот скрип, это вовсе не значит, что вы сделали что-то неправильно - просто вам необходимо набраться терпения!