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:...
За счет такого сходства в устройстве с Java имеем и большое сходство в производительности (она примерно одинаковая, только Питон чуть быстрее загружает программы за счет того, что не пишет байт-код на диск).
ОтветитьУдалитьпри всей моей любви к питону должен признать это черезчур смелое утверждение...
Спабоибо большое за статью, всё коротко и ясно.
ОтветитьУдалитьВесьма интересно, спасибо.
ОтветитьУдалитьСуховато...
ОтветитьУдалитьсам интерпретатор реализует reference counting, и во-вторых, есть время от времени запускаемый garbage collector, работающий по более замысловатым, но более быстрым и надежным алгоритмам
ОтветитьУдалитьgarbage collector работает медленнее reference counting
ОтветитьУдалить>>> 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
За счет такого сходства в устройстве с Java имеем и большое сходство в производительности (она примерно одинаковая, только Питон чуть быстрее загружает программы за счет того, что не пишет байт-код на диск).
ОтветитьУдалитьДа, крайне странное утверждения. Питон в общем случае значительно медленнее хотя бы из-за динамической типизации.
Очень хорошая статья.
ОтветитьУдалитьТолько я бы обязательно добавил хоть несколько строк про отступы.
А то, если человек только столкнулся с питоном, будет недоумевать, почему не работают примеры из этой же статьи
Отличная статья, автору большое спасибо.
ОтветитьУдалитьПерехожу ко вторйо статье :)
спасибо, лаконично и доступно
ОтветитьУдалить