среда, марта 19, 2008

Доклад по Python: часть I

1. Что такое?..

Python - это интерпретируемый алгоритмический объектно-ориентированный язык со строгой динамической типизацией, полиморфизм в нем реализован в виде Duck Typing.

Трансляция питона организована очень схожим с Java образом. Именно, исходник компилируется в байт-код, а затем этот байт-код исполняется. Это сходство настолько велико, что существует реализация Питона (Jython), генерирующая Java байт-код для исполнения виртуальной машиной Java. Различие состоит в политике, когда записывать байт-код на диск. Напомню, для Java традиционный способ запустить только что написанную программу такой: запускаем компилятор, подсовывая ему исходник - он генерирует байт-код и записывает его в файл. Затем запускаем виртуальную машину, подсовывая ей байт-код - и она его исполняет.
Питон же обычно не записывает байт-код на диск. В простейшем случае запуск программы происходит так: мы "скармливаем" исходник интерпретатору; он генерирует байт-код, но оставляет его в памяти, а затем передает виртуальной машине (являющейся частью интерпретатора). Это ускоряет запуск программы за счет отсутствия необходимости записывать байт-код на диск.
Однако, при загрузке (импорте) модулей Питон пытается сохранить байт-код, чтобы в следующий раз загрузка модуля происходила быстрее. Есть возможность и саму программу записать в виде байт-кода. Интерпретатору Питона можно подсовывать не только исходники, но и байт-код - он загрузится быстрее.
За счет такого сходства в устройстве с Java имеем и большое сходство в производительности (она примерно одинаковая, только Питон чуть быстрее загружает программы за счет того, что не пишет байт-код на диск).
И именно из-за этого сходства Питон обычно сравнивают именно с Явой.

Еще одно сходство с Явой (и многими другими интерпретируемыми и даже некоторыми компилируемыми языками) - это автоматическое управление памятью. В Питоне нет new[] и delete[], память отводится и освобождается автоматически. Алгоритм сборки мусора как бы "двухслойный": во-первых, сам интерпретатор реализует reference counting (удаляя объекты, на которые никто не ссылается), и во-вторых, есть время от времени запускаемый garbage collector, работающий по более замысловатым, но более быстрым и надежным алгоритмам (например, reference counting не удалит два объекта, ссылающихся друг на друга, даже если на них больше никто не ссылается).

Удобным свойством интерпретатора Питона является наличие REPL (read-eval-print loop), то есть возможности вводить языковые конструкции с консоли и тут же получать результат. Это часто используется для проверки каких-нибудь идей или для отладки.

1.1. Синтаксис.

В самых общих чертах синтаксис Питона напоминает С или Паскаль. Операторы записываются обычно по одному на строку. Присваивание записывается как в С, знаком =. Но при этом присваивание не возвращает значения, поэтому его нельзя использовать в условии. Для сравнений используются обычные для С знаки ==, !=, >,<.>=,<=. Небольшое отличие состоит в том, что Питон понимает "двойные" сравнения в "математическом" смысле, т.е. 0 < x < 10 понимается как 0 < x and x < 10. Основные конструкции проще показать на примерах:

if x < 0:
print "Yes"
elif x==0:
print "X is 0"
else:
print "No"

for i in [1,2,3]:
print i
else:
print "Loop was not break'ed"

while x>0:
print x
x -= 1

try:
z = x/y
except ZeroDivisionError,e:
print e
else:
print "Divided sucsessfully."
finally:
print "Some cleanup."

def fun(x,y):
return x+y

class Child(Parent):
x = 0
def method(self,x,y):
return self.x - x + y

f = open("abc.txt")
for line in f:
print "<%s>" % line
f.close()

В последнем примере показана замена функции sprintf: "строка" % (данные). В проектируемой сейчас версии Python3000 этот способ исчезнет, а вместо него появится другой: "Value {0}, Value {1}".format("one", 2).


1.2. Типы данных.

В Питоне выделяют атомарные и структурные (или ссылочные) типы данных. К атомарным типам относятся числа и строки. Структурные типы - это списки, кортежи (tuples), словари, функции, классы и экземпляры классов.

Данные некоторых типов (а именно - кортежи и строки) являются неизменяемыми.

Списки записываются так: [1, "one", 25]. Список может содержать любое количество объектов любого типа. Обращение к элементу списка - по индексу: a[2] (элементы нумеруются с нуля). Добавление элементов в список можно делать так:

a = a + [25]

или так (предпочтительней, т.к. быстрее):

a.append(25).

Кортежи записываются как значения через запятую: a,b,c. Часто для ясности кортежи приходится записывать в скобках: (a,b,c). Кортежи, как и списки, могут содержать значения любых типов, но, в отличие от списков, являются неизменяемыми.

Для строк, списков и кортежей есть специальный синтаксис для обращения к части данных, называемый срезами (впервые такой синтаксис появился еще в Фортране):

>>> a = "abcde"
>>> a[3:5]
'de'
>>> a[:2]
'ab'
>>> a[4:]
'e'
>>> a[:-2]
'abc'
>>> a[::-1]
'edcba'
>>> a[5:2:-1]
'ed'
>>> a[:]
'abcde'
>>> a[::2]
'ace'

Словари в Питоне - это структура, соответствующая хэшам в Перле, массивам в PHP и std::map в C++. Записываются так:

{'a': 1, 'b': 2, 'c': 3}

или так:

dict(a=1,b=2,c=3).

В качестве индексов в словарях могут быть использованы числа, строки, и вообще, любые объекты, имеющие метод __hash__() (например, кортежи). Обращение к элементам словаря выглядит так:

print d['key']

Метод keys() возвращает список из ключей словаря, а values() - список из значений. Порядок следования ключей не определен:

>>> d = dict(a=1,b=2,c=3)
>>> d.keys()
['a', 'c', 'b']
>>> d.values()
[1, 3, 2]

Функции создаются так:

def f(x):
"Documentation string (docstring)"
return x*x

или так:

f = lambda x: x*x

О классах и их экземплярах будем подробно говорить дальше.

2. Типизация.

Главное отличие Питона от Явы (если брать только чисто "лингвистические" свойства) - это типизация. Напомню, в Яве используется строгая статическая типизация с явным объявлением типов переменных. Это означает, что типы всех переменных известны в момент компиляции, и тогда же происходит проверка типов. Это дает преимущество в том плане, что значительная часть ошибок отлавливается в момент компиляции. Зато это замедляет компиляцию. В Питоне используется строгая динамическая типизация. "Динамическая" означает, что типы переменных становятся известными только во время выполнения, и тогда же выполняются проверки типов. Это дает больше возможностей написать неработающий код, но ускоряет компиляцию и дает значительную гибкость. Пример:

>> a = 20 # теперь a - это число, тип int
>> a = "a string" # а теперь - строка, тип str.

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

>>> x = [1,2,3] # x - это список
>>> y = x # y указывает туда же, куда x (копируется указатель).
>>> y[1] = 5 # изменяем элемент из y
>>> x # он изменился и в x.
[1, 5, 3]

Таким образом, в терминах C++, имя переменной - это ссылка (указатель), (void*). Исключением являются атомарные типы данных - числа и строки. Пример:

>>> a = 1
>>> b = a # здесь копируется уже не ссылка, а собственно значение
>>> b = 3 # присваиваем другое значение...
>>> a # на переменную a это не повлияло.
1

По той же схеме передаются аргументы в функцию.

За счет того, что информация о типе хранится вместе со значением, она доступна во время исполнения. Пример:

>>> a = 1
>>> type(a)
<type 'int'>
>>> a = "a string"
>>> type(a)
<type 'str'>

Таким образом, имеет смысл запись вроде if type(a) == str:...

10 комментариев:

  1. Анонимный3/19/2008 8:20 AM

    За счет такого сходства в устройстве с Java имеем и большое сходство в производительности (она примерно одинаковая, только Питон чуть быстрее загружает программы за счет того, что не пишет байт-код на диск).

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

    ОтветитьУдалить
  2. Спабоибо большое за статью, всё коротко и ясно.

    ОтветитьУдалить
  3. Весьма интересно, спасибо.

    ОтветитьУдалить
  4. Анонимный3/20/2008 8:25 AM

    сам интерпретатор реализует reference counting, и во-вторых, есть время от времени запускаемый garbage collector, работающий по более замысловатым, но более быстрым и надежным алгоритмам

    garbage collector работает медленнее reference counting

    ОтветитьУдалить

  5. >>> a = 1
    >>> b = a # здесь копируется уже не ссылка, а собственно значение
    >>> b = 3 # присваиваем другое значение...
    >>> a # на переменную a это не повлияло.
    1

    Не совсем верное утверждение. При первой операции копируется ссылка:

    Python 2.4.4 (#2, Jan 3 2008, 13:36:28)
    [GCC 4.2.3 20071123 (prerelease) (Debian 4.2.2-4)] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> a=1
    >>> id(a)
    135577008
    >>> b=a
    >>> id(b) # a, b указывают на один фрагмент памяти
    135577008
    >>> b=3
    >>> id(b) # a, b указывают теперь на разные фрагменты памяти
    135576984

    ОтветитьУдалить
  6. За счет такого сходства в устройстве с Java имеем и большое сходство в производительности (она примерно одинаковая, только Питон чуть быстрее загружает программы за счет того, что не пишет байт-код на диск).

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

    ОтветитьУдалить
  7. Очень хорошая статья.
    Только я бы обязательно добавил хоть несколько строк про отступы.
    А то, если человек только столкнулся с питоном, будет недоумевать, почему не работают примеры из этой же статьи

    ОтветитьУдалить
  8. Отличная статья, автору большое спасибо.
    Перехожу ко вторйо статье :)

    ОтветитьУдалить
  9. Анонимный2/19/2009 1:36 PM

    спасибо, лаконично и доступно

    ОтветитьУдалить