Обзор новых возможностей в Python 2.6 и 3.0

Автор: Евгений Ильин (aka Jenyay)
Московский Авиационный Институт

Источник: RSDN Magazine #2-2008
Материал предоставил: http://jenyay.net
Опубликовано: 26.08.2008
Версия текста: 1.0
Введение
Строки и Unicode
Функция print()
Форматирование строк
Свойства
Абстрактные базовые классы
Обработка исключений
Оператор with
Математика
Разное
Заключение

Введение

В сентябре 2008 года должны выйти сразу две версии языка Python – 2.6 и 3.0. Версия 3.0 потеряет обратную совместимость с линейкой 2.x. Облегчить переход на новую ветку должна версия 2.6, в которой будут реализованы основные возможности из Python 3.0, но в которой еще сохранится обратная совместимость с предыдущими версиями. Таким образом, в версии 2.6 уже можно будет пользоваться многими возможностями Python 3.0, но в то же время старый код будет продолжать работать, и будет время для перехода на Python 3.0.

В этой статье мы рассмотрим основные изменения, которые произошли в Python 2.6 и 3.0 по сравнению с Python 2.5. Для запуска примеров использовались первые бета-версии Python 2.6 и 3.0, поэтому к выходу финальной версии еще может что-то измениться.

Строки и Unicode

В Python 3.0 все строки стали юникодными. С формальной точки зрения, строки остались экземплярами класса str, а unicode исчез как класс. :) Не стало даже спецификатора u перед кавычками. В Python 2.6 по-прежнему остались два типа строк – str и unicode.

Кроме того, в Python 3.0 изменилась встроенная функция str(), теперь она, по сути, заменяет старую функцию unicode() и принимает те же три параметра – объект для преобразования в строку, строку, определяющую кодировку, и строку, определяющую поведение функции на случай, если str() не сможет преобразовать какие-то символы.

Функция print()

В Python 3.0 не стало ключевого слова print, зато появилась встроенная функция print(). В Python 2.6 ключевое слово print осталось, но добавилась возможность использовать одноименную функцию, для чего надо импортировать print_function из модуля __future__ :

			from __future__ import print_function
ПРИМЕЧАНИЕ

Импорт из модуля __future__ должен находиться в самом начале исходника, даже до импорта других модулей, иначе Python напишет такую ошибку:

File "H:\Черновики\Python 2.6 & 3.0\print_2.py", line 3

from __future__ import print_function

SyntaxError: from __future__ imports must occur at the beginning of the file

Функция print() определена следующим образом:

print([object, ...][, sep=' '][, end='\n'][, file=sys.stdout])

Сначала идут объекты, текстовое представление которых надо вывести. Затем идут необязательные именованные параметры:

Сравним результаты работы двух исходников (оба запускались в Python 2.6). В первом случае print – ключевое слово.

print ("test")
print ()
print "test1", "test2"
print ("test1", "test2")

В результате выполнения получим:

test
()
test1 test2
('test1', 'test2')

И то же самое, но если используется функция print():

			from __future__ import print_function

print ("test")
print ()
print ("test1", "test2")

Результатом работы этого кода будет:

test

test1 test2

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

В заключение этого раздела проиллюстрирую использование дополнительных параметров функции print():

			from __future__ import print_function

print ("test1", "test2", "test3")
print ("test4", "test5", "test6", sep = " # ")
print ("test7", "test8", "test9", end = "###")
print ("test10", "test11", "test12")

fp = file ("print_test.txt", "w")
print ("Тест1", "Тест2", "Тест3", file = fp)
fp.close()

Если запустить этот скрипт из-под Python 2.6, то в результате в консоли будет выведено:

test1 test2 test3
test4 # test5 # test6
test7 test8 test9###test10 test11 test12

Кроме того, будет создан файл с именем print_test.txt, который будет содержать строку "Тест1 Тест2 Тест3".

А вот Python 3.0 beta 1 отказывается выполнять строку:

print ("Тест1", "Тест2", "Тест3", file = fp)

ссылаясь на то, что у функции print() нет параметра file, хотя в справке он присутствует. Думаю, что это можно списать на глюки бета-версии.

Форматирование строк

В Python 2.6 и 3.0 появился новый способ форматирования строк, причем способ с помощью оператора % тоже работает. Новый способ форматирования напоминает функцию String.Format() из .NET Framework.

Теперь у класса str есть новый метод – format(), работу которого мы сейчас и рассмотрим. Думаю, что не стоит приводить полный синтаксис форматирования (его вы можете найти в документации), а лучше рассмотрим достаточно большое количество примеров.

Начнем с простого примера:

listval = [1.1, 2.2, 3.3]
print ("{0} | {1} | {2} | {3}".format (45, 123.4, "abyrvalg", listval))

В результате выполнения этого кода на экран будет выведен следующий текст:

45 | 123.4 | abyrvalg | [1.1000000000000001, 2.2000000000000002, 3.2999999999999998]

Таким образом, метод format() подставил вместо выражений {0}, {1}, ..., {3} значения параметров, которые были ему переданы. Заметьте, что в строке форматирования параметры в фигурных скобках не обязательно должны идти в той последовательности, в которой они передаются в метод format(). Например, вполне нормально будет работать следующий код:

print ("{1} | {0} | {3} | {2}".format (45, 123.4, "abyrvalg", listval))

В результате на экран будет выведена строка:

123.4 | 45 | [1.1000000000000001, 2.2000000000000002, 3.2999999999999998] | abyrvalg

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

>>> # Обычный десятичный формат
>>> print ("Decimal: {0:d}".format (45))
Decimal: 45

>>> # Двоичный формат
>>> print ("Binary: {0:b}".format (45))
Binary: 101101

>>> # Восьмиричный формат
>>> print ("Octal: {0:o}".format (45))
Octal: 55

>>> # Шестнадцатиричный формат
>>> print ("Hex: {0:x}".format (45))
Hex: 2d

>>> print ("Hex: {0:X}".format (45))
Hex: 2D

>>> # Основной формат, используемый по умолчанию
>>> print ("General: {0:g}".format (123.4))
General: 123.4

>>> # Экспоненциальный формат
>>> print ("Exponent: {0:e}".format (45))
Exponent: 4.500000e+01

>>> print ("Exponent: {0:E}".format (45))
Exponent: 4.500000E+01

>>> # Вывод процентов. Число будет умножено на 100
>>> print ("Percent: {0:%}".format (0.25))
Percent: 25.000000%

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

В следующей группе примеров для числа отводится 15 символов.

>>> # Используется выравнивание по умолчанию (по правому краю)
>>> print ("|{0:15}|".format (-19))
|            -19|

>>> # То же выравнивание можно задать явно с помощью символа '>'
>>> print ("|{0:>15}|".format (-19))
|            -19|

>>> # Выравнивание по левому краю задается с помощью символа '<'
>>> print ("|{0:<15}|".format (-19))
|-19            |

>>> # Выравнивание по центру задается с помощью символа '^'
>>> print ("|{0:^15}|".format (-19))
|      -19      |

>>> # Знак числа будет выровнен по левому краю, а само число по правому. Для этого используется символ '='
>>> print ("|{0:=15}|".format (-19))
|-            19|

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

>>> # Не используем выравнивание
>>> print ("|{0}|".format (-123.4))
|-123.4|

>>> # Задаем выравнивание
>>> print ("|{0:15}|".format (-123.4))
|       .0-123.4|

>>> print ("|{0:<15}|".format (-123.4))
|.0-123.4       |

>>> print ("|{0:^15}|".format (-123.4))
|   .0-123.4    |

>>> print ("|{0:=15}|".format (-123.4))
|       .0-123.4|

Но стоит явно указать, что форматировать надо в основном формате (g), как сразу все начинает работать как положено:

>>> print ("|{0:15g}|".format (-123.4))
|         -123.4|

>>> print ("|{0:<15g}|".format (-123.4))
|-123.4         |

>>> print ("|{0:^15g}|".format (-123.4))
|    -123.4     |

>>> print ("|{0:=15g}|".format (-123.4))
|-         123.4|

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

>>> print ("|{0:15.4g}|".format (-22.56789))
|         -22.57|

>>> print ("|{0:15.5g}|".format (-22.56789))
|        -22.568|

>>> print ("|{0:15.15g}|".format (-22.56789))
|      -22.56789|

>>> print ("|{0:15.1g}|".format (-22.56789))
|         -2e+01|

Порядок следования подстановочных параметров в строке форматирования можно задавать не только с помощью порядкового номера параметра метода format(), но и давая им имена, например:

>>> print ("x = {x}; y = {y}".format (x = 124.2, y = 56.8))
x = 124.2; y = 56.8

Здесь параметрам мы дали имена x и y, которые также используются в фигурных скобках.

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

>>> print ("x = {0}; y = {y}".format (124.2, y = 56.8))
x = 124.2; y = 56.8

Кроме перечисленных, у метода format() есть еще несколько настроек форматирования, который мы рассматривать не будем.

Свойства

В новых версиях Python появился новый, более удобный способ задания свойств. До Python 2.6 и 3.0 класс со свойствами мог выглядеть примерно так:

			class Foo(object):
    def __init__(self, xval):
        print ("*** Foo.__init__()")
        self._x = xval

    def getx(self):
        print ("*** X.Getter(): x = %d" % self._x)
        return self._x

    def setx(self, value):
        self._x = value
        print ("*** X.Setter(): new x value = %d" % self._x)

    x = property (getx, setx)

f = Foo(123)
print (f.x)

f.x = 345
print (f.x)

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

			class Foo(object):
    def __init__(self, xval):
        print ("*** Foo.__init__()")
        self._x = xval

    @property
    def x(self):
        print ("*** X.Getter(): x = %d" % self._x)
        return self._x

    @x.setter
    def x(self, value):
        self._x = value
        print ("*** X.Setter(): new x value = %d" % self._x)

f = Foo(123)
print (f.x)

f.x = 345
print (f.x)

Внутри класса метод для получения значения свойства (getter) можно пометить декоратором @property, а метод для установки значения свойства (setter) – декоратором, имя которого представляет собой имя_свойства.setter.

Кроме того, по аналогии с setter так же можно объявлять deleter, который вызывается, если к свойству применить оператор del. Вот пример использования такого свойства:

			class Foo(object):
    def __init__(self, xval):
        print ("*** Foo.__init__()")
        self._x = xval

    @property
    def x(self):
        print ("*** X.Getter(): x = %d" % self._x)
        return self._x

    @x.setter
    def x(self, value):
        self._x = value
        print ("*** X.Setter(): new x value = %d" % self._x)

    @x.deleter
    def x(self):
        print ("*** X.Deleter()")
        del self._x

f = Foo(123)
print (f.x)

f.x = 345
print (f.x)

del f.x

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

*** Foo.__init__()
*** X.Getter(): x = 123
123
*** X.Setter(): new x value = 345
*** X.Getter(): x = 345
345
*** X.Deleter()

Абстрактные базовые классы

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

Для работы с абстрактными классами появился новый модуль – abc (сокращение от слов Abstract Base Classes).

Чтобы объявить класс абстрактным, в Python 2.6 необходимо переменной __metaclass__ в его определении присвоить значение abc.ABCMeta.

			from abc import ABCMeta

class Foo (object):
  __metaclass__ = ABCMeta

В Python 3.0 абстрактный класс должен быть объявлен следующим образом:

			class Foo (metaclass=ABCMeta):

Непонятно, почему разработчики сделали разные способы объявления классов абстрактными в версиях 2.6 и 3.0, причем способ с __metaclass__ = ABCMeta не работает в Python 3.0, а способ с class Foo (metaclass=ABCMeta) не работает в Python 2.6.

Чтобы сделать абстрактный метод внутри класса, его надо пометить декоратором @abs.abstractmethod.

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

Python 2.6:

				from abc import ABCMeta
from abc import abstractmethod

class Foo (object):
  __metaclass__ = ABCMeta

  def __init__(self):
    print ("*** Foo.__init__()")

  @abstractmethod
  def run (self):
    print ("*** Foo.run()")

class Bar (Foo):
  def __init__ (self):
    print ("*** Bar.__init__()")
    Foo.__init__ (self)

  def run (self):
    print ("*** Bar.run()")

bar_obj = Bar()
bar_obj.run()

Python 3.0:

				from abc import ABCMeta
from abc import abstractmethod

class Foo (metaclass = ABCMeta):
  def __init__(self):
    print ("*** Foo.__init__()")

  @abstractmethod
  def run (self):
    print ("*** Foo.run()")

class Bar (Foo):
  def __init__ (self):
    print ("*** Bar.__init__()")
    Foo.__init__ (self)

  def run (self):
    print ("*** Bar.run()")

bar_obj = Bar()
bar_obj.run()

В результате выполнения этого скрипта мы получим следующий результат:

*** Bar.__init__()
*** Foo.__init__()
*** Bar.run()

А теперь мы попытаемся создать экземпляр класса Foo:

foo_obj = Foo()

В результате выполнения этой строки будет сгенерировано исключение:

Traceback (most recent call last):
File "H:\Черновики\Python 2.6 & 3.0\abstract_1.py", line 33, in <module>
foo_obj = Foo()
TypeError: Can't instantiate abstract class Foo with abstract methods run

Теперь создадим другой производный от Foo класс – Bar2, но "забудем" переопределить абстрактный метод run():

				class Bar2 (Foo):
  def __init__ (self):
    print ("*** Bar2.__init__()")
    Foo.__init__ (self)

Если попытаться создать экземпляр класса Bar2:

bar2_obj = Bar2()

то будет сгенерировано то же исключение:

Traceback (most recent call last):
File "H:\Черновики\Python 2.6 & 3.0\abstract_1.py", line 35, in <module>
bar2_obj = Bar2()
TypeError: Can't instantiate abstract class Bar2 with abstract methods run

Теперь попробуем объявить класс абстрактным, но без абстрактных методов, и попытаемся создать экземпляр такого класса в стиле Python 2.6 (дальше во всех примерах этого раздела будем использовать именно Python 2.6):

				from abc import ABCMeta
from abc import abstractmethod

class Foo (object):
  __metaclass__ = ABCMeta

  def __init__(self):
    print ("*** Foo.__init__()")

foo_obj = Foo()

Как ни странно, объект будет успешно создан, и мы увидим строку

*** Foo.__init__()

Аналогично ведет себя и Python 3.0. Не знаю, ошибка ли это в первых бетах, или так и было задумано, но, по моему мнению, такое поведение нелогично. Если уж объявлять класс абстрактным, то надо всегда запрещать создавать экземпляры этого класса независимо от того, есть ли у него абстрактные методы (или свойства) или нет.

Кроме методов, абстрактными могут быть также и свойства. Чтобы пометить свойство как абстрактное, его getter надо пометить декоратором @abc.abstractproperty. Например:

				from abc import ABCMeta, abstractproperty

class Foo (object):
  __metaclass__ = ABCMeta

  def __init__ (self):
    print ("*** Foo.__init__()")
    self._x = 0

  @abstractproperty
  def x (self):
    pass

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

				class Bar (Foo):
  def __init__ (self):
    print ("*** Bar.__init__()")
    Foo.__init__ (self)

  @property
  def x (self):
    return self._x

Если теперь попытаемся создать объект класса Foo, то снова получим исключение:

Traceback (most recent call last):
File "H:\Черновики\Python 2.6 & 3.0\abstract_4.py", line 21, in <module>
foo_obj = Foo()
TypeError: Can't instantiate abstract class Foo with abstract methods x

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

Если мы хотим сделать абстрактным также и setter свойства, то придется воспользоваться старым стилем определения свойств:

				from abc import ABCMeta, abstractproperty

class Foo (object):
  __metaclass__ = ABCMeta

  def __init__ (self):
    print ("*** Foo.__init__()")
    self._x = 0

  def getx (self):
    pass

				def setx (self, value):
    pass

  x = abstractproperty(getx, setx)

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

				class Bar (Foo):
  def __init__ (self):
    print ("*** Bar.__init__()")
    Foo.__init__ (self)

  @property
  def x (self):
    return self._x

  @x.setter
  def x (self, value):
    self._x = value


bar_obj = Bar()
bar_obj.x = 3
print (bar_obj.x)

В результате работы этого скрипта получим:

*** Bar.__init__()
*** Foo.__init__()
3

Обработка исключений

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

			try:
  raise TypeError("111")
except TypeError, exc:
  print (exc)

В Python 2.6 этот метод будет работать, а вот в Python 3.0 будет ругаться на неверный синтаксис. Чтобы этот код работал и в Python 2.6, и в Python 3.0 (но не в Python 2.5 и ниже), в строке с ключевым словом except запятую необходимо заменить ключевым словом as:

			try:
  raise TypeError("111")
except TypeError as exc:
  print (exc)

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

			try:
  #raise TypeError("111")

			raise ValueError("222")
except (TypeError, ValueError) as exc:
  print (exc)

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

Кроме того, теперь в Python 3.0 классы всех вызываемых исключений обязаны быть производными от одного базового класса – BaseException, в противном случае оператор raise вместо ожидаемого исключения вызовет исключение типа TypeError. Однако в документации для пользовательских исключений в качестве базового класса рекомендуют использовать класс Exception, а класс BaseException оставить для встроенных исключений.

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

			class FooException (Exception):
  def __init__ (self, val):
    Exception.__init__ (self, val)

try:
  raise FooException ("My Exception")
except FooException as exc:
  print (exc)

А теперь рассмотрим пример с ошибкой, в котором класс FooException не унаследован от BaseException или его потомков:

			class FooException ():
  def __init__ (self, val):
    pass

			try:
  raise FooException ("My Exception")
except FooException as exc:
  print (exc)

В Python 3.0 строка raise FooException ("My Exception") вызовет исключение:

TypeError: catching classes that do not inherit from BaseException is not allowed

В Python 2.6 без использования параметра командной строки -3 (этот параметр предназначен для того, чтобы Python предупреждал, если в коде используются конструкции, которые не будут работать в Python 3.0; об этом параметре более подробно будет сказано ниже) этот пример будет работать без проблем, а при включении параметра -3 выдаст сразу два предупреждения:

H:\Черновики\Python 2.6 & 3.0\exception_6.py:8: DeprecationWarning: exceptions must derive from BaseException in 3.x
raise FooException ("My Exception")
H:\Черновики\Python 2.6 & 3.0\exception_6.py:9: DeprecationWarning: catching classes that don't inherit from BaseException is not allowed in 3.x
except FooException as exc:

Оператор with

Оператор with представляет собой некоторый аналог обертывания объекта в try / finally. Он был введен еще в Python 2.5, но для его использования необходимо было импортировать with_statement из модуля __future__ :

			from __future__ import with_statement

Начиная с Python 2.6, этого не требуется, и with стал обычным ключевым словом.

Суть его состоит в том, что для объекта, подаваемого на вход оператора with, сначала вызывается специальный метод – __enter__(), а перед выходом из оператора вызывается метод __exit__(), причем метод __exit__() вызывается независимо от того, был ли блок внутри оператора with выполнен до конца, или был прерван исключением.

Рассмотрим пример и на нем разберемся с синтаксисом оператора with. Поддержку оператора with имеет, например, класс File, поэтому, чтобы быть уверенными, что файл в любом случае будет закрыт после записи, мы можем написать такой код:

fname = "test.txt"
			with file (fname, "w") as fp:
  fp.write ("Hello, with")

with file (fname, "w") as fp:
  fp.write ("Hello, with - 2")

На входе оператора with мы создаем объект File с помощью выражения file (fname, "w") и присваиваем его переменной fp с помощью выражения as fp, которое, строго говоря, является необязательным, если мы не хотим использовать созданный объект внутри блока оператора.

В этом примере блок with специально был использован дважды, чтобы показать, что файл test.txt после блока with закрыт, иначе при повторном открытии файла на запись произошло бы исключение. Как мы можем убедиться, файл test.txt будет создан и в нем будет записана строка «Hello, with - 2», т.е. вторая запись в файл будет успешно выполнена.

Давайте теперь создадим свой класс объектов, которые смогут использовать оператор with. Рассмотрим следующий код:

			class Foo (object):
  def __init__ (self):
    print ("*** Foo.__init__()")

  def __enter__ (self):
    print ("*** Foo.__enter__()")
    return self

  def __exit__ (self, exc_type, exc_value, traceback):
    print ("*** Foo.__exit__()")
    print ("*** exc_type: {0}\n*** exc_value: {1}\n*** traceback: {2}"
      .format (exc_type, exc_value, traceback))

  def Run (self):
    print ("*** Foo.Run()")

with Foo() as foo_obj:
  print ("With begin")
  foo_obj.Run()
  print ("With end")

print ("After With")

Здесь мы создали класс Foo и объявили в нем методы __enter__() и __exit__(). Сначала рассмотрим первый метод.

У метода __enter__() нет никаких параметров, кроме self. При этом метод __enter__() должен возвращать значение, которое будет присвоено переменной, стоящей после ключевого слова as оператора with, т.е. в нашем случае foo_obj. Как видите, оператор as не просто присваивает переменной значение выражения внутри with. Мы могли бы вернуть из __enter__() объект другого класса.

У метода __exit__() кроме self есть еще три параметра, которые описывают возникшее внутри блока with исключение.

Если блок внутри with завершился без исключений, то все эти параметры равны None.

Теперь посмотрим результат работы последнего примера:

*** Foo.__init__()
*** Foo.__enter__()
With begin
*** Foo.Run()
With end
*** Foo.__exit__()
*** exc_type: None
*** exc_value: None
*** traceback: None
After With

Итак, первое, что делает оператор with, создает объект класса Foo и вызывает его метод __enter__(). Затем полностью выполняется блок внутри with и вызывается метод __exit__(), значениями аргументов которого являются три None.

Изменим пример так, чтобы метод Run() генерировал исключение:

			class Foo (object):
  def __init__ (self):
    print("*** Foo.__init__()")

  def __enter__ (self):
    print("*** Foo.__enter__()")
    return self

  def __exit__ (self, exc_type, exc_value, traceback):
    print("*** Foo.__exit__()")
    print("*** exc_type: {0}\n*** exc_value: {1}\n*** traceback: {2}".format(
      exc_type, exc_value, traceback))

  def Run(self):
    raise AssertionError ("Run error")


with Foo() as foo_obj:
  print ("With begin")
  foo_obj.Run()
  print("With end")

print("After With")

И вот результат его работы:

*** Foo.__init__()
*** Foo.__enter__()
With begin
*** Foo.__exit__()
*** exc_type: <type 'exceptions.AssertionError'>
*** exc_value: Run error
*** traceback: <traceback object at 0x00C3D620>
Traceback (most recent call last):
File "H:\Черновики\py_26_with_3.py", line 22, in <module>
foo_obj.Run()
File "H:\Черновики\py_26_with_3.py", line 16, in Run
raise AssertionError ("Run error")
AssertionError: Run error

Таким образом, с помощью оператора with мы действительно гарантируем, что метод __exit__() будет вызван. Думаю, дополнительных комментариев здесь не требуется.

Математика

Еще одно заметное нововведение в Python 3.0 (в 2.6 этого нет) – это то, что для целочисленного деления теперь нужно будет использовать оператор // . Оператор / теперь используется для дробного деления.

Если раньше в результате выполнения строк

print (3/2)
print (4/2)

мы бы получили:

1
2

то в Python 3.0 оператор / предназначен для дробного деления, результатом которого является объект типа float, и в результате выполнения этого же кода, мы получим:

1.5
2.0

Для того чтобы выполнить целочисленное деление, этот пример надо переписать в следующем виде:

print (3//2)
print (4//2)

В результате выполнения этих строк получим опять:

1
2

Гиперболические функции acosh(), asinh() и atanh() теперь есть не только в модуле cmath, но и в модуле math (это уже относится и к Python 3.0, и к Python 2.6).

Были введены особые числа с плавающей точкой – nan (not a number), +inf и -inf (соответственно, «+» и «-» бесконечности). Создавать их можно с помощью встроенной функции float():

>>> float ('nan')
nan

>>> float ('-inf')
-inf

>>> float ('inf')
inf

Рассмотрим несколько простых примеров с использованием этих чисел:

>>> a = float ('inf')
>>> a
inf

>>> # Бесконечность, умноженная на 0
>>> a * 0
nan

>>> # Бесконечность, деленная на бесконечность
>>> a / a
nan

>>> # Очень большое число
>>> a=1.0e999
>>> a
inf

>>> # Бесконечность, умноженная на -1
>>> b = -a
>>> b
-inf

>>> # Деление бесконечностей разных знаков
>>> a/b
nan

>>> # Сумма бесконечностей разных знаков
>>> a + b
nan

Чтобы определить является ли число бесконечностью или NaN, в модулях math и cmath созданы специальные функции – isnan() и isinf(), которые возвращают True, если число является NaN или бесконечностью соответственно, и False в противном случае.

В модуле math появилась новая функция – log1p(x), которая вычисляет натуральный логарифм от 1 + x.

>>> math.log1p (0)
0.0

>>> math.log1p (1)
0.69314718055994529

>>> math.log (2)
0.69314718055994529

В модуле cmath появились новые функции для работы с комплексными числами.

Функция polar() возвращает значение амплитуды и фазы комплексного числа в полярной системе координат:

>>> r, phi = cmath.polar(complex ('1+1j'))
>>> r
1.4142135623730951

>>> phi * 180.0 / math.pi
45.0

Функция rect() модуля cmath делает обратную операцию – она создает комплексное число по значению амплитуды и фазы в полярной системе координат:

>>> cmath.rect (r, phi)
(1.0000000000000002+1j)

Функция phase() из модуля cmath возвращает значение фазы комплексного числа:

>>> cmath.phase (complex ('1+1j')) * 180.0 / math.pi
45.0

Разное

В Python 3.0 не стало функции xrange(), осталась только range(), но теперь она возвращает не список, а итератор. То есть по сути получается, что старую функцию range() убрали, а функцию xrange() переименовали в range().

Если в Python 2.6 и ранее результатом выполнения скрипта

vals = range (1, 10, 1)
print (vals)
print (list (vals))

было:

[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]

А в результате выполнения кода

vals = xrange (1, 10, 1)
print (vals)
print (list (vals))

на экран выводилось:

xrange(1, 10)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

То в Python 3.0 после запуска первого скрипта мы увидим:

range(1, 10)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

А второй скрипт просто не будет работать.

У словарей в Python 3.0 больше нет метода has_key(), вместо него теперь надо использовать оператор in, поэтому такой код будет работать во всех версиях Python, включая 3.0.

a = dict()
a["1"] = 1
print ("1"
			in a)

А следующий код в Python 3.0 работать не будет:

a = dict()
a["1"] = 1
print (a.has_key("1"))

Для выявления таких устаревших методов в Python 2.6 введен новый параметр командной строки:

-3. Если он задан, то транслятор выведет следующее предупреждение, когда будет выполнять третью строку последнего примера:

H:\Черновики\Python 2.6 & 3.0\other_1.py:5: DeprecationWarning: dict.has_key() not supported in 3.x; use the in operator

Кроме метода has_key(), этот параметр укажет транслятору, что надо писать похожие предупреждения, если используются функции apply(), callable(), coerce(), execfile(), reduce(), reload(). Кроме того, предупреждения будут выдаваться при использовании устаревших конструкций языка.

В Python 3.0 разработчики решили отказаться от оператора сравнения <> , вместо него везде нужно будет использовать оператор != . Python 2.6 при использовании оператора <> (если установлен параметр командной строки -3) будет выдавать следующее предупреждение:

DeprecationWarning: <> not supported in 3.x; use !=

В Python 3.0 (но не 2.6) появился новый способ документирования входных параметров и возвращаемого значения функций. После имени каждого параметра функции через двоеточие можно задать любое выражение, которое может быть вычислено на момент описания функции. Это выражение будет связано с именем параметра в специальном словаре. Также после описания всех параметров, но до символа «:» можно задать выражение (с помощью оператора ->), которое будет связано с возвращаемым значением. Самым очевидным применением этой возможности является задание текстовых строк, описывающие входные параметры и возвращаемое значение.

Рассмотрим пример:

			def foo (x: "First parameter", y: "Second parameter") -> "Summ":
  return x + y

В этом примере с параметром x мы связали значение строкового выражения "First parameter" , с параметром y"Second parameter". С возвращаемым значением мы связали символьное выражение "Summ". В Python 3.0 у всех вызываемых типов теперь есть член __annotations__, который представляем собой словарь. В качестве ключей в нем выступают строковые выражения с именами параметров, а в качестве значений – связанные с этими параметрами описания (аннотации). Если мы выполним следующий код:

			def foo (x: "First parameter", y: "Second parameter") -> "Summ":
  return x + y

print (foo.__annotations__)

то в результате получим:

{'y': 'Second parameter', 'x': 'First parameter', 'return': 'Summ'}

Как видно, возвращаемому значению соответствует ключ 'return' .

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

			def foo (x: [1, 2, 3, 4], y: 5 + 7) -> None:
  pass

print (foo.__annotations__)

Результатом работы этого скрипта является следующий словарь:

{'y': 12, 'x': [1, 2, 3, 4], 'return': None}

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

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

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

Заключение

В заключении кратко перечислим наиболее значимые изменения, которые произошли в языках Python 2.6 и 3.0.

Мы рассмотрели основные нововведения языков Python 2.6 и 3.0. Из-за потери обратной совместимости язык Python 3.0 с версиями 2.х, можно назвать новым языком, который будет развиваться некоторое время параллельно версиям 2.х, хотя не думаю, что мы когда-нибудь увидим Python 2.7 с новыми возможностями. Скорее всего, в ветке 2.х будут только исправляться ошибки.

Обе версии 2.6 и 3.0 кажутся очень интересными. Если при переходе на версию 3.0 надо будет хорошенько тестировать старый код на работоспособность, то переход на версию 2.6 должен быть практически безболезненным. Осталось дождаться поддержки новых версий Python сторонними библиотеками.


Эта статья опубликована в журнале RSDN Magazine #2-2008. Информацию о журнале можно найти здесь