Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
 iakovlev.org 
 Languages
 С
 GNU С Library 
 Qt 
 STL 
 Threads 
 C++ 
 Samples 
 stanford.edu 
 ANSI C
 Libs
 LD
 Socket
 Pusher
 Pipes
 Encryption
 Plugin
 Inter-Process
 Errors
 Deep C Secrets
 C + UNIX
 Linked Lists / Trees
 Asm
 Perl
 Python
 Shell
 Erlang
 Go
 Rust
 Алгоритмы
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
 Secure Programming for Li...6507 
 Linux Kernel 2.6...5279 
 Trees...1119 
 Максвелл 3...1051 
 William Gropp...990 
 Go Web ...963 
 Ethreal 3...930 
 Ethreal 4...917 
 Gary V.Vaughan-> Libtool...914 
 Ext4 FS...905 
 Rodriguez 6...900 
 Clickhouse...900 
 Ethreal 1...897 
 Steve Pate 1...886 
 C++ Patterns 3...864 
 Assembler...854 
 Ulrich Drepper...844 
 DevFS...788 
 MySQL & PosgreSQL...774 
 Стивенс 9...758 
 
  01.01.2024 : 3621733 посещений 

iakovlev.org

Python

Я написал цикл статей для сайта IBM, в которых затронул широкий аспект фундаментальных вопросов, связанных с программированием на питоне. Ссылки на cайт IBM:

Часть 2: Строки в питоне
Часть 3: Списки в питоне
Часть 4: Словари
Часть 5: Модули
Часть 6: Классы
Часть 7: Специальные методы и атрибуты классов
Часть 8: Файловая система
Часть 9: Процессы и потоки

Строки в питоне

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

  1. Строковый тип.
  2. Срезы (slicing).
  3. Операции со строками.
  4. Unicode.
  5. Форматирование.
  6. Встроенные методы.
  7. Тест на конкатенацию.

1. Строковый тип

Строка – это последовательность символов с произвольным доступом. Строки в языке Python невозможно изменить – в этом случае говорят, что это immutable тип. Попытка изменить символ в определенной позиции или подстроку вызовет ошибку:

>>> word = 'strength'
 >>> word[2] = 'y'  
 TypeError: 'str' object does not support item assignment
 

Но если очень хочется, то изменить можно, например, так:

>>> word = word[:3] + '!' + word[4:]
 'str!ngth'
 
 

Или так:

>>> word = word.replace('!','e')
 'strength' 
 

Индексы могут иметь отрицательные значения для отсчета с конца – отсчет начинается с -1:

>>> word[-1]     
 h
 

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

>>> '123'
 '123'
 
 >>> "7'8''9"
 "7'8''9"
 

Длинные строки можно разбивать на несколько строк с помощью обратного слеша:

>>> s = 'this is first word\
 and this is second word'
 

Большие наборы строк и целые тексты можно заключать в тройные кавычки:

>>> print """
 One
 Two
 Three
 """
 

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

После слеша может идти один или несколько символов.

В следующем примере комбинация '\n' – это новая строка, '\t' – это табуляция:

>>> s ='a\nb\tc'
 >>> print s
 a
 b	c
 

В следующем примере строка состоит из бинарной последовательности трех чисел – двух восьмеричных и одного шестнадцатеричного:

>>> s = '\001\002\x03'
 >>> s
 '\x01\x02\x03'
 
 >>> len(s)
 3
 

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

2. Срезы

Срез – это механизм гибкого управления строкой на основе индексации. Можно получить любой символ строки по его индексу. Подобно си, первый символ имеет индекс 0. Подстрока может быть определена с помощью среза – двух индексов, разделенных двоеточием:

>>> word = 'strength'
 >>> word[4]
 n
 
 >>> word[0:2]
 st
 >>> word[2:4]
 re
 

Если в срезе опущен первый символ, значит, он равен нулю; если опущен последний символ – он равен длине строки:

>>> word[:3]
 str
 >>> word[5:]
 gth
 

Можно выбирать последовательность символов из строки с определенной цикличностью:

>>> s = '1234567890'
 >>> s[::2]
 '13579'
 
 >>> s[1:10:2]
 '13579'
 >>> s[::-1]
 '0987654321'
 

3. Операции со строками

Строки можно склеивать с помощью оператора + :

>>> var = 'Moscow' + 'city'  
 
 

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

Строки можно умножать с помощью оператора * :

>>> '123' * 3
 '123123123'
 

Строки можно сравнивать с помощью операторов <, <=, ==, !=, >, >=.

4. Unicode

Unicode позволяет применять все символы, используемые в текстах на разных языках. Ранее мы могли использовать только 256 символов из определенной кодовой страницы. Перед строкой нужно поставить спецификатор u – при этом на каждый символ отводится 2 байта, так как по умолчанию ставится кодировка UTF-8:

>>> w = u'Вася Пупкин'
 >>> w
 u'\u0412\u0430\u0441\u044f \u041f\u0443\u043f\u043a\u0438\u043d' 
 

Юникодную строку можно также создать в известной кодировке: ASCII, UTF-8, UTF-16, KOI8-R, CP1251, CP866 и т.д.:

>>> s = unicode("Привет", "KOI8-R")
 >>> s
 u'\u043f\xf7\u044f\u2500\u043f\u2566\u043f\u2561\u043f\u2563\u044f\u250c'
 

Метод encode() позволяет преобразовывать строки Unicode в обычные строки, содержащие текст в указанной кодировке:

>>> s.encode("KOI8-R")
 '\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82'
 

5. Форматирование

Форматирование в питоне – мощный инструмент управления строками. Есть несколько подходов – стандартный и с использованием шаблонов. Для форматирования в питоновских строках используется стандартный оператор – символ %. Слева от процента указываем строку, справа – значение или список значений:

>>> s = 'Hello %s' % 'word'
 >>> s
 'Hello word'
 
 >>> s = 'one  %s %s' % ('two','three')
 >>> s
 'one  two three'
 

Если нужно преобразование числа в строку, используется числовой спецификатор – %d или %f:

>>> s = 'one  %d %f' % (2 , 3.5)
 >>> s
 'one  2 3.500000'
 

При форматировании можно указать общую ширину строки и точность для чисел, при этом число будет дополнено незначащими нулями. В следующем примере результирующая строка будет иметь длину 10 символов, на дробную часть будет отведено 5 символов:

>>> x = 4/3
 >>> '%10.5f' % x
 '   1.00000'
 

Пробелы слева можно отформатировать нулями:

>>> from math import pi
 >>> '%015.10f' % pi
 '0003.1415926536'
 
 

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

>>> from string import Template
 >>> s = Template('1 $two 3 4 $five')
 >>> d={}
 >>> d['two']=2
 >>> d['five']=5
 >>> s.substitute(d)
 '1 2 3 4 5'
 


Таблица типов форматирования для строк
КодЗначение
sСтроковый
rСтроковый, но с использованием repr, а не str
cПосимвольный
dДесятичный
iЦелый
uТо же, что и d (no longer unsigned)
oВосьмеричный
xШестнадцатеричный
XШестнадцатеричный в верхнем регистре
eFloating-point exponent, нижний регистр
EТо же, что и e, но в верхнем регистре
fFloating-point decimal
FFloating-point decimal
gFloating-point e или f
CFloating-point E или F
%Символьный %

6. Методы

Строки обладают большим набором разнообразных методов. Наиболее популярные из них:

find – находит подстроку в строке – возвращает позицию вхождения строки, либо -1:

>>> s = 'The find method finds a substring'
 >>> s.find('find')
 4
 
 >>> s.find('finds')
 16
 >>> s.find('findsa')
 -1
 

join – объединяет через разделитель набор строк:

>>> seq = ['one','two','three']
 >>> sep = ','
 >>> sep.join(seq)
 'one,two,three'
 

split – это обратная функция для join, разбивает строку на последовательность:

>>> s = '/usr/local/bin'
 >>> s.split('/')
 ['', 'usr', 'local', 'bin']
 

replace – заменяет в строке одну подстроку на другую:

>>> s = 'replace method returns a string'
 >>> s.replace('returns','return')
 'replace method return a string'
 

strip – удаляет пробелы слева и справа:

>>> '       this is whitespace string    '.strip()
 'this is whitespace string'
 

translate – в отличие от replace, может делать множественную замену. В следующем примере каждый символ '1' в исходной строке будет заменен на символ '3', а символ '2' – на символ '4' соответственно:

>>> from string import maketrans
 >>> table = maketrans('12', '34')
 >>> '1212 5656'.translate(table)
 '3434 5656'
 

Для конверсии различных типов в строковый используются функции str, int, ord, chr:

  • str – конвертирует число в строку;
  • int – конвертирует строку в число;
  • ord – возвращает значение байта;
  • chr – конвертирует число в символ.

Таблица методов, доступных в Python 3.0
S.capitalize()
S.ljust(width [, fill])
S.center(width [, fill])
S.lower()
S.count(sub [, start [, end]])
S.lstrip([chars])
S.encode([encoding [,errors]])
S.maketrans(x[, y[, z]])
S.endswith(suffix [, start [, end]])
S.partition(sep)
S.expandtabs([tabsize])
S.replace(old, new [, count])
S.find(sub [, start [, end]])
S.rfind(sub [,start [,end]])
S.format(fmtstr, *args, **kwargs)
S.rindex(sub [, start [, end]])
S.index(sub [, start [, end]])
S.rjust(width [, fill])
S.isalnum()
S.rpartition(sep)
S.isalpha()
S.rsplit([sep[, maxsplit]])
S.isdecimal()
S.rstrip([chars])
S.isdigit()
S.split([sep [,maxsplit]])
S.isidentifier()
S.splitlines([keepends])
S.islower()
S.startswith(prefix [, start [, end]])
S.isnumeric()
S.strip([chars])
S.isprintable()
S.swapcase()
S.isspace()
S.title()
S.istitle()
S.translate(map)
S.isupper()
S.upper()
S.join(iterable)
S.zfill(width)

7. Тест на конкатенацию

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

Проведем несложный тест: к строке будем конкатенировать символьное представление натуральных чисел по возрастанию в диапазоне от 0 до миллиона.

1-й метод. Используем для хранения строки массив символов array. Будем просто добавлять туда строковое представление натурального числа, а в конце применим метод array.tostring(). Этот метод оказался самым медленным.

2-й метод. Используем тот же алгоритм, что и в первом методе, только вместо массива array используем список, а в конце сделаем стандартный джойн.

3-й метод. Используем модуль cStringIO, в котором есть возможность писать в псевдо-файл, который на самом деле хранится в памяти. В конце вызываем метод getvalue().

4-й метод. Создаем в цикле список символьных представлений чисел, а потом одним махом джойним этот список. Отличие его от второго метода в том, что не используется append(). Вы увидите, что этот метод – самый быстрый.

Код:

import time
 
 loop_count = 1000000
 
 def method1():
   from array import array
   char_array = array('c') 
   for num in xrange(loop_count):
     char_array.fromstring(`num`)
   return char_array.tostring()
     
 def method2():
   str_list = []
   for num in xrange(loop_count):
     str_list.append(`num`)
   return ''.join(str_list)
     
 def method3():
   from cStringIO import StringIO
   file_str = StringIO()
   for num in xrange(loop_count):
     file_str.write(`num`)
   return file_str.getvalue()    
   
 def method4():
   return ''.join([`num` for num in xrange(loop_count)])
     
 t1 = time.time()
 method1()
 t2 = time.time()
 print "\t%.1f" % ((t2 - t1))
 method2()
 t3 = time.time()
 print "\t%.1f" % ((t3 - t2))
 method3()
 t4 = time.time()
 print "\t%.1f" % ((t4 - t3))
 method4()
 t5 = time.time()
 print "\t%.1f" % ((t5 - t4))
 

Подведение итогов

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

Списки в питоне

После изучения строк перейдем к спискам — в питоне это один из самых используемых типов данных. Они отдаленно напоминают массивы в Java или C — это тоже упорядоченный список объектов. В частности, аналогию можно провести с массивом в Java — с классом Vector, который способен содержать произвольные объекты. Питоновские списки можно также сравнить с массивами на языке Perl. По своей мощи, гибкости, простоте использования список превосходит аналоги из других языков программирования.

В этой статье мы рассмотрим следующие темы.

  1. Что такое список.
  2. Операции со списками.
  3. Встроенные функции.
  4. Стек и очередь.
  5. Кортежи (Tuple).
  6. Сеты (Set).
  7. Встроенные функции filter(), map(), zip(), reduce().

1. Что такое список

Для группировки множества элементов в питоне используется список list, который может быть записан как индексированная последовательность значений, разделенных запятыми, заключенная в квадратные скобки. Списки имеют произвольную вложенность, т.е. могут включать в себя любые вложенные списки. Физически список представляет собой массив указателей (адресов) на его элементы. С точки зрения производительности (performance) списки имеют следующие особенности.

  1. Время доступа к элементу есть величина постоянная и не зависит от размера списка.
  2. Время на добавление одного элемента в конец списка есть величина постоянная.
  3. Время на вставку зависит от того, сколько элементов находится справа от него, т.е. чем ближе элемент к концу списка, тем быстрее идет его вставка.
  4. Удаление элемента происходит так же, как и в пункте 3.
  5. Время, необходимое на реверс списка, пропорционально его размеру — O(n).
  6. Время, необходимое на сортировку, зависит логарифмически от размера списка.

Элементы списка не обязательно должны быть одного типа. Приведем вариант статического определения списка:

>>> lst = ['spam', 'drums', 100, 1234]  
 
 

Как и для строк, для списков нумерация индексов начинается с нуля. Для списка можно получить срез, объединить несколько списков и так далее:

>>> lst[1:3]
 ['drums', 100]
 

Можно менять как отдельные элементы списка, так и диапазон:

>>> lst[3] = 'piano'
 >>> lst[0:2] = [1,2]
 >>> lst
 [1, 2, 100, 'piano']
 

Вставка:

>>> lst[1:1] = ['guitar','microphone']
 >>> lst
 [1, 'guitar', 'microphone', 2, 100, 'piano']
 

Можно сделать выборку из списка с определенной частотой:

>>> numbers = [1,2,3,4,5,6,7,8,9,0]
 >>> numbers[::4]
 [1, 5, 9]
 

2. Операции со списками

К операциям мы относим:

  • копирование списка;
  • сложение и умножение списков;
  • итерацию — проход в цикле по элементам списка;
  • конструктор списков (list comprehension);
  • распаковку списка — sequence unpacking.

Создание копии списка.

  1. L1 = L2[:] — создание второй копии списка. Здесь создается вторая копия обьекта.
  2. L1 = list(L2) — тоже создание второй копии списка.
  3. L1 = L2 — создание второй ссылки, а не копии. 3-й вариант показывает, что создаются две ссылки на один и тот же обьект, а не две копии.

Сложение или конкатенация списков:

   L1 + L2
 

Умножение, или повтор списков:

   L1 * 2
 
 

Итерацию списков в питоне можно делать несколькими различными способами:

  • простая итерация списка:
      for x in L:
     

  • сортированная итерация:
      for x in sorted(L):
     

  • уникальная итерация:
      for x in set(L):
     

  • итерация в обратном порядке:
      for x in reversed(L):
     
     

  • исключающая итерация — например, вывести элементы 1-го списка, которых нет во 2-м списке:
      for item in set(L).difference(L2)
     

Для генерации списков, кроме статической формы, можно использовать конструктор списков — list comprehension — цикл внутри квадратных скобок — на примере списка квадратов первых 10 натуральных чисел:

>>> a = [ i*i for i in range(1,10)]
 >>> a
 [1, 4, 9, 16, 25, 36, 49, 64, 81]
 

Конструктор может быть условным — найдем квадраты четных натуральных чисел:

>>> a = [ i*i for i in range(1,10) if i % 2 == 0]
 >>> a
 [4, 16, 36, 64]
 
 

С помощью конструктора решим конкретную задачу — отсортируем слова в предложении в порядке их длительности:

words = ' to perform the task of sorting the words in a string by their length'.split()
 wordlens = [(len(word), word) for word in words]
 wordlens.sort()
 print ' '.join(w for (_, w) in wordlens)
 >>> a by in of to the the task their words length string perform sorting
 

Операция Sequence unpacking — присваивание списку переменных списка значений:

a, b = [1,2]
 

3. Встроенные функции

Списки имеют большой набор функций:

  • append , extend — добавление;
  • insert — вставка;
  • index — найти индекс первого вхождения конкретного элемента;
  • count — подсчет повторов элемента;
  • remove , del — удаление элемента;
  • sort — сортировка;
  • reverse — реверс;
  • pop — извлечение элемента;
  • len — длина списка;
  • max — максимальный элемент;
  • min — минимальный элемент;
  • оператор in — проверка элемента на вхождение.

Добавлять можно как одинарные элементы, так и набор элементов. Списки могут быть вложенными — вложенный список добавим в конец с помощью append():

>>> lst = [1, 'guitar', 'microphone', 2, 100, 'piano']
 >>> lst2 = ['sintezator','drums']
 
 >>> lst.append(lst2)
 >>> lst
 [1, 'guitar', 'microphone', 2, 100, 'piano', ['sintezator', 'drums']]
 

Элемент можно добавить в произвольную позицию списка с помощью метода insert:

>>> lst.insert(0,'vocal')
 >>> lst
 ['vocal', 1, 'guitar', 'microphone', 2, 100, 'piano', ['sintezator', 'drums']]
 

Для проверки, является ли элемент членом списка, есть оператор in:

>>> 2 in lst
 True
 >>> 10 in lst
 False
 

index() — взять элемент списка по индексу:

>>> lst.index('guitar')
 2
 

count() — подсчет числа повторов какого-то элемента:

>>> lst.count('vocal')
 1
 
 

remove() — удаление конкретного элемента:

>>> lst.remove(100)
 >>> lst
 ['vocal', 1, 'guitar', 'microphone', 2, 'piano', ['sintezator', 'drums']]
 

del — удаление по индексу:

  del lst[1]   
 

При удалении нужно помнить о том, что нельзя одновременно делать итерацию по списку — последствия будут непредсказуемы.

sort() — сортировка списка:

>>> lst.sort()
 >>> lst
 [1, 2, ['sintezator', 'drums'], 'guitar', 'microphone', 'piano', 'vocal']
 

reverse() — реверс списка:

>>> lst.reverse()
 
 >>> lst
 ['vocal', 'piano', 'microphone', 'guitar', ['sintezator', 'drums'], 2, 1]
 

pop() — извлечение элемента из списка, функция без параметра удаляет по умолчанию последний элемент списка, в качестве параметра можно поставить произвольный индекс:

>>> lst.pop()
 >>> lst
 ['vocal', 'piano', 'microphone', 'guitar', ['sintezator', 'drums'], 2]
 

len() — длина списка:

>>> len(lst)
 6
 
 

max() — максимальный элемент списка:

>>> max(lst)
 'vocal'
 

min() — минимальный элемент списка:

>>> min(lst)
 2
 

extend() — аналогичен append(), добавляет последовательность элементов:

>>> lst.extend([3,4])
 >>> lst
 ['vocal', 'piano', 'microphone', 'guitar', ['sintezator', 'drums'], 2, 3, 4]
 

4. Стек и очереди

Список можно использовать как стек — когда последний добавленный элемент извлекается первым (LIFO, last-in, first-out). Для извлечения элемента с вершины стека есть метод pop():

>>> stack = [1,2,3,4,5]  
 
 >>> stack.append(6)
 >>> stack.append(7)
 >>> stack.pop()
 >>> stack
 [1, 2, 3, 4, 5, 6]
 

Список можно использовать как очередь — элементы извлекаются в том же порядке, в котором они добавлялись (FIFO, first-in, first-out). Для извлечения элемента используется метод pop() с индексом 0:

>>> queue = ['rock','in','roll']  
 
 >>> queue.append('alive')
 >>> queue.pop(0)
 >>> queue
 ['in', 'roll', 'alive']
 

5. Кортежи (Tuple)

Список так же может быть неизменяемым (immutable), как и строка, в этом случае он называется кортеж (tuple). Кортеж использует меньше памяти, чем список. Кортеж вместо квадратных скобок использует круглые (хотя можно и совсем без скобок). Кортеж не допускает изменений, в него нельзя добавить новый элемент, хотя он может содержать объекты, которые можно изменить:

>>> t = 1,[2,3]
 >>> t
 (1, [2, 3])
 >>> t[1] = 2
 TypeError: 'tuple' object does not support item assignment
 >>> t[1].append(4)
 >>> t
 (1, [2, 3, 4])
 

Функция tuple() берет в качестве аргумента строку или список и превращает его в кортеж:

>>> tuple('abc')
 ('a', 'b', 'c')
 
 

6. Сеты (Set)

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

>>> s = set('abcde')
 >>> s
 set(['a', 'c', 'b', 'e', 'd'])
 >>> s2 = set('aghij')
 >>> s2
 set(['a', 'h', 'j', 'g', 'f'])
 
 

Над сетами можно выполнять разные операции, например:

  • вычитание:
    >>> s3 = s - s2
     >>> s3
     set(['c', 'b', 'e', 'd'])
     

  • сложение:
    >>> s3 = s | s2
     >>> s3
     set(['a', 'c', 'b', 'e', 'd', 'g', 'i', 'h', 'j'])
     

  • пересечение:
    >>> s3 = s & s2
     
     >>> s3
     set(['a'])
     

Сеты имеют встроенные функции:

add() — добавление элемента:

>>> s.add(6)
 >>> s
 set(['a', 'c', 'b', 'e', 'd', 6])
 

remove() — удаление элемента:

>>> s.remove('a')
 >>> s
 set(['c', 'b', 'e', 'd', 6])
 
 

Итерация:

>>> for item in s:print (item)
 c
 b
 e
 d
 6
 

Сеты можно использовать для фильтрации дублей в коллекциях. Для этого коллекцию нужно сконвертировать в сет, а потом обратно:

>>> L = [1,2,3,4,1,2,6,7]
 >>> set(L)
 set([1, 2, 3, 4, 6, 7])
 >>> L = list(set(L))
 >>> L
 [1, 2, 3, 4, 6, 7]
 

Сеты можно использовать для работы с большими наборами данных:

допустим, у нас имеются базы данных программистов и менеджеров:

>>> programmers = set(['ivanov','petrov','sidorov'])
 >>> managers = set(['ivanov','moxov','goroxov'])
 

Найти тех, кто одновременно и программист, и менеджер:

>>> programmers & managers
 set(['ivanov'])
 

Найти всех программистов и менеджеров:

>>> programmers | managers
 set(['ivanov', 'petrov', 'sidorov', 'goroxov', 'moxov'])
 

Найти программистов, которые не менеджеры:

>>> programmers - managers
 set(['petrov', 'sidorov'])
 

7. Встроенные функции filter(), map(), zip(), reduce().

filter(function, sequence) возвращает последовательность, состоящую из тех элементов последовательности sequence, для которых function(item) является истиной. Функция применяется для каждого элемента последовательности. Пример: определим простые числа в диапазоне до 100:

def f(x):
      for y in xrange(2, x):
          if x%y==0: return 0
      return 1
 
 print filter(f, xrange(2, 100))
 >>> [2, 3, 5, 7, 11, 13, 17, 19, 23, ... , 59, 61, 67, 71, 73, 79, 83, 89, 97]
 

map(function, sequence) возвращает список значений, полученных применением функции function к элементам одной или нескольких последовательностей. Например, создадим список кубов натуральных чисел от 1 до 10:

def cube(x): return x*x*x
 print map(cube, xrange(1, 11))
 

Можно перебирать элементы одновременно нескольких последовательностей одной длины:

seq1 = [1,2,3]
 seq2 = [11,12,13]
 for x, y in map(None, seq1, seq2):
     print x, y
 >>> 1 11
 >>> 2 12
 
 >>> 3 13
 

zip(sequence) — функция, аналогичная map() в последнем варианте, но может работать с последовательностями разной длины, возвращает список кортежей:

>>> a = (1, 2, 3, 4)
 >>> b = (5, 6, 7, 8)
 >>> zip(a, b)
 [(1, 5), (2, 6), (3, 7), (4, 8)]
 

reduce(function, sequence) возвращает значение, полученное путем последовательного применения бинарной функции function сначала к первым двум элементам последовательности sequence, затем к результату и следующему элементу и т. д. Например, вычислим сумму арифметической последовательности:

>>> def add(x, y): return x+y
 ...
 >>> reduce(add, xrange(1, 11))
 55
 

Подведение итогов

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

Код примеров проверялся на версии питона 2.6.

Словари

После списков словарь является самым гибким встроенным типом. Если список — это упорядоченная коллекция, то словарь — неупорядоченная. Основные особенности словарей:

  1. Доступ осуществляется по ключу, а не по индексу. По аналогии со списком, в словаре можно получить доступ к элементам в цикле по ключам.
  2. Значения словаря хранятся в неотсортированном порядке, более того, ключи могут храниться не в том порядке, в котором они добавляются.
  3. По аналогии со списками, словарь может хранить вложенные словари. Словарь может хранить в качестве значений объекты любого типа (heterogeneous). Ключ в словаре — immutable тип, может быть строкой, целым числом, float либо кортежем, состоящим из указанных типов.
  4. Словари реализованы как хеш-таблицы с быстрым доступом.
  5. Словари, так же как и списки, хранят ссылки на объекты, а не сами объекты.

Сегодня мы рассмотрим следующие темы.

  1. Что такое словарь.
  2. Функции/методы словаря.
  3. Операции со словарем.
  4. Примеры.

1. Что такое словарь

Словарь (dictionary) — это ассоциативный массив или хеш. Это неупорядоченное множество пар ключ: значение с требованием уникальности ключей. Пара фигурных скобок {} создает пустой словарь. В отличие от последовательностей, доступ к элементам словаря производится по ключу, а не по индексу, ключ может быть любого типа, ключ не допускает изменений.

Основные операции над словарем — сохранение с заданным ключом и извлечение по нему значения. Также можно удалить пару key: value с помощью инструкции del.

Метод keys() для словаря возвращает список всех используемых ключей в произвольном порядке; для сортировки списка нужно применить метод sort(). Для определения наличия определенного ключа есть метод has_key(), который в версии 3.0 успеет устареть — вместо него есть оператор in. Добавление нового объекта в словарь не требует предварительных проверок: если ранее ключу уже соответствовало некоторое значение, оно будет перезаписано.

Пример — словарь в качестве телефонного справочника:

>>> dic = {'vanya' : 23323223, 'smith' : 32232332}
 >>> dic['fedya'] = 33332222
 >>> dic
 {'vanya': 23323223, 'fedya': 33332222, 'smith': 32232332}
 >>> dic['smith']
 32232332
 >>> del dic['vanya']
 >>> dic
 {'fedya': 33332222, 'smith': 32232332}
 >>> dic.keys()
 ['fedya', 'smith']
 >>> dic.has_key('fedya')
 True
 
 

Создать словарь можно несколькими способами:

  1. Обычное выражение — оно удобно, если словарь статичен:
        D = {'name': 'mel', 'age': 45}
     

  2. Динамический вариант создания на лету:
        D = {}
         D['name'] = 'mel'
         D['age'] = 45
     

  3. С помощью функции dict() — ключи при этом должны быть строками. С помощью этой функции можно избавить себя от обязательного условия заключать ключ в кавычки. В примере приведены четыре варианта создания одного и того же словаря:
        d1 = dict(id=1948, name="Washer", size=3)
         d2 = dict({"id": 1948, "name": "Washer", "size": 3})
         d3 = dict([("id", 1948), ("name", "Washer"), ("size", 3)])
         d4 = dict(zip(("id", "name", "size"), (1948, "Washer", 3)))
     

  4. С помощью fromkeys() — создает словарь по списку ключей с пустыми значениями:
        D = {}.fromkeys(['name', 'age'],123)
     

  5. С помощью конструктора:
        d = dict((x, x**2) for x in xrange(5))
     

2. Функции/методы словаря

dict() — создание словаря;

len() — возвращает число пар;

clear() — удаляет все значения из словаря;

copy() — создает псевдокопию словаря;

deepcopy() — создает полную копию словаря;

fromkeys() — создание словаря;

get() — получить значение по ключу;

has_key() — проверка значения по ключу;

items() — возвращает список значений;

iteriyems() — возвращает итератор;

keys() — возвращает список ключей;

iterkeys() — возвращает итератор ключей;

pop() — извлекает значение по ключу;

popitem() — извлекает произвольное значение;

update() — изменяет словарь;

values() — возвращает список значений;

itervalues() — возвращает итератор на список значений.

in — оператор, проверяет наличие значения по ключу;

del — оператор, удаляет пару по ключу;

dict() — конструирует словарь с помощью последовательности.

Например, создать словарь с помощью списка кортежей:

>>> items = [('name','sveta'),('age',20)]
 >>> d = dict(items)
 
 >>> d
 {'age': 20, 'name': 'sveta'}
 
 >>> len(d)
 2
 

in() — оператор проверки вхождения.

Пример: база данных может быть заполнена в виде словаря.

Проверить наличие в базе данных телефона по имени:

people = {'Alice': {'phone': '2341', 'addr': 'Foo drive 23' },
           'Beth':  {'phone': '9102', 'addr': 'Bar street 42'}}
 name = 'Alice'          
 key = 'phone'
 if name in people: 
   print "%s phone is %s" % (name, people[name][key])
 >>> Alice phone is 2341
 
 copy()
 
 

Пример создания копии словаря:

>>> x = {"user":'admin','attr':[1,2,3]}
 >>> y = x.copy()
 >>> y
 {'user': 'admin', 'attr': [1, 2, 3]}
 

Метод copy() не делает полного копирования: если мы, например, сделаем операцию:

>>> x['attr'].remove(1)  
 
 

то с удивлением обнаружим, что удаление атрибута произойдет также и в копии.

Чтобы этого не произошло, нужно использовать метод deepcopy().

>>> from copy import deepcopy
 >>> y = x.deepcopy()
 

fromkeys() — создает словарь по заданным ключам с пустыми значениями:

>>> {}.fromkeys(['name', 'age'])
 {'age': None, 'name': None}
 
 

Можно все значения заполнить по умолчанию:

>>> {}.fromkeys(['name', 'age'],123)
 {'age': 123, 'name': 123}
 

get() — получает значение по ключу, в случае отсутствия дает None:

>>> d = {}
 >>> print d.get('name')
 None
 

has_key() — проверяет, есть ли в словаре значение по данному ключу:

>>> d = {}
 >>> d.has_key('name')
 False
 

items() — возвращает список значений:

for key, value in d.items():
         print(key, value)        
 

iteriyems() — возвращает итератор — выдает тот же результат:

>>> for k, v in d.iteritems():
 ...     print k, v
 

keys() — возвращает список ключей;

iterkeys() — возвращает итератор ключей:

>>> d.keys()
 ['url', 'title']
 >>> d.iterkeys()
 <dictionary-keyiterator object at 0xb7c4dd00>
 
 

pop() — извлекает значение по ключу с последующим удалением:

>>> d.pop('title')
 >>> d
 {'url': 'http://www.python.org'}
 

popitem() — извлекает произвольное значение с последующим удалением:

>>> d = {'title': 'Python Web Site', 'url': 'http://www.python.org', 'www': 'python'}
 >>> d.popitem()
 
 >>> d
 {'www': 'python', 'title': 'Python Web Site'}
 

update() — изменяет значение по ключу:

>>> d2 = {'www':'python.org'}
 >>> d.update(d2)
 >>> d
 {'www': 'python.org', 'title': 'Python Web Site'}
 

values() — возвращает список значений:

>>> d={}
 >>> d[1]=1
 >>> d[2]=2
 >>> d[3]=3
 >>> d
 {1: 1, 2: 2, 3: 3}
 >>> d.values()
 [1, 2, 3]
 

del — оператор удаляет пару ключ: значение по ключу:

>>> del d[2]
 >>> d
 {1: 1, 3: 3}
 

3. Операции

Поскольку словари представляют собой мапы (map), а не последовательности, к ним нельзя применить конкатенацию или срезы.

К словарям можно применять стандартные операторы сравнения:

    <, <=, ==, !=, >=, >
 
 

Для того чтобы сделать проход по ключам словаря, используем for:

>>> table = {'Python': 'Guido van Rossum',
 ...          'Perl':     'Larry Wall',
 ...          'Tcl':      'John Ousterhout' }
 >>> for lang in table:
 ...     print(lang, table[lang])
 ..
 >>> Tcl     John Ousterhout
 >>> Python  Guido van Rossum
 >>> Perl    Larry Wall
 

Словари хорошо подходят для хранения многомерных массивов или матриц:

>>> Matrix = {}
 
 >>> Matrix[(2, 3, 4)] = 88
 >>> Matrix[(7, 8, 9)] = 99
 >>>
 >>> X = 2; Y = 3; Z = 4
 >>> Matrix[(X, Y, Z)]
 88
 >>> Matrix
 {(2, 3, 4): 88, (7, 8, 9): 99}
 

С помощью словарей можно хранить структурированную информацию в виде записей:

>>> man = {'name': 'Serg',
 ...        'jobs': ['programmer', 'writer'],
 ...        'web': 'www.iakovlev.org',
 ...        'home': {'city': 'Moscow', 'zip':129000}}
 >>> man['name']
 Serg
 
 >>> man['jobs'][1]
 'writer'
 

4. Примеры

Пример 1. Подсчитаем, сколько раз в строке встречается каждый символ:

def histogram(s):
     d = dict()
     for c in s:
         if c not in d:d[c] = 1
         else:d[c] += 1
     return d
 hist = histogram('how many times')
 >>> {'a': 1,'e': 1,'i': 1,'h': 1,'m': 2,'o': 1,'n': 1,'s': 1,'t': 1,'w': 1,'y': 1}
 

Если нам нужно инвертировать данный словарь и в качестве ключа поставить частоту:

def invert_dict(d):
     inv = dict()
     for key in d:
         val = d[key]
         if val not in inv:inv[val] = [key]
         else:inv[val].append(key)
     return inv
 print invert_dict(hist)
 >>> {1: ['a', 'e', 'i', 'h', 'o', 'n', 's', 't', 'w', 'y'], 2: [' ', 'm']}
 

Пример 2. Подсчитать частоту повторов каждого уникального слова в файле:

import string
 import sys
 words = {}
 strip = string.whitespace + string.punctuation + string.digits + "\"'"
 filename = 'file'
 for line in open(filename):
     for word in line.lower().split():
         word = word.strip(strip)
         if len(word) > 2:
             words[word] = words.get(word, 0) + 1
 for word in sorted(words):
     print("'{0}' occurs {1} times".format(word, words[word]))
 

Пример 3. Сортировка словаря по ключам:

author = {"php":"Rasmus Lerdorf",\
     "perl":"Larry Wall",\
     "tcl":"John Ousterhout",\
     "awk":"Brian Kernighan",\
     "java":"James Gosling",\
     "parrot":"Simon Cozens",\
     "python":"Guido van Rossum"}
 #Либо так:
 langs = author.keys()
 langs.sort()
 for language in langs:
     print language," - ",author[language]
 #либо так:
 for key in sorted(author.iterkeys()):
     print "%s: %s" % (key, author[key])
    
 
 >>> awk  -  Brian Kernighan
 >>> java  -  James Gosling
 >>> parrot  -  Simon Cozens
 >>> perl  -  Larry Wall
 >>> php  -  Rasmus Lerdorf
 >>> python  -  Guido van Rossum
 >>> tcl  -  John Ousterhout
 

Пример 4. Как инвертировать словарь, т.е. поменять ключи со значениями:

def invert_dict_nonunique(d):
     newdict = {}
     for k, v in d.iteritems():
         newdict.setdefault(v, []).append(k)
     return newdict
 d = {'child1': 'parent1','child2': 'parent1','child3': 'parent2','child4': 'parent2'}
 print invert_dict_nonunique(d)
 >>> {'parent2': ['child3', 'child4'], 'parent1': ['child1', 'child2']}
 

Заключение

Можно подвести итоги: словари наряду со списками являются наиболее простыми, гибкими и мощными коллекционными типами. Словарь, как и список, является изменяемым (mutable) типом данных, хотя и содержит неизменяемые ключи и может неограниченно расти. Если вам нужна коллекция с доступом по ключу — словарь подходит для этого лучше всего. Если вам нужна коллекция для хранения произвольных объектов произвольной вложенности — словарь в этом вам поможет.

Модули

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

Модули выполняют как минимум три важных функции:

  • Повторное использование кода: такой код может быть загружен много раз во многих местах.
  • Управление адресным пространством: модуль — это высокоуровневая организация программ, это пакет имен, который избавляет вас от конфликтов. Каждый объект «проживает» свой цикл внутри своего модуля, поэтому модуль — это средство для группировки системных компонентов.
  • Глобализация сервисов и данных: для реализации объекта, который используется во многих местах, достаточно написать один модуль, который будет импортирован.

Cегодня мы рассмотрим следующие темы.

  1. Что такое модуль.
  2. Как импортировать модуль.
  3. Что такое компиляция.
  4. Стандартные модули.
  5. Пакеты.

1. Что такое модуль

Python позволяет поместить классы, функции или данные в отдельный файл и использовать их в других программах. Такой файл называется модулем. Объекты из модуля могут быть импортированы в другие модули. Имя файла образуется путем добавления к имени модуля расширения .py. При импорте модуля интерпретатор ищет файл с именем my_module.py сначала в текущем каталоге, затем в каталогах, указанных в переменной окружения PYTHONPATH, затем в зависящих от платформы путях по умолчанию, а также в специальных файлах с расширением '.pth', которые лежат в стандартных каталогах. Программист может внести изменения в PYTHONPATH и в '.pth', добавив туда свой путь. Каталоги, в которых осуществляется поиск, можно посмотреть в переменной sys.path.

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

2. Импорт модуля

Если запустить в каталоге, в котором лежит данный модуль (например, my_module.py), интерпретатор:

>>> python
 

и потом сделать импорт модуля:

>>> import my_module
 

то мы получаем доступ ко всем функциям, которые в модуле определены:

>>> my_module.func1()
 >>> my_module.func2()
 ...
 

Для более короткой записи можно создать локальную переменную:

>>> f1 = my_module.func1
 

Второй вариант импорта — взятие непосредственно имени без имени модуля:

>>> from my_module import func1, func2
 >>> func1()
 

Третий вариант импорта — включение всех имен, определенных в модуле:

>>> from my_module import *
 >>> func1()
 

Для предотвращения конфликта имен можно использовать создание алиаса:

>>> from my_module import open as my_open
 

Пример. Импорт на основе from обладает такой особенностью, что он делает импортируемые атрибуты read-only:

>>> from small import x, y
 >>> x = 42
 
 

В данном случае x — это локальная переменная, в то время как переменные x, y в самом модуле small не меняются:

>>> import small
 >>> small.x = 42
 

здесь x — глобальная переменная.

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

Поскольку модуль загружается один раз, для его повторной загрузки можно использовать функцию reload().

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

Модули могут импортировать другие модули. Обычно инструкцию import располагают в начале модуля или программы.

3. Компиляция файлов

Для ускорения запуска программ, использующих большое количество модулей, если уже существует файл с именем my_module.pyc в том же каталоге, где найден my_module.py, считается, что он содержит байт-компилированный модуль my_module. Если такого файла нет, то он создается, и время последнего изменения my_module.py записывается в созданном my_module.pyc. Содержимое байт-компилированных файлов является платформенно-независимым (но может быть разным для разных версий интерпретатора), так что каталог с модулями может совместно использоваться машинами с разными архитектурами.

Некоторые полезные опции компиляции:

  1. -O — эта опция заставляет интерпретатор компилировать так называемый оптимизированный байт-код и сохранять его в файле с расширением '.pyo'. При этом из кода удаляются ассерты, игнорируется условный дебаг, '.pyc'-файлы игнорируются.
  2. -OO — эта опция делает то же, что и предыдущая опция, плюс удаляет комменты.
  3. Файл, запускаемый непосредственно из командной строки, никогда не компилируется. Для оптимизации его запуска необходимо большую часть кода убрать в модули.
  4. Модуль может загружаться из файлов с расширением '.pyс' или '.pyo', даже если нет файла с расширением '.py'. Это может пригодиться в тех случаях, когда вы не хотите распространять исходный код.
  5. Кроме того, интерпретатор может загружать бинарники, собранные с помощью языка си — файлы с расширением '.so' в линуксе либо '.dll' в Windows.
  6. Модуль можно «зазипповать» в архив с расширением '.zip' и импортировать из архива.
  7. Может быть загружен Java-класс, собранный с помощью Jython.

4. Стандартные модули

Python распространяется с библиотекой стандартных модулей. Библиотека включает в себя более 200 модулей, которые выполняют платформенно-зависимую поддержку таких задач, как: интерфейс к операционной системе, управление объектами, поиск, сеть + интернет, GUI и т.д. Полный список стандартных модулей можно посмотреть на http://docs.python.org/library/.

Часть модулей встроена в интерпретатор по умолчанию, обеспечивая доступ к операциям; они встроены либо из соображений эффективности, либо для обеспечения доступа к примитивам операционной системы — например, модуль sys.

Переменная sys.path содержит список строк с именами каталогов, в которых происходит поиск модулей. Она инициализируется из значения переменной окружения PYTHONPATH и встроенного значения по умолчанию. Можно добавить путь:

>>> import sys
 >>> sys.path.append(/home/my/lib/python)
 

Для выяснения имен, определенных в модуле, можно использовать встроенную функцию dir(). Она возвращает отсортированный список строк:

>>> dir(sys)
 ['__displayhook__', '__doc__', '__egginsert', '__excepthook__', '__name__',
 ...
 'stderr', 'stdin', 'stdout', 'subversion', 'version', 'version_info']
 

5. Пакеты

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

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

Например, есть пакет, который лежит в корневой папке TCP. В нем лежат два подкаталога — Server и Client:

TCP/
    _init_.py
    main.py
    
    Server/
          _init_.py
          tcp.py
          server.py
          lib.py
    Client/
          _init_.py
          tcp.py
          client.py
          lib.py
 

Файл _init_.py необходим для того, чтобы интерпретатор распознал каталог, как содержащий пакет. Обычно это пустой файл. Тогда импорт индивидуальных модулей пакета может быть таким:

>>> import TCP.Server.lib
 >>> import TCP.Client.lib
 

Ссылка на функцию должна быть полной:

>>> import TCP.Server.lib.connect()
 

Можно сделать альтернативную загрузку:

>>> from TCP.Server import lib as server_lib 
 >>> from TCP.Client import lib as client_lib 
 >>> server_lib.connect()
 >>> client_lib.connect()
 

Здесь вместо lib может быть подставлен модуль, подпакет или имя, определенное в TCP.Server — т.е. это может быть функция, класс или переменная.

Что касается варианта с импортом:

>>> from TCP import *
 

то в корневом __init__.py может быть определен список __all__ , в котором перечисляются модули, которые импортируются в этом случае. Например:

    __all__ = ["Server", "Client"]
 

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

Заключение

Сегодня мы узнали основы модульной системы питона и импорта компонентов. Импорт модулей — это основа программной архитектуры в питоне. Большие программы состоят из большого количества файлов, и объединяет их линковка во время исполнения на основе импорта. Модули структурируют программу, разбивая логику на отдельные компоненты. Код внутри одного модуля изолирован от остальных модулей, что минимизирует коллизию имен внутри программы.

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

Классы

Мы переходим к одной из самых интересных тем цикла — объектно-ориентированному программированию (ООП) в Python. С точки зрения ООП, класс представляет собой коллекцию данных. Использование классов дает нам прежде всего преимущества абстрактного подхода в программировании.

  1. Полиморфизм: в разных объектах одна и та же операция может выполнять различные функции. Слово «полиморфизм» имеет греческую природу и означает «имеющий многие формы». Простым примером полиморфизма может служить функция count(), выполняющая одинаковое действие для различных типов обьектов: 'abc'.count('a') и [1, 2, 'a'].count('a'). Оператор плюс полиморфичен при сложении чисел и при сложении строк.
  2. Инкапсуляция: можно скрыть ненужные внутренние подробности работы объекта от окружающего мира. Это второй основной принцип абстракции. Он основан на использовании атрибутов внутри класса. Атрибуты могут иметь различные состояния в промежутках между вызовами методов класса, вследствие чего сам объект данного класса также получает различные состояния — state.
  3. Наследование: можно создавать специализированные классы на основе базовых. Это позволяет нам избегать написания повторного кода.
  4. Композиция: объект может быть составным и включать в себя другие объекты.

Объектно-ориентированный подход в программировании подразумевает следующий алгоритм действий.

  1. Описывается проблема с помощью обычного языка с использованием понятий, действий, прилагательных.
  2. На основе понятий формулируются классы.
  3. На основе действий проектируются методы.
  4. Реализуются методы и атрибуты.

Мы получили скелет — объектную модель. На основе этой модели реализуется наследование. Для проверки модели:

  1. пишется так называемый use cases — сценарий возможного поведения модели, где проверяется вся функциональность;
  2. функционал при этом может быть исправлен либо добавлен.

Объектно-ориентированный подход хорош там, где проект подразумевает долгосрочное развитие, состоит из большого количества библиотек и внутренних связей.

Механизм классов языка Python представляет собой смесь механизмов классов C++ и Modula-3. Наиболее важные особенности классов в питоне:

  1. множественное наследование;
  2. производный класс может переопределить любые методы базовых классов;
  3. в любом месте можно вызвать метод с тем же именем базового класса;
  4. все атрибуты класса в питоне по умолчанию являются public, т.е. доступны отовсюду; все методы — виртуальные, т.е. перегружают базовые.

Сегодня мы рассмотрим следующие аспекты объектно-ориентированного программирования.

  1. Что такое класс.
  2. Атрибуты класса.
  3. self.
  4. Наследование.
  5. ООП в действии: пример создания классов.

1. Что такое класс

Класс — это пользовательский тип. Простейшая модель определения класса выглядит следующим образом:

 class имя_класса:
  инструкция 1
  .
  инструкция №
 
 

Каждая такая запись генерирует свой объект класса. Отличие от C++ в том, что в плюсах описание класса — это лишь объявление, а в питоне — это создание объекта. Есть также другой тип объекта — инстанс класса, который генерируется при вызове:

 инстанс_класса = имя_класса()
 

Объект класса и инстанс класса — это два разных объекта. Первый генерируется на этапе объявления класса, второй — при вызове имени класса. Объект класса может быть один, инстансов класса может быть сколько угодно.

Инструкция — это, как правило, определение функции. При определении класса создается новое пространство имен и создается объект-класс, который является оболочкой для всех инструкций.

Объекты классов поддерживают два вида операций:

  • доступ к атрибутам;
  • создание экземпляра класса.

2. Атрибуты класса

Атрибуты класса бывают двух видов:

  • атрибуты данных;
  • атрибуты-методы.

Атрибуты данных обычно записываются сверху. Память для атрибутов выделяется в момент их первого присваивания — либо снаружи, либо внутри метода. Методы начинаются со служебного слова def.

Доступ к атрибутам выполняется по схеме obj.attrname.

Пример.

class Simple:
  u'Простой класс'
  var = 87
  def f(x):
  return 'Hello world'
 

Здесь Simple.var и Simple.f — пользовательские атрибуты. Есть также стандартные атрибуты:

>>> print Simple.__doc__
 

Целое число

>>> print Simple.var.__doc__
 int(x[, base]) -> integer
 ...
 

Создание экземпляра класса похоже на то, как будто мы делаем вызов функций:

 smpl = Simple()
 
 

Будет создан пустой объект smpl. Если мы хотим, чтобы при создании выполнялись какие-то действия, нужно определить конструктор, который будет вызываться автоматически:

 class Simple:
  def __init__(self):
  self.list = []
 

При создании объекта smpl будет создан пустой список list. Конструктору можно передать аргументы:

 class Simple:
  def __init__(self, count, str):
  self.list = []
  self.count = count
  self.str = str
  
 >>> s = Simple(1,'22')
 >>> s.count, s.str
 1 22
 

Атрибут данных можно сделать приватным (private) — т.е. недоступным снаружи — для этого слева нужно поставить два символа подчеркивания:

class Simple:
  u'Простой класс с приватным атрибутом'
  __private_attr = 10 
  def __init__(self, count, str):
  self.__private_attr = 20
  print self.__private_attr
  
 s = Simple(1,'22')
 print s.__private_attr
 

Последняя строка вызовет исключение — атрибут __private_attr годен только для внутреннего использования.

Методы необязательно определять внутри тела класса:

def method_for_simple(self, x, y):
  return x + y
 
 class Simple:
  f = method_for_simple
  
 >>> s = Simple()
 >>> print s.f(1,2)
 3
 

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

class Customer:
  pass
 custom = Customer()
 custom.name = 'Вася'
 custom.salary = 100000
 

3. self

Обычно первый аргумент в имени метода — self. Как говорит автор языка Гвидо Ван Россум, это не более чем соглашение: имя self не имеет абсолютно никакого специального значения.

self полезен для того, чтобы обращаться к другим атрибутам класса:

class Simple:
  def __init__(self):
  self.list = []
  def f1(self):
  self.list.append(123)
  def f2(self):
  self.f1()
  
 >>> s = Simple()
 >>> s.f2()
 >>> print s.list
 [123]
 

Self — это аналог "this" в C++.

4. Наследование

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

 class Derived(Base):
 

Если базовый класс определен не в текущем модуле:

 class Derived(module_name.Base):
 

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

 Base.method()
 

В питоне существует ограниченная поддержка множественного наследования:

 class Derived(Base1,Base2,Base3):
 

Поиск атрибута производится в следующем порядке:

  1. в Derived;
  2. в Base1, затем рекурсивно в базовых классах Base1;
  3. в Base2, затем рекурсивно в базовых классах Base2
  4. и т.д.

5. Пример

Создадим два класса: Person — хранит общую информацию о людях — имя, профессия, зарплата; класс Manager — специализированный производный класс. В классе Person мы создадим свою версию для стандартной встроенной функции str, которая есть по умолчанию в любом питоновском классе — для этого она будет иметь префикс с двумя символами подчеркивания слева и справа. Когда мы попытаемся распечатать инстанс класса, будет вызвана __str__.

 
 # -*- coding: utf-8 -*-
 class Person:
  def __init__(self, name, job=None, pay=0):
  self.name = name
  self.job = job
  self.pay = pay
  def lastName(self):
  return self.name.split()[-1]
  def giveRaise(self, percent):
  self.pay = int(self.pay * (1 + percent))
  def __str__(self):
  return '[Person: %s, %s]' % (self.name, self.pay)
 
 class Manager(Person):
  def __init__(self, name, pay): 
  Person.__init__(self, name, 'mgr', pay) 
  def giveRaise(self, percent, bonus=100):
  Person.giveRaise(self, percent + bonus)
 

Создаем первый инстанс класса Person:

>>> ivan = Person('Иван Petrov')
 

Создаем второй инстанс класса Person:

>>> john = Person('John Sidorov', job='dev', pay=100000)
 

Вызываем перегруженную функцию __str__;

>>> print(ivan)
 >>> print(john)
 

Выводим фамилию:

>>> print(ivan.lastName(), john.lastName())
 

Начисляем премиальные:

>>> john.giveRaise(.10)
 

И получаем:

>>> print(john)
 

Создаем инстанс класса Manager:

>>> tom = Manager('Tom Jones', 50000)
 

Начисляем мегапремиальные:

>>> tom.giveRaise(.10)
 
 

Выводим:

print(tom.lastName())
 print(tom)
 

Вывод:

[Person: Иван Petrov, 0]
 [Person: John Sidorov, 100000]
 ('Petrov', 'Sidorov')
 [Person: John Sidorov, 110000]
 Jones
 [Person: Tom Jones, 5055000]
 

Заключение

Основные свойства ООП — полиморфизм, наследование, инкапсуляция. Класс — это пользовательский тип, состоящий из методов и атрибутов. Инстанс класса создается путем вызова имени класса как функции с параметрами. Объект состоит из атрибутов и методов. Атрибут — это переменная, метод — это функция. Отличия метода от функции в том, что у него есть первый параметр — self. Полиморфизм позволяет нам работать с различными типами объектов так, что нам не нужно задумываться о том, к какому типу они принадлежат. Объекты могут скрывать (инкапсулировать) свое внутреннее состояние. Это достигается за счет того, что доступ к атрибутам осуществляется не напрямую, а через методы. Класс может быть производным от одного или нескольких классов. Производный класс наследует все методы базового класса. Базовых классов может быть несколько. Объектный дизайн должен быть прозрачным, понятным и описан, что называется, в 'терминах человеческого языка'.

Специальные методы и атрибуты классов

Мы продолжаем изучать классы в Python. Специальные зарезервированные методы здесь имеют префикс — двойной символ подчеркивания. С их помощью реализованы такие механизмы, как конструкторы, последовательности, итераторы, проперти, слоты и т.д.

Сегодня мы рассмотрим следующие темы.

  1. Объекты классов и специальные методы.
  2. Экземпляры классов и специальные методы.
  3. Экземпляры классов в качестве последовательностей.
  4. Приведение объектов к базовым типам.
  5. Bound и unbound методы.
  6. Метод super.
  7. Статические методы.
  8. Итератор.
  9. Property.
  10. Singleton.
  11. Слоты.
  12. Функтор.
  13. Дескриптор.
  14. Sequence.

1. Объекты классов и специальные методы

Объект-класс создается с помощью определения класса. Объекты-классы имеют следующие атрибуты:

__name__ — имя класса;

__module__ — имя модуля;

__dict__ — словарь атрибутов класса, можно изменять этот словарь напрямую;

__bases__ — кортеж базовых классов в порядке их следования;

__doc__ — строка документации класса.

2. Экземпляры классов и специальные методы

Экземпляр (инстанс) класса возвращается при вызове объекта-класса. Объект у класса может быть один, экземпляров (или инстансов) — несколько. Экземпляры имеют следующие атрибуты:

__dict__ — словарь атрибутов класса, можно изменять этот словарь напрямую;

__class__ — объект-класс, экземпляром которого является данный инстанс;

__init__ — конструктор. Если в базовом классе есть конструктор, конструктор производного класса должен вызвать его;

__del__ — деструктор. Если в базовом классе есть деструкор, деструктор производного класса должен вызвать его;

__cmp__ — вызывается для всех операций сравнения;

__hash__ — возвращает хеш-значение объекта, равное 32-битному числу;

__getattr__ — возвращает атрибут, недоступный обычным способом;

__setattr__ — присваивает значение атрибуту;

__delattr__ — удаляет атрибут;

__call__ — срабатывает при вызове экземпляра класса.

3. Экземпляры классов в качестве последовательностей

Экземпляры классов можно использовать для эмуляции последовательностей. Для такой реализации есть встроенные методы:

__len__ — возвращает длину последовательности;

__getitem__ — получение элемента по индексу или ключу;

__setitem__ — присваивание элемента с данным ключом или индексом;

__delitem__ — удаление элемента с данным ключом или индексом;

__getslice__ — возвращает вложенную последовательность;

__setslice__ — заменяет вложенную последовательность;

__delslice__ — удаляет вложенную последовательность;

__contains__ — реализует оператор in.

4. Приведение объектов к базовым типам

Объекты классов можно привести к строковому или числовому типу.

__repr__ — возвращает формальное строковое представление объекта;

__str__ — возвращает строковое представление объекта;

__oct__ , __hex__ , __complex__ , __int__ , __long__ , __float__ — возвращают строковое представление в соответствующей системе счисления.

5. Bound и unbound методы

Рассмотрим конкретный пример. Есть базовый класс Cat, и есть производный от него класс Barsik:

   
 class Cat:
   def __init__(self):
       self.hungry = True
   def eat(self):
       if self.hungry:
           print 'I am hangry...'
           self.hungry = False
       else:
           print 'No, thanks!'
    
 class Barsik(Cat):
   def __init__(self):
       self.sound = 'Aaaammm!'
       print self.sound
 

Создаем экземпляр производного класса:

>>> brs = Barsik()
 Aaaammm!
 >>> brs.eat()
 AttributeError: Barsik instance has no attribute 'hungry'
 

На первый взгляд — странная ошибка, поскольку атрибут hungry есть в базовом классе. На самом деле, конструктор производного класса — перегруженный, при этом конструктор базового класса не вызывается, и его нужно явно вызвать. Это можно сделать двумя путями. Первый вариант считается устаревшим:

class Barsik(Cat):
   def __init__(self):
       Cat.__init__(self)            
       self.sound = 'Aaaammm!'
       print self.sound
 

Здесь мы напрямую вызываем конструктор базового класса, не создавая инстанс базового класса Cat — поэтому такой базовый конструктор относится к категории unbound-методов, в пику методам, которые вызываются для инстансов классов и называются bound-методами. Для вызова bound-метода в качестве первого параметра методу нужно передать инстанс класса.

6. Метод super

Второй вариант: в начале программы нужно определить метакласс , который указывает на то, что класс реализован в так называемом новом стиле — new-style. Затем нужно вызвать стандартный метод super для базового конструктора:

__metaclass__ = type
 ...
 class Barsik(Cat):
     def __init__(self):
       super(Barsik, self).__init__()
       self.sound = 'Aaaammm!'
       print self.sound
 
 >>> brs = Barsik()
 >>> brs.eat()
 Aaaammm!
 I am hangry...
 
 

7. Статические методы

Статический метод — функция, определенная вне класса и не имеющая атрибута self:

 
 class Spam:
     numInstances = 0
     def __init__(self):
         Spam.numInstances = Spam.numInstances + 1
     
 def printNumInstances( ):
     print "Number of instances created: ", Spam.numInstances
     
 >>> a=Spam()
 >>> b=Spam()
 >>> printNumInstances()
 Number of instances created:  2
 

Статический метод может быть определен и внутри класса — для этого используется ключевое слово staticmethod, причем метод может быть вызван как статически, так и через инстанс:

 
 class Multi:
     def imeth(self, x):
         print self, x
     def smeth(x):
         print x
     def cmeth(cls, x):
         print cls, x
     smeth = staticmethod(smeth)
     cmeth = classmethod(cmeth)
      
 >>> Multi.smeth(3)    
 3
 >>> obj=Multi()
 >>> obj.smeth(5)
 5
 

Методы класса определяются с помощью ключевого слова classmethod — здесь автоматически питон передает в качестве первого параметра сам класс (cls):

 
 >>> Multi.cmeth(7)
 __main__.Multi 7
 >>> obj.cmeth(10)
 __main__.Multi 10 
 

8. Итератор

Итераторы хороши там, где списки не подходят в силу того, что занимают много памяти, а итератор возвращает его конкретное значение. В классе нужно определить два стандартных метода — __iter__ и next. Метод __iter__ будет возвращать объект через метод next:

 
 class Reverse:
     def __init__(self, data):
         self.data = data
         self.index = len(data)
     def __iter__(self):
         return self
     def next(self):
         if self.index == 0:
             raise StopIteration
         self.index = self.index - 1
         return self.data[self.index]
 
 >>> for char in Reverse('12345'):
 >>>   print char
 5
 4
 3
 2
 1
 

Итератор можно сконвертировать в список:

>>> rvr = list(Reverse('12345'))
 >>> rvr
 ['5', '4', '3', '2', '1']
 

9. Property

Property — атрибут класса, возвращаемый через стандартную функцию property, которая в качестве аргументов принимает другие функции класса:

   
 class DateOffset:
     def __init__(self):
         self.start = 0
     
     def _get_offset(self):
         self.start +=  5
         return self.start 
 
     offset = property(_get_offset)
 
 
 >>> d = DateOffset()
 >>> d.offset
 5
 >>> d.offset
 10
 

10. Singleton

Данный паттерн позволяет создать всего один инстанс для класса. Используется метод __new__:

 
 class Singleton(object):
   def __new__(cls, *args, **kw):
       if not hasattr(cls, '_instance'):
         orig = super(Singleton, cls)
         cls._instance = orig.__new__(cls, *args, **kw)
       return cls._instance
  
      
 >>> one = Singleton()
 >>> two = Singleton()
 >>> id(one)
 3082687532
 >>> id(two)
 3082687532
 

11. Слоты

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

class limiter(object):
     __slots__ = ['age', 'name', 'job']
     
 >>> x=limiter()    
 >>> x.age = 20
 

12. Функтор

Функтор — это класс, имеющий метод __call__ — при этом объект можно вызвать как функцию.

Пример. Пусть у нас имеется класс Person, имеется коллекция объектов этого класса- people, нужно отсортировать эту коллекцию по фамилиям. Для этого можно использовать функтор Sortkey:

class SortKey:
   def __init__(self, *attribute_names):
       self.attribute_names = attribute_names
 
   def __call__(self, instance):
       values = []
       for attribute_name in self.attribute_names:
           values.append(getattr(instance, attribute_name))
       return values
  
 class Person:
   def __init__(self, forename, surname, email):
       self.forename = forename
       self.surname = surname
       self.email = email
  
 >>> people=[]
 >>> p=Person('Petrov','','')
 >>> people.append(p)
 >>> p=Person('Sidorov','','')
 >>> people.append(p)
 >>> p=Person(u'Ivanov','','')
 >>> people.append(p)
 >>> for p in people:
 ...   print p.forename
 Petrov
 Sidorov
 Ivanov
 
 >>> people.sort(key=SortKey("forename"))
 >>> for p in people:
 ...   print p.forename
 Ivanov
 Petrov
 Sidorov
 

13. Дескриптор

Дескриптор — это класс, который хранит и контролирует атрибуты других классов. Вообще любой класс, который имплементирует один из специальных методов — __get__ , __set__ , __delete__, является дескриптором.

Пример:

class ExternalStorage:
   __slots__ = ("attribute_name",)
   __storage = {}
 
   def __init__(self, attribute_name):
       self.attribute_name = attribute_name
 
   def __set__(self, instance, value):
       self.__storage[id(instance), self.attribute_name] = value
 
   def __get__(self, instance, owner=None):
       if instance is None:
           return self
       return self.__storage[id(instance), self.attribute_name]
      
 class Point:
   __slots__ = ()
   x = ExternalStorage("x")
   y = ExternalStorage("y")
   def __init__(self, x=0, y=0):
     self.x = x
     self.y = y
         
 >>> p1=Point(1,2)       
 
 >>> p2=Point(3,4)       
 

В данном случае класс Point не имеет собственных атрибутов x, y, хотя вызывает их так, как будто они есть — на самом деле они хранятся в дескрипторе ExternalStorage.

14. Sequence

Последовательность реализуется с помощью методов __getitem__, __setitem__. В данном примере класс MySequence возвращает по индексу элемент последовательности неопределенной длины, представляющей собой арифметическую прогрессию вида: 1 3 5 7 ... Здесь нельзя применить стандартные методы __del__ , __len__:

class MySequence:
   def __init__(self, start=0, step=1):
       self.start = start                         
       self.step = step                           
       self.changed = {}                          
   def __getitem__(self, key):
       return self.start + key*self.step    
   def __setitem__(self, key, value):
       self.changed[key] = value                  
    
 >>> s = MySequence(1,2)
 >>> s[0]
 1
 >>> s[1]
 3
 >>> s[100]
 201
 

Заключение

Сегодня мы узнали, что классы в питоне имеют большой набор встроенных методов и атрибутов, которые позволяют гибко использовать модель объектно-ориентированного программирования и упрощают решение стандартных задач и алгоритмов. Методы могут быть статическими в зависимости от природы объекта, что позволяет смешивать объектно-ориентированную и функциональную архитектуру. Вызов методов базового класса имеет собственную семантику. Последовательности и мапы, реализованные на базе итераторов, экономят ресурсы и память. Проперти упрощают сложную реализацию атрибутов класса. Функторы обращаются с коллекцией объектов пользовательского типа так, как будто это стандартные типы. Дескрипторы реализуют различную логику хранения атрибутов класса. В продолжение цикла расскажем о работе с файловой системой средствами Python.

Файловая система

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

Сегодня мы рассмотрим следующие темы.

  1. Как открыть файл.
  2. Базовые файловые методы.
  3. Стандартный ввод/вывод.
  4. Произвольный доступ.
  5. Построчная работа с файлами.
  6. Закрытие файла.
  7. Итерация.
  8. Pickling.
  9. Бинарные файлы – модуль struct.
  10. Работа с файловой системой.

1. Как открыть файл

Открыть файл можно с помощью функции open:

  open(name[, mode[, buffering]])
 
 

Функция возвращает файловый объект. Обязателен только первый аргумент. Если остальные параметры отсутствуют, файл будет доступен на чтение. Таблица режимов (mode) функции open:

'r' – чтение.

'w' – запись.

'a' – добавление.

'b' – бинарный режим.

'+' – чтение/запись.

Режим '+' может быть добавлен к остальным режимам. По умолчанию питон открывает файлы в текстовом режиме. Для открытия файла в бинарном режиме на чтение можно добавить 'rb'. Третий параметр устанавливает размер буферизации при работе с файлом. По умолчанию он выключен, и чтение/запись идет напрямую с диска на диск. Для включения буфера третий параметр должен быть отличным от нуля.

2. Базовые файловые методы

В питоне многие объекты являются файлами: стандартный ввод sys.stdin, стандартный вывод sys.stdout, объекты, открываемые функцией urllib.urlopen и т.д.

Запись в файл:

>>> f = open('my_file', 'w')
 >>> f.write('Hello, ')
 >>> f.write('World!')
 
 >>> f.close()
 

Чтение:

>>> f = open('my_file', 'r')
 >>> f.read(5)
 'Hello'
 >>> f.read()
 ', World!'
 

3. Стандартный ввод/вывод

В командной строке можно записать подряд несколько команд, передавая результат работы от одной команды к другой по конвейеру – или по каналу (pipe):

  cat my_file | python test.py
 

Первая команда – cat – пишет содержимое текстового файла my_file на стандартный вывод sys.stdout . Вторая команда запускает питоновский файл, который читает стандартный ввод sys.stdin , подсчитывает в нем количество слов и выводит результат:

test.py:
 import sys
 text = sys.stdin.read()
 words = text.split()
 wordcount = len(words)
 print 'Wordcount:', wordcount
 

Канал – или пайп (pipe) – это конструкция, объединяющая стандартный вывод со стандартным вводом и позволяющая обмениваться данными между двумя командами.

4. Произвольный доступ

По умолчанию метод read() читает данные последовательно по порядку, от начала и до конца файла. Для произвольного доступа к файлу есть функция seek:

  seek(offset[, whence])
 

offset – смещение в байтах относительно начала файла;

whence – по умолчанию равен нулю, указывает на то, что смещение берется относительно начала файла.

Пример:

>>> f = open(r'my_file', 'w')
 >>> f.write('01234567890123456789')
 >>> f.seek(5)
 >>> f.write('Hello, World!')
 
 >>> f.close()
 >>> f = open(r'my_file')
 >>> f.read()
 '01234Hello, World!89'
 

Функция tell() возвращает текущую позицию файла.

5. Построчная работа с файлами

Обычно мы имеем дело с текстовыми файлами. Прочитать одну строку:

  file.readline()
 
 

Функция readline() без параметра читает всю строку, наличие параметра указывает функции максимальное число символов строки, которое будет прочитано. Прочитать все строки и вернуть список строк:

  file.readlines()
 

Записать строки в файл:

  file.writelines()
 

Пример. Прочитать файл и записать его содержимое в другой файл:

f = open(r'my_file')
 lines = f.readlines()
 f.close()
 lines[0] = "This is a my_file2 \n" # изменяем 1-ю строку
 f = open(r'my_file2','w')
 f.writelines(lines)
 f.close()
 

6. Закрытие файла

Для закрытия файла есть метод close(). Обычно файл закрывается сам после того, как вы выходите из программы, но файлы нужно закрывать вручную по нескольким причинам.

  1. Питон может буферизировать запись в файл ваших данных, что может привести к неожиданным эффектам и возникновению ошибок.
  2. У операционной системы есть ограничение на число одновременно открытых файлов.
  3. При доступе к файлу из разных мест одновременно и на чтение, и на запись необходимо синхронизировать файловые операции. Буферизация записи может привести к тому, что запись уже произошла, а данных в файле еще нет.

Для полной уверенности в закрытии файла можно использовать блок try/finally:

  try:
      # Тут идет запись в файл
   finally:
      file.close()
 

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

 with open("my_file") as somefile:
     do_something(somefile)
 
 

Если вы все же не хотите закрывать файл, то синхронизировать многопользовательский доступ к файлу на чтение/запись можно с помощью функции flush(), которая актуализирует все операции записи на диск. При этом возможна блокировка файла на чтение.

7. Итерация

Итерация по файлу является базовой операцией и имеет множество вариантов. Использование функции read() для байтового чтения:

f = open(filename)
 while True:
   char = f.read(1)
   if not char: break
   process(char)
 f.close()
 

Построчное чтение текстовых файлов и функция readline():

f = open(filename)
 while True:
   line = f.readline()
   if not line: break
   process(line)
 f.close()
 

Файл сам может выступать в роли итератора:

for line in open(filename):
   process(line)
 

8. Pickling

Практически любой тип объекта может быть сохранен на диске в любой момент его жизни, а позже прочитан с диска. Для этого есть модуль pickle:

import pickle
 t1 = [1, 2, 3]
 s = pickle.dumps(t1)
 t2 = pickle.loads(s)
 print t2
 [1, 2, 3]
 

Здесь есть небольшой нюанс: t1 и t2 будут двумя разными объектами, хотя и идентичными.

9. Бинарные файлы

Стандартный модуль struct позволяет преобразовывать объекты в структуры C в виде строк в бинарном формате и обратно. Данные в строке располагаются в соответствии со строкой формата. Эти возможности могут быть использованы для чтения и сохранения в двоичном формате.

Функции этого модуля:

   
    pack(format, value1, value2 ...)
 

Возвращает строку, содержащую значения value1 ..., упакованные в соответствии с форматом. Количество и тип аргументов должны соответствовать значениям, которые требует строка формата format.

   unpack(format, string)
 

Распаковывает строку string в соответствии с форматом format и возвращает кортеж объектов.

   calcsize(format)
 

Возвращает размер структуры (т.е. длину строки), соответствующей формату format.


Таблица основных форматов
 ===========================
 Format      C Type          Python 	
 ===========================
 c             char               string of length 1 	 
 ?             Bool              bool 	
 i 	   int                  integer 	 
 l 	   long               integer 	 
 f 	   float               float 	 
 d 	   double           float 	 
 s 	   char[]            string 	 
 

Перед символом формата может идти число, обозначающее количество повторений. Например, строка формата '4h' полностью эквивалентна строке 'hhhh'. Символы пропуска между символами формата игнорируются, однако символы пропуска между числом и символом формата не допускаются.

Число перед символом формата 's' интерпретируется как длина строки, а не число повторений. То есть '10s' обозначает строку из 10 символов, в то время как '10c' – 10 раз по одному символу.

Можно изменить порядок следования байтов вручную:

  < - little-endian
   > - big-endian
 
 

В следующем примере мы упаковываем в структуру два числа – целое и float, строку из пяти символов, сохраняем в бинарный файл, а потом извлекаем из файла:

from struct import *
 out = open("123.bin", "wb")    
 format = "if5s"                
 data   = pack(format, 24,12.48,'12345')
 out.write(data)
 out.close()
 input = open("123.bin", "rb")
 data = input.read()
 input.close()
 format = "if5s"                   # one integer
 value,value2,value3 = unpack(format, data) # note the ',' in 'value,': 
 	unpack apparently returns a n-uple
 print value
 print value2
 print value3
 print calcsize(format)
 
 >>> 24
 >>> 12.4799995422
 >>> 12345
 >>> 13
 

10. Работа с файловой системой

Стандартный модуль os имеет интерфейс работы с файловой системой. Каждая программа имеет текущий каталог. Функция os.getcwd возвращает текущий каталог:

import os
 cwd = os.getcwd()
 print cwd
 

Проверить наличие файла в текущем каталоге:

  os.path.exists('my_file')
 

Вывести список файлов и подкаталогов для данного каталога:

  os.listdir(path)
 

Следующий пример рекурсивно выводит список всех файлов и подкаталогов для данного каталога:

import os
 def walk(dir):
   for name in os.listdir(dir):
     path = os.path.join(dir, name)
     if os.path.isfile(path):
         print path
     else:
         walk(path)
 walk(path)
 
 

В следующем примере мы получим статистическую информацию о текущем каталоге: общий размер каталога в байтах, число файлов, число подкаталогов. Стандартная функция os.path.walk имеет три параметра: каталог, пользовательская функция, список для подсчета:

import os, sys
 def getlocaldata(sms,dr,flst):
    for f in flst:
       fullf = os.path.join(dr,f)
       if os.path.islink(fullf): continue # don't count linked files
       if os.path.isfile(fullf):
           sms[0] += os.path.getsize(fullf)
           sms[1] += 1
       else:
           sms[2] += 1
 def dtstat(dtroot):
    sums = [0,0,1] # 0 bytes, 0 files, 1 directory so far
    os.path.walk(dtroot,getlocaldata,sums)
    return sums
 
 report = dtstat('.')
 print report
 

В следующем примере сделана интерпретация системной утилиты grep. В текущем каталоге будут найдены файлы с питоновским расширением, в которых будет найдена поисковая строка 'import os':

import os, sys, fnmatch
 
 mask = '*.py'
 pattern = 'import os'
 
 def walk(arg,dir,files):
    for file in files:
      if fnmatch.fnmatch(file,mask):
         name = os.path.join(dir,file)
         try:
           data = open(name,'rb').read()
           if data.find(pattern) != -1:
             print name
         except:
             pass    
 os.path.walk('.',walk,[])
 

Заключение

Сегодня мы узнали, что файловые объекты поддерживают чтение/запись. Для корректной работы с данными файл нужно программно закрывать. Файлы можно открывать в различных режимах. Стандартный ввод/вывод – это тоже файлы. Можно построчно читать и писать в файл. К файлам можно применять байтовую и построчную итерацию. Любые объекты могут быть сохранены на диске в произвольный момент времени в произвольном состоянии и позже восстановлены путем считывания с диска. Чтение/запись можно выполнять в бинарном режиме, соблюдая совместимость со структурами на языке си. Интерфейс с операционной системой позволяет писать компактные программы, дополняющие стандартные утилиты операционной системы.

Процессы и потоки

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

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

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

Сегодня мы рассмотрим следующие темы.

  • Как работают процессы.
  • Как работают потоки в питоне.
  • Создание потока.
  • Очереди (Queue).
  • Блокировки (Lock).

1. Как работают процессы

В питоне есть стандартный модуль subprocess, который упрощает управление другими программами, передавая им опции командной строки и организуя обмен данными через каналы (pipe). Мы рассмотрим пример, в котором пользователь запускает программу из командной строки, которая в свою очередь запустит несколько дочерних программ. В данном примере два скрипта – рarent.py и child.py. Запускается parent.py. Child.py выступает в роли аргумента command, который передается в запускаемый процесс. У этого процесса есть стандартный вход, куда мы передаем два аргумента – поисковое слово и имя файла. Мы запустим два экземпляра программы child.py, каждый экземпляр будет искать слово word в своем файле – это будут файлы исходников самих программ. Запись на стандартный вход осуществляет модуль subprocess. Каждый процесс пишет результат своего поиска в консоль. В главном процессе мы ждем, пока все child не закончат свою работу.

Код parent.py:

 
 import os
 import subprocess
 import sys
 
 
 child = os.path.join(os.path.dirname(__file__), "./child.py")
 word  = 'word'
 file = ['./parent.py','./child.py']
 
 pipes = []
 for i in range(0,2):
   command = [sys.executable, child]
   pipe = subprocess.Popen(command, stdin=subprocess.PIPE)
   pipes.append(pipe)
   pipe.stdin.write(word.encode("utf8") + b"\n")
   pipe.stdin.write(file[i].encode("utf8") + b"\n")
   pipe.stdin.close()
 
 while pipes:
     pipe = pipes.pop()
     pipe.wait()
 

Код child.py:

 
 import sys
 
 word = sys.stdin.readline().rstrip()
 filename = sys.stdin.readline().rstrip()
 
 try:
   with open(filename, "rb") as fh:
     while True:
       current = fh.readline()
       if not current:
           break
       if (word in current ):
           print("find: {0} {1}".format(filename,word))
 except :
     pass
 

2. Как работают потоки в питоне

Если нужно, чтобы ваше приложение выполняло несколько задач в одно и то же время, то можете воспользоваться потоками (threads). Потоки позволяют приложениям выполнять в одно и то же время множество задач. Многопоточность (multi-threading) важна во множестве приложений, от примитивных серверов до современных сложных и ресурсоёмких игр.

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

  for x in L
 

а второй в этот момент начнет удалять значения из этого списка. Тут может произойти все что угодно: программа может упасть, или мы просто получим неверные данные.

Решением в этом случае является применение блокировки. При этом доступ к заблокированному списку будет иметь только один поток, второй будет ждать, пока блокировка не будет снята.

Применение блокировки порождает другую проблему – дедлок (deadlock) – мертвая блокировка. Пример дедлока: имеется два потока и два списка. Первый поток блокирует первый список, второй поток блокирует второй список. Первый поток изнутри первой блокировки пытается получить доступ к уже заблокированному второму списку, второй поток пытается проделать то же самое с первым списком. Получается неопределенная ситуация с бесконечным ожиданием. Эту ситуации легко описать, на практике все гораздо сложнее.

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

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

Питоновская реализация многопоточности ограниченная. Интерпретатор питона использует внутренний глобальный блокировщик (GIL), который позволяет выполняться только одному потоку. Это сводит на нет преимущества многоядерной архитектуры процессоров. Для многопоточных приложений, которые работают в основном на дисковые операции чтения/записи, это не имеет особого значения, а для приложений, которые делят процессорное время между потоками, это является серьезным ограничением.

3. Создание потока

Для создания потоков мы будем использовать стандартный модуль threading. Есть два варианта создания потоков:

вызов функции

  threading.Thread()
 

вызов класса

  threading.Thread
 

Следующий пример показывает, как к потоку приаттачить функцию через вызов функции:

import threading
 import time
 def clock(interval):
     while True:
         print("The time is %s" % time.ctime())
         time.sleep(interval)
 t = threading.Thread(target=clock, args=(15,))
 t.daemon = True
 t.start()
 

Пример на создание потока через вызов класса: в конструкторе обязательно нужно вызвать конструктор базового класса. Для запуска потока нужно выполнить метод start() объекта-потока, что приведет к выполнению действий в методе run():

import threading
 import time
 class ClockThread(threading.Thread):
     def __init__(self,interval):
         threading.Thread.__init__(self)
         self.daemon = True
         self.interval = interval
     def run(self):
         while True:
             print("The time is %s" % time.ctime())
             time.sleep(self.interval)
 t = ClockThread(15)
 t.start()
 

Для управления потоками существуют методы:

start() – дает потоку жизнь.

run() –этот метод представляет действия, которые должны быть выполнены в потоке.

join([timeout]) – поток, который вызывает этот метод, приостанавливается, ожидая завершения потока, чей метод вызван. Параметр timeout (число с плавающей точкой) позволяет указать время ожидания (в секундах), по истечении которого приостановленный поток продолжает свою работу независимо от завершения потока, чей метод join был вызван. Вызывать join() некоторого потока можно много раз. Поток не может вызвать метод join() самого себя. Также нельзя ожидать завершения еще не запущенного потока.

getName() – возвращает имя потока.

setName(name) – присваивает потоку имя name.

isAlive() – возвращает истину, если поток работает (метод run() уже вызван).

isDaemon() – возвращает истину, если поток имеет признак демона.

setDaemon(daemonic) – устанавливает признак daemonic того, что поток является демоном.

activeCount() – возвращает количество активных в настоящий момент экземпляров класса Thread. Фактически это len(threading.enumerate()).

currentThread() – возвращает текущий объект-поток, т.е. соответствующий потоку управления, который вызвал эту функцию.

enumerate() – возвращает список активных потоков.

4. Очереди (Queue)

В следующем примере будет решена аналогичная задача, что и в предыдущем примере с процессами: будут запущены три потока, каждый из которых будет работать по принципу утилиты grep. Имеется глобальный ресурс – work_queue – список файлов для поиска, который мы положим в очередь. Для этого будет использован объект Queue, который имеет встроенную блокировку:

 
 import threading
 import Queue
 
 class Worker(threading.Thread):
 
     def __init__(self, work_queue, word):
         super(Worker,self).__init__()
         self.work_queue = work_queue
         self.word = word
 
     def run(self):
         try:
             filename = self.work_queue.get()
             self.process(filename)
         finally:
             pass
 
     def process(self, filename):
         previous = "
         current=True
         with open(filename, "rb") as fh:
             while current:
                 current = fh.readline()
                 if not current: break
                 current = current.decode("utf8", "ignore")
                 if self.word in current :
                     print("find {0}: {1}".format(self.word,filename))
                 previous = current
 
 word = 'import'
 filelist = ['./file1.py','./file2.py','./file3.py']
 work_queue = Queue.Queue()
 for filename in filelist:
     work_queue.put(filename)
 for i in range(3):
     worker = Worker(work_queue, word)
     worker.start()
 

5. Блокировки (Lock)

В следующем примере будут созданы три потока, каждый из которых будет считывать стартовую страницу по указанному Web-адресу. В примере имеется глобальный ресурс – список урлов – url_list – доступ к которому будет блокироваться с помощью блокировки threading.Lock(). Объект Lock имеет методы:

acquire([blocking=True]) – делает запрос на запирание замка. Если параметр blocking не указан или является истиной, то поток будет ожидать освобождения замка.

Если параметр не был задан, метод не возвратит значения.

Если blocking был задан и истинен, метод возвратит True (после успешного овладения замком).

Если блокировка не требуется (т.е. задан blocking=False), метод вернет True, если замок не был заперт и им успешно овладел данный поток. В противном случае будет возвращено False.

release() – запрос на отпирание замка.

locked() – возвращает текущее состояние замка (True – заперт, False – открыт).

import threading
 from urllib import urlopen
 
 class WorkerThread(threading.Thread):
   def __init__(self,url_list,url_list_lock):
     super(WorkerThread,self).__init__()
     self.url_list=url_list
     self.url_list_lock=url_list_lock
     
   def run(self):
     while (1):
       nexturl = self.grab_next_url()
       if nexturl==None:break
       self.retrieve_url(nexturl)
         
   def grab_next_url(self):
     self.url_list_lock.acquire(1)
     if len(self.url_list)<1:
       nexturl=None
     else:
       nexturl = self.url_list[0]
       del self.url_list[0]
     self.url_list_lock.release()
     return nexturl  
         
         
   def retrieve_url(self,nexturl):
     text = urlopen(nexturl).read()
     print text
     print '################### %s #######################' % nexturl
     
 url_list=['http://linux.org.ru','http://kernel.org','http://python.org']
 url_list_lock = threading.Lock()
 workerthreadlist=[]
 for x in range(0,3):
   newthread = WorkerThread(url_list,url_list_lock)
   workerthreadlist.append(newthread)
   newthread.start()
 for x in range(0,3):
   workerthreadlist[x].join()
 

Заключение

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

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

Сетевое программирование

Компьютерный мир глобализируется на основе сетевых коммуникаций и протоколов. Интернет становится обязательным атрибутом повседневности. Все больше появляется приложений, ориентированных на сеть: это серверы баз данных, сетевые игры, различные сетевые протоколы, Web-серверы, апплеты, сервлеты, CGI-скрипты и т.д. Более того, сеть – это уже компьютер в том случае, когда используется распределенная кластерная архитектура вычислений. В этой статье речь пойдет о сетевом программировании на Python. Модуль socket предлагает простой интерфейс для использования сокетов. Программисты на C/C++ найдут, что здесь реализация сокетов значительно проще. В Python есть и другие сетевые модули: httplib, ftplib, telnetlib, smtplib, реализующие различные сетевые протоколы. Кроме того, в статье значительное внимание будет уделено инструментарию twisted, который еще в большей степени унифицирует рутинные операции, связанные с особенностями сетевого программирования.

Сегодня мы рассмотрим следующие темы.

  1. Как написать простой TCP клиент-сервер.
  2. Архитектура TCP-сервера.
  3. Что такое Twisted.
  4. Twisted протокол.
  5. Twisted фабрика.
  6. Twisted клиент-сервер.
  7. Twisted чат-сервер.

1. TCP клиент-сервер

TCP – стандартный протокол для межсетевого взаимодействия. Его основным достоинством является принцип гарантированной доставки – все пакеты, посланные сервером, будут доставлены клиенту.

Напишем простое клиент-серверное приложение. Для этого нам нужно импортировать класс socket из стандартной библиотеки, в котором есть все методы для организации соединения. Клиент посылает строку на сервер, сервер получает ее и отсылает клиенту обратно.

Код простого сервера: вначале мы создаем сокет, представляющий собой указатель на объект соединения. Этому сокету мы передаем два аргумента: первый аргумент говорит о том, что это интернет-сокет, второй – что мы используем TCP-протокол.

Первый метод, который мы используем – bind(), он инициализирует ip-адрес и порт. При этом проверяется, не занят ли порт другой программой.

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

Третья функция – accept() – блокирует приложение до тех пор, пока не придет сообщение от клиента. Функция возвращает кортеж из двух параметров – объект самого соединения и адрес клиента.

Четвертая функция – recv() – читает данные из сокета. Аргумент устанавливает максимальное количество байтов в сообщении.

Пятая функция – send() – отсылает данные клиенту.

Шестая функция – close() – закрывает сокет.

Функция raw_input() просто блокирует клавиатуру.

import socket
 import sys
 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 host = 'localhost'
 port = 8007
 s.bind(host, port)
 s.listen(1)
 conn, addr = s.accept()
 data = conn.recv(1000000)
 print 'client is at', addr , data
 conn.send(data)
 z = raw_input()
 conn.close()
 

Клиент вначале создает точно такой же сокет, что и сервер. Первый клиентский метод – connect() – позволяет соединиться с сервером. Второй метод – send() – отсылает данные на сервер. Третий метод – recv() – получает данные с сервера. Четвертый метод – close() – закрывает сокет.

   
 import socket
 import sys
 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 host = 'localhost'
 port = 8007
 s.connect((host, port))
 s.send('hello')  
 data = s.recv(1000000) 
 print 'received', data, len(data), 'bytes'
 s.close()
 

Что стоит за этими простыми методами? По сути, они представляют собой оболочки (wrappers) для аналогичных системных вызовов. Так, метод socket.send() фактически вызывает вызов send() операционной системы. Посылаемые данные копируются в буфер операционной системы и при этом могут разбиваться на отдельные блоки (chunk). После того как последний блок будет скопирован в буфер, функция send() вернет управление программе, при этом совсем не факт, что все данные уже уйдут по назначению и будут получены на том конце клиентом. Клиент по дороге может обнаружить, что отдельные блоки пропали, и запросит их у сервера повторно. Но это уже не наша забота – за полную гарантированную отсылку данных отвечает операционная система, а не приложение.

2. Архитектура TCP-сервера

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

  1. использование отдельного потока на каждого клиента;
  2. использование неблокирующих сокетов;
  3. использование select/poll.

В Python неблокирующий сокет реализуется с помощью специального метода setblocking() с параметром, равным нулю.

Пример:

lstn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 cs = []
 nc = 2
 for i in range(nc):
    (clnt,ap) = lstn.accept()
    clnt.setblocking(0)
    cs.append(clnt)
 

Недостаток этого метода в том, что нам вручную нужно проверять готовность каждого клиента. Третий вариант с использованием select() позволяет переложить эту проверку на саму операционную систему. Более поздняя его вариация – функция poll().

3. Twisted

Twisted – кросс-платформенная сетевая библиотека, написанная на Python. Это асинхронный инструмент, который избавляет вас от необходимости использовать потоки. Он поддерживает работу с mail, web, news, chat, DNS, SSH, Telnet, RPC, и т.д.

Многие дистрибутивы Linux уже включают в себя twisted. Можно установить инструментарий из исходных текстов, которые лежат тут: http://twistedmatrix.com/projects/core/

В основе Twisted лежат события – event. Работой таких событий управляют специальные функции, называемые хэндлерами – event handler. Есть специальный бесконечный цикл по обработке событий – так называемый event loop. Он отлавливает события, после чего запускает соответствующие хэндлеры. После этого кошмар с последовательной обработкой событий заканчивается. За работу цикла event loop в twisted отвечает объект, называемый reactor, который находится в модуле twisted.internet . Для его запуска нужно вызвать команду:

  reactor.run()  
 

4. Twisted Protocol

Для написания сетевого приложения нужно использовать класс Protocol, который находится в twisted.internet.protocol.Protocol. Большинство протоколов унаследованы от этого класса. Они никогда не ждут события, обрабатывая их по мере появления. Вот простой пример:

from twisted.internet.protocol import Protocol
   class Echo(Protocol):
     def dataReceived(self, data):
       self.transport.write(data)
 

Это простейший протокол. Он просто пишет назад то, что пишет ему клиент, и не отвечает ни на какие события. Вот пример протокола, отвечающего на событие connect:

      
 from twisted.internet.protocol import Protocol
 class Connector(Protocol):
   def connectionMade(self):
     self.transport.write("Connection made ... \r\n") 
     self.transport.loseConnection()
 

Этот протокол отвечает на соединение тем, что обрывает его. Событие connectionMade происходит при коннекте. Событие connectionLost срабатывает при разрыве коннекта.

5. Twisted Factory

Конфигурация поведения протокола прописывается в фабрике – классе Factory, который унаследован от twisted.internet.protocol.Factory. В программе может быть несколько протоколов, фабрика является для них организующим и конфигурационным компонентом. По умолчанию фабрика запускает каждый протокол, и устанавливает ему атрибут, называемый factory, который указывает на себя. Пример фабрики, которая позволяет протоколу писать лог-файл:

    
 from twisted.internet.protocol import Factory
 from twisted.protocols.basic import LineReceiver
  
 class LoggingProtocol(LineReceiver):
     def lineReceived(self, line):
         self.factory.fp.write(line+'\n')
 
 class LogfileFactory(Factory):
     protocol = LoggingProtocol
     def __init__(self, fileName):
         self.file = fileName
     def startFactory(self):
         self.fp = open(self.file, 'a')
     def stopFactory(self):
         self.fp.close()
 

6. Twisted Client-Server

Код простого сервера: на событие dataReceived сервер выводит полученные данные и тут же отсылает их обратно:

from twisted.internet.protocol import Factory, Protocol
 from twisted.internet import reactor
  
 class Server(Protocol):
   def connectionMade(self):
     self.transport.write(self.factory.quote+'\r\n')
   def connectionLost(self, reason):
     print 'connection lost ...'
   def dataReceived(self, data):
     print data
     self.transport.write(data)
   
 class ServerFactory(Factory):
   protocol = Server
   def __init__(self, quote=None):
     self.quote = quote 
 
 reactor.listenTCP(8007, ServerFactory("quote"))
 reactor.run()
 

В этом примере клиент делает два отложенных асинхронных сообщения с интервалом в одну секунду, используя метод реактора callLater:

    
 from twisted.internet import reactor
 from twisted.internet.protocol import Protocol, ClientCreator
  
 class Client(Protocol):
   def sendMessage(self, msg):
     self.transport.write("%s\n" % msg)
     for i in range(1,5):
       self.transport.write("%d\n" % i)
   def dataReceived(self, data):
     print data
  
 def gotProtocol(p):
     p.sendMessage("Hello")
     reactor.callLater(1, p.sendMessage, "world")
     reactor.callLater(2, p.transport.loseConnection)
  
 c = ClientCreator(reactor, Client)
 c.connectTCP("localhost", 8007).addCallback(gotProtocol)
 reactor.run()
 

Вывод сервера:

Hello
 1
 2
 3
 4
 world
 1
 2
 3
 4
 connection lost ...
 

7. Twisted чат-сервер

Чат-сервер – это сервер, который делает широковещательную рассылку всех сообщений, которые были посланы клиентами. Сервер анализирует клиентские сообщения и в зависимости от их типа может сообщить остальным о том, что клиент зашел под своим именем, может просто разослать всем клиентское сообщение, либо отключает клиента. Список клиентов хранится в фабрике и называется clientProtocols. При каждом новом коннекте клиента в методе connectionMade протокола происходит добавление объекта ChatProtocol в этот список. Код простого чат-сервера:

  
 from twisted.internet import reactor
 from twisted.internet.protocol import ServerFactory 
 from twisted.protocols.basic import LineOnlyReceiver 
 
 class ChatProtocol(LineOnlyReceiver): 
 
     name = "" 
 
     def getName(self): 
         if self.name!="": 
             return self.name 
         return self.transport.getPeer().host 
 
     def connectionMade(self): 
         print "New connection from "+self.getName() 
         self.sendLine("Welcome to my my chat server.") 
         self.sendLine("Send '/NAME [new name]' to change your name.") 
         self.sendLine("Send '/EXIT' to quit.") 
         self.factory.sendMessageToAllClients(self.getName()+" has joined the party.") 
         self.factory.clientProtocols.append(self)
 
     def connectionLost(self, reason): 
         print "Lost connection from "+self.getName() 
         self.factory.clientProtocols.remove(self) 
         self.factory.sendMessageToAllClients(self.getName()+" has disconnected.") 
 
     def lineReceived(self, line): 
         print self.getName()+" said "+line 
         if line[:5]=="/NAME": 
             oldName = self.getName() 
             self.name = line[5:].strip() 
             self.factory.sendMessageToAllClients(oldName+" changed name 
 				to "+self.getName()) 
         elif line=="/EXIT": 
             self.transport.loseConnection() 
         else: 
             self.factory.sendMessageToAllClients(self.getName()+" says "+line) 
 
     def sendLine(self, line): 
         self.transport.write(line+"\r\n") 
 
 class ChatProtocolFactory(ServerFactory): 
 
     protocol = ChatProtocol 
 
     def __init__(self): 
         self.clientProtocols = [] 
 
     def sendMessageToAllClients(self, mesg): 
         for client in self.clientProtocols:
             client.sendLine(mesg) 
 
 print "Starting Server"
 factory = ChatProtocolFactory()
 reactor.listenTCP(12345, factory)
 reactor.run() 
 

В качестве клиента можно использовать telnet:

>> telnet localhost 12345
 

Заключение

Сегодня мы узнали, что TCP протокол – это надежный сетевой протокол с гарантированной доставкой. При написании TCP клиент-серверных приложений функции протокола распределяются между приложением и самой операционной системой. В зависимости от архитектуры сервера, сокеты могут быть блокирующими и неблокирующими. Системные вызовы select() и poll() позволяют писать высоконагруженные серверы с подключением большого количества клиентов. Инструментарий twisted значительно облегчает труд по написанию сетевых приложений, предоставляя программисту возможность сфокусироваться на логике приложения, скрывая при этом низкоуровневые подробности сетевого протокола.

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

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

 Автор  Комментарий к данной статье
Гаврила
  http:www.ibm.comdeveloperworksrulibraryl-python_part_2

И зачем было все это копировать?
2013-02-04 16:24:49
Олег Цилюр
  Здесь гораздо больше, чем было на сайте IBM - вот зачем было всё это копировать. 
2013-09-03 21:46:56