Примеры программ на языке Python: различия между версиями

Материал из Викиучебника — открытых книг для открытого мира
Содержимое удалено Содержимое добавлено
{{Темы|Python (язык программирования)}}
Строка 352: Строка 352:
</source>
</source>


{{Темы|Python (язык программирования)}}
[[Категория:Python]]

Версия от 16:25, 18 мая 2015

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

Нахождение 10 наиболее частых слов на web странице

Данный пример чисто демонстрационный, так как его можно значительно улучшить.

from urllib2 import urlopen         # из модуля urllib2 импортируем функцию urlopen

u = urlopen("http://python.org")    # открываем URL на чтение
words = {}                          # связываем имя words с пустым словарём
                                    # (словарь — неупорядоченный [[ассоциативный массив]])
for line in u:          # читаем u по строкам
    line = line.strip(" \n")    # отбрасываем начальные и конечные пробелы
    for word in line.split(" "): # режем каждую строку на слова, ограниченные пробелами
        try:                            # блок обработки исключений
            words[word] += 1            # пытаемся увеличить words[word] на единицу
        except KeyError:                # если не получилось (раньше words[word] не было)
            words[word] = 1             # присваиваем единицу

# теперь словарь words содержит частоту встречаемости каждого слова.
# Например, words может содержать {"яблоко":5, "апельсин": 12, "груша": 8}

pairs = words.items()               # делаем из словаря список пар
                                    # pairs == [("яблоко",5), ("апельсин",12), ("груша",8)]
pairs.sort(key=lambda x: x[1], reverse=True)  # сортируем по убыванию второго элемента пары

for p in pairs[:10]:                # печатаем первые 10 элементов списка
    print p[0], p[1]

Примеры работы с последовательностями

Иллюстрируют особенности индексации элементов и срезов: при взятии среза нумеруются не сами элементы, а промежутки между ними.

 >>> l = ['A', 'B', 'C', 'D', 'E']    # исходный список
 >>> #   0    1    2    3    4    5   # пронумерованные промежутки между элементами
 >>> #   -5   -4   -3   -2   -1       # нумерация с конца
 >>> l[0:2]          # срез от нулевого до второго промежутка
 ['A', 'B']
 >>> l[1:-2]         # срез от второго до второго с конца элемента
 ['B','C']
 >>> l[1::2]         # каждый второй элемент начиная с первого
 ['B', 'D']
 >>> l[::-1]         # все элементы в обратном порядке
 ['E', 'D', 'C', 'B', 'A']

Функции подобные range() поддерживают то же правило (для версий языка 2.x):

 >>> range(2, 5)
 [2, 3, 4]
 >>> range(5)
 [0, 1, 2, 3, 4]

Генерация коллекций:

 >>> [0]*10
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
 >>> [ i for i in range(5) if i%2==0]
 [0, 2, 4]

Реализация перегрузки функций

Это пример простой реализации поддержки перегрузки функций на Python. Она демонстрирует, как, используя уже имеющиеся в Python средства, можно обойти одно из ограничений базовой реализации. Поддерживается минимум возможностей (только фиксированное количество позиционных аргументов, нет именованных аргументов, нет приведения типов (например int -> float) и т. п.), но работает достаточно быстро.

import sys                                   # для получения объектов из вышележащих фрэймов стека

class CannotResolve(Exception):                     # класс исключения для случая ненахождения функции
    pass          

class Resolver(object):                      # класс, реализующий разрешение на этапе исполнения
    emess = "Can't found appropriate signature of func %s() for call with" + \
            " params %r"                     # сообщение об ошибке
    def __init__(self,name):                 # конструктор
        self.function_map = {}               # словарь, отображающий типы параметров на функции
        self.default = None                  # функция по умолчанию
        self.name = name                     # имя функции для вывода сообщений об ошибках
    def __call__(self,*dt):                  # имитируем функцию, принимающую любое количество 
                                             # позиционных параметров
        cls = tuple(map(type,dt))            # создаем tuple из типов переданных аргументов
                                             # функция type возвращает тип своего параметра
                                             # map вызовет type для каждого элемента из dt
        try:                                 
            x = self.function_map[cls]       # пытаемся получить функцию из словаря
        except KeyError:                     # если подходящей нет,
            if self.default is not None:     # используем функцию по умолчанию
                x = self.default             
            else:                            # если её нет - возбуждаем исключение
                raise CannotResolve(self.emess % (self.name,cls))
        return x(*dt)                        # вызываем функцию и возвращаем результат
def overload(*dt):                           # декоратор для перегрузки в качестве параметров
                                             # принимает типы параметров
    def closure(func):                       
        name = func.__name__                 # получаем имя функции
        fr = sys._getframe(1).f_locals.get(name,Resolver(name)) 
                                             # опускаемся на один шаг вниз по стеку и находим
                                             # локальную переменную с именем функции
                                             # если же ее нет, то используем новый 
                                             # Resolver-объект
        fr.function_map[dt] = func           # добавляем новую функцию к словарю 
                                             # разрешения вызовов
        return fr                             
    return closure
def overdef(func):                           # для создания функции по умолчанию
    name = func.__name__                     # аналогично как и в функции overload
    fr = sys._getframe(1).f_locals.get(name,Resolver(name))
    fr.default = func
    return fr
# теперь воспользуемся полученными декораторами
@overdef                                     # это будет функция по умолчанию
def f(*dt,**mp):                             
    print "Default call"                     # если нет явного return, то вернется None
@overload(int)                               # единственный параметр - целое
def f(x):
    return x + 1                             
@overload(str)                               # единственный параметр - строка
def f(x):
    return x + "1"
@overload(str,int)                           # строка и целое
def f(x,y):
    return x + str(y)
print f(1)           # напечатает : 2
print f("1")         # напечатает : 11
f(2,2)               # напечатает : Default call

Управление контекстом выполнения

Следующий пример из PEP343 иллюстрирует применение оператора with для защиты блока кода от одновременного выполнения двумя потоками:

from __future__ import with_statement     # задействует оператор with в коде
from contextlib import contextmanager
from threading import Lock

# Описание менеджера контекста
@contextmanager
def locked(lock):
    lock.acquire()
    try:
        yield
    finally:
        lock.release()

# Определение блокировки
myLock = Lock()

# Применение оператора
with locked(myLock):
    #
    print "Охраняемый блок кода. Блокировка будет освобождена при любом выходе из этого блока."
    #

Генератор чисел Фибоначчи

Пример генератора чисел Фибоначчи и его использования:

def fibonacci(max):        # генератор (а не функция, т.к. оператор return заменён на yield)
    a, b = 0, 1
    while a < max:
        yield a            # return a, + запоминаем место рестарта для следующего вызова
        a, b = b, a + b    # параллельное присваивание, которое выполняется одновременно и параллельно

for n in fibonacci(100):   # используем генератор fibonacci() как итератор
    print (n)               # печатаем все числа Фибоначчи меньшие 100 через пробел

Альтернативный синтаксис доступа к элементам словаря

Можно определить словарь, который в дополнение к обычному синтаксису доступа к значению по ключу d[key] может предоставлять синтаксически более наглядный доступ к атрибуту d.key в случае алфавитно-цифровых ключей:

class Entity(dict):                                 # наследуем класс от __builtin__.dict
    def __getattr__(self, key):                     # этот метод будет вызван, если атрибут
                                                    # с именем key не будет найден у экземпляра класса
        try: 
             return self[key]                       # пытаемся вернуть элемент словаря
        except KeyError, k:                         # если такого элемента нет, то возбуждаем
             raise AttributeError, k                # исключение AttributeError
                                                    # по договоренности __getattr__ 
                                                    # не должно возбуждать других исключений

    def __setattr__(self, key, value):              # этот метод будет вызван при присвоении
        self[key] = value                           # атрибуту key значения value

    def __delattr__(self, key):                     # а этот при удалении атрибута 
        try:                                        # с помощью del mydict.g
            del self[key]
        except KeyError, k: 
            raise AttributeError, k

    def __repr__(self):                             # используется функцией repr 
        return self.__class__.__name__ + "(" + dict.__repr__(self) + ")"

d = Entity(a=1)
d.b_100 = 100
assert d.a == d['a'] and d.b_100 == d['b_100']

Функтор с генерацией байтокода

Пример эффективной реализации функтора, основанный на генерации байтокода во время исполнения. Этот пример демонстрирует следующие возможности/особенности Python:

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

Это только пример, он реализует всего одну операцию — сложение и имеет несколько других ограничений.

#-------------------------------------------------------------------------------
import byteplay        # специальный модуль для удобной работы с Python-байтокодом
import new             # для создания функции во время исполнения
import functools       # для update_wrapper
import inspect         # для получения информации о параметрах, принимаемых функцией
#-------------------------------------------------------------------------------
class FastFunctor(object):
    def __init__(self,func,code = None):
        self.func = None                # здесь будем хранить результирующую функцию
        self.ofunc = func               # а здесь исходную(original) функцию
        if code is None:     
            # конструируем байтокод для вызова функции
            self.code = [(byteplay.LOAD_CONST,func)]
            rparams = inspect.getargspec(func)[0] # получаем список параметров, принимаемых функцией
            self.code.extend((byteplay.LOAD_FAST,i) for i in rparams)
            self.code.append((byteplay.CALL_FUNCTION,len(rparams)))
        else:
            # если же функтор создан из другого функтора, 
            # то только копируем переданный байтокод
            self.code = code
        # создаем новый объект кода
        self.ocode = bp.Code.from_code(func.func_code)
    def __add__(self,obj):   # этот метод вызывается для операции '+'
        code = self.code[:]  # копируем байтокод
        if isinstance(obj,FastFunctor):  # если прибавляемый объект - функтор
            # просто дописываем его код к нашему
            # после своего исполнения он "оставит" в вершине стека результат
            code.extend(obj.code) 
        else:
            # иначе загружаем объект в стек
            code.append((byteplay.LOAD_CONST,obj))
        # дописываем байтокод, складывающий два верхних элемента в стеке
        code.append((byteplay.BINARY_ADD ,None )) 
        # создаем новый функтор, с байтокодом получения суммы
        return self.__class__(self.ofunc,code = code)
    def __call__(self,*dt,**mp):        # этот метод будет вызван для операции вызова object()
        return self.fast()(*dt,**mp)    # конструируем и вызываем функцию
    def fast(self): # конструируем функцию из байтокода
        if self.func is None:           # если функция не была создана раннее
            code = self.code + [(bp.RETURN_VALUE,None)] # добавляем байтокод возврата
            oc = self.ocode
            # создаем объект кода из байтокода и другой информации
            bin_code =  byteplay.Code(code,
                                oc.freevars,
                                oc.args,
                                oc.varargs,
                                oc.varkwargs,
                                oc.newlocals,
                                "<just_code_%s>" % id(self),
                                "<auto_gen_%s>" % id(self),
                                0,
                                "auto_generated code")
            # конструируем новую функцию из объекта кода
            self.func = new.function(bin_code.to_code(),globals())
            # после этой операции для всех средств интроспекции 
            # созданная функция будет выглядеть как оригинальная
            self.func = functools.update_wrapper(self.func,self.ofunc)
        return self.func

   # Ниже представлено тестирование скорости объектов FastFunctor и SlowFunctor
   # (статья "Функциональное программирование на Python", см. сноску после блока кода)
   # из IPython (для удобства чтения лог немного изменен)
   # строки, начинающиеся с "In [XX]:" вводятся, остальные — вывод интерпретатора 
   In [1]: import fastfunctor  
   In [2]: func = lambda x : x + 1                   # Создаем очень простую функцию
   In [3]: vl = 1                                    # Переменная, для предотвращения оптимизации
   In [4]: functor = fastfunctor.Functor(func)
   In [5]: %timeit (functor + functor + 1)(vl)       # Тестируем "лобовой" способ
   1000 loops, best of 3: 661 mks per loop           # Очень медленно
   In [6]: functor2 = (functor + functor + 1)        # Конструируем функтор один раз
   In [7]: %timeit functor2(vl)                      # и тестируем только непосредственно вызов
   100000 loops, best of 3: 4.52 mks per loop        # Значительно лучше
   In [8]: functor3 = (functor + functor + 1).fast() # Получаем результирующую функцию
   In [9]: %timeit functor3(vl) 
   1000000 loops, best of 3: 1.42 mks per loop
   In [10]: def of(vl): return x(vl) + x(vl) + 1     # Создаем функцию "вручную"
   In [11]: %timeit of(vl)
   1000000 loops, best of 3: 1.42 mks per loop       # Скорость полностью совпадает со 
                                                     # скоростью функтора
   In [12]: sfunctor = SlowFunctor(func)             # Простая реализация функтора
   In [13]: sfunctor = sfunctor + sfunctor + 1       # 
   In [14]: %timeit sfunctor(vl)                     # 
   100000 loops, best of 3: 12.6 mks per loop        # Примерно в 9 раз медленнее, чем статический 
                                                     # вариант

Код SlowFunctor можно посмотреть здесь.
Приведенные значения времени следует рассматривать только в сравнении друг с другом.
ipython — расширение интерпретатора Python для интерактивной работы.

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

Транспонирование матрицы

Пример лаконичной реализации операции транспонирования матриц с использованием парадигмы функционального программирования.

from pprint import pprint # модуль pprint используется для удобного вывода на экран
matrix = [[0.5,   0,   0,   0,   0],
          [  1, 0.5,   0,   0,   0],
          [  1,   1, 0.5,   0,   0],
          [  1,   1,   1, 0.5,   0],
          [  1,   1,   1,   1, 0.5]]

matrix_t = list(zip(*matrix)) # непосредственно транспонирование

pprint(matrix)
pprint(matrix_t)

Вывод:

[[0.5, 0, 0, 0, 0],
 [1, 0.5, 0, 0, 0],
 [1, 1, 0.5, 0, 0],
 [1, 1, 1, 0.5, 0],
 [1, 1, 1, 1, 0.5]]

[[0.5, 1, 1, 1, 1],
 [0, 0.5, 1, 1, 1],
 [0, 0, 0.5, 1, 1],
 [0, 0, 0, 0.5, 1],
 [0, 0, 0, 0, 0.5]]

Нахождение Факториала

factorial = lambda x: factorial(x - 1) * x if x > 1 else 1