Архив метки: Python

pep8 и 80 символов в строке

На самом деле 79 если внимательно читать pep 8, если что…

Посетил на днях Python Party, организованной компанией Yandex.
Мероприятие понравилось, а самый интерес был потом, на «поболталках» в кабаке.

Был на party доклад от Кирилла Борисова «Контроль за стилем кода». Интересный и толковый.

Только я возражаю против с высказывания вроде:
  — pep8 рекомендует ограничивать длину строк в 79 символов максимум. Мы с этим несогласны — сейчас мониторы большие, можно писать 120 символов и это великолепно помещается на экране.

Я везде строго пишу с ограничением на 79 символов.

Попробую объяснить почему.

1. Во первых сам код Python так написан и patch вылезающий за границы просто не будет принят. OK, я committer — значит тем более обязан следовать соглашениям.
2. Во вторых мой редактор (emacs если что) настроен на то чтобы подсвечивать длинные строки. И когда я открываю код библиотеки, наплевавшей на ограничение по длине строки — у меня половина экрана «красная». Это огорчает.
3. В третьих и главное: если у вас широкий монитор — это прекрасная возможность разбить его по вертикали и видеть одновременно несколько редактируемых файлов. У меня даже на 14'' ноутбуке Full HD — это значит что при размере шрифта в 13pt у меня помещается два буфера. Коллега на 24'' привык работать в vim с шестью буферами: 3х2. Это очень удобно — гораздо лучше видеть сразу несколько файлов чем один, но с длинными строками.

Что до «невозможности» уместить код в 79 символов — это распространенное заблуждение.
При некотором навыке всё легко получается.

К тому же такой подход провоцирует сохранение промежуточных вычислений в локальные переменные — что хорошо само по себе, так как улучшает читабельность кода (вы же даете переменным не слишком длинные, но «говорящие» имена, верно?)

Коротко говоря, 79 символов заставляют лучше писать код и помогают его читать. Что вам всем и рекомендую.

Автор: Andrew Svetlov

Перегрузка операций

В питоне буквально все используют магические методы. Когда пишем конструктор класса — называем его __init__ и т.д.

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

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

Создаем класс-точку

Итак, имеем точку в двухмерном пространстве:

class Point(object):

def __init__(self, x, y):
self.x = x
self.y = y

def __repr__(self):
return 'Point({}, {})'.format(self.x, self.y)

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

Обновленная версия:

class Point(object):

def __init__(self, x, y):
self._x = x
self._y = y

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

@property
def y(self):
return self._y

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

Над точками нужно производить какие-то операции. Самая, наверное, распространенная — это сравнение.

Сравнение

class Point:

# ...

def __eq__(self, other):
return self._x == other._x and self._y == other._y

Что плохо? То, что попытка сравнить точку с не-точкой (Point(1, 2) == 1) выбросит исключение AttributeError:

>>> Point(1, 2) == 1
AttributeError: 'int' object has no attribute '_x'

в то время как стандартные питоновские типы ведут себя иначе:

>>> 1 == 'a'
False

Меняем сравнение:

def __eq__(self, other):
if not isinstance(other, Point):
return False
return self._x == other._x and self._y == other._y

Теперь сравнивание работает почти правильно:

>>> Point(1, 2) == Point(1, 2)
True

>>> Point(1, 2) == 1
False

Слово почти я употребил потому, что Питон работает так:

  • сначала пытается сделать сравнение a == b
  • если сравнение не дает результата — делается вторая попытка с перестановкой операторов b == a

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

def __eq__(self, other):
if not isinstance(other, Point):
return NotImplemented
return self._x == other._x and self._y == other._y

В паре с == всегда идет оператор !=, не нужно про него забывать:

def __ne__(self, other):
return not (self == other)

На самом деле Питон будет сам использовать метод __eq__ если __ne__ не определен, но я считаю что лучше и понятней написать __ne__ самому, тем более что это не трудно.

hash

Наши точки сравниваются, всё прекрасно. Но если мы захотим, скажем, использовать их как ключи в словаре — получим ошибку:

>>> {Point(1, 2): 0}
TypeError: unhashable type: 'Point'

Нужно определить метод __hash__. Питон прекрасно умеет считать хэш для кортежа, чем мы и воспользуемся:

def __hash__(self):
return hash((self._x, self._y))

Результат:

>>> {Point(1, 2): 0}
{Point(1, 2): 0}

Определять только __hash__ без __eq__/__ne__ неправильно: в случае коллизии задействуются операторы сравнения. Если они не определены — можно получить некорректный результат.

Упорядочивание

Как говорил один преподаватель, не используйте слово «сортировка» — оно очень созвучно слову «сортир».

Точки на плоскости не имеют естественного порядка. Поэтому реализовывать операторы упорядочивания (<, >, <=, >=) не нужно.

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

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

Арифметика

Точки можно складывать и вычитать.

def __add__(self, other):
if not isinstance(other, Point):
return NotImplemented
return Point(self._x + other._x, self._y + other._y)

def __sub__(self, other):
if not isinstance(other, Point):
return NotImplemented
return Point(self._x - other._x, self._y - other._y)

Пример:

>>> Point(1, 2) + Point(2, 3)
Point(3, 5)

>>> Point(1, 2) - Point(2, 3)
Point(-1, -1)

Так как точки неизменяемые, то возвращается новый объект.

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

Как и для сравнения, если не знаем что делать -- во
звращаем NotImplemented. Тогда Питон попробует переставить аргументы местами, но вызовет уже __radd__:

res = a.__add__(b)
if res is NotImplemented:
res = b.__radd__(a)

Реализуем и эти методы:

def __radd__(self, other):
if not isinstance(other, Point):
return NotImplemented
return other + self

def __rsub__(self, other):
if not isinstance(other, Point):
return NotImplemented
return other - self

Зачем это нужно? Допустим, мы хотим складывать наши точки с QPointиз библиотеки PyQt, полуая в результате опять объекты класса Point.

Тогда нужно расширить наши __add__ и __radd__:

def __add__(self, other):
if isinstance(other, Point):
return Point(self._x + other._x, self._y + other._y)
elif isinstance(other, QPoint):
return Point(self._x + other.x(), self._y + other.y())
return NotImplemented

def __radd__(self, other):
if isinstance(other, Point):
return Point(self._x + other._x, self._y + other._y)
elif isinstance(other, QPoint):
return Point(self._x + other.x(), self._y + other.y())
return NotImplemented

Реализацию __iadd__/__isub__ рассматривать не буду, там всё очевидно. К тому же Питон сам способен сделать как надо, сконструировав нужный код на основе вызовов __add__/__sub__.

Умножение и деление для точек не имеют смысла, поэтому их просто не делаем. Если бы делали, скажем, операции над векторами -- ввели бы скалярное и векторное произведения. "Просто точкам" эти излишества не нужны.

Заключение

Вот и всё, набросок для класса точки готов.

Надеюсь, хотя бы некоторым читателям написанное окажется полезным.

Автор: Andrew Svetlov

exrex генератор данных по регулярным выражениям

Всем привет!

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

API крайне простой
count — возвращет количество возможных вариаций
parse — парсер регулярных выражений
getone — возвращает псевдо-рандомное значение
generate — возвражает генератор со всеми вариантами

Примеры использования

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

>>> print exrex.getone('[A-Z][a-z]{1,10} [A-Z][a-z]{1,10}')
Boris Yeltsin

>>> print exrex.getone('+[0-9]([0-9]{3}) [0-9]{3}-[0-9]{2}-[0-9]{2}')
+5(777) 790-68-69

>>> print [r for r in exrex.generate('|'.join(str(i) for i in range(5)))]
[u'0', u'1', u'2', u'3', u'4']

>>> exrex.count('[0-9]{1}')
10

>>> print exrex.parse('[0-9]{1,100}')
[('max_repeat', (1, 100, [('in', [('range', (48, 57))])]))]

Есть также возможность запуска из консоли

>>> python -m exrex -r [0-9]{2}
31
Возвращает псевдо-рандомное число из 2 цифр

За более подробной информацией в ссылки.


Ссылки

https://github.com/asciimoo/exrex
http://exrex.readthedocs.org/

Автор: Евгений Курочкин

python кодировка исходников

Всем привет!

Я думаю каждый из нас ловил что-то подобное:

 File «python_coding_style.py», line 2
SyntaxError: Non-ASCII character 'xd0' in file python_coding_style.py on line 3, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details

Проблема в том что интерпретатор не знает в какой кодировке у нас файлы исходников. Проблема решается явным указанием их кодировки:
# coding: utf8

Чаще на практике встречаются следующие варианты

# -*- coding: utf8 -*-
# coding: utf8
# coding=utf8

В PEP-0263 декларируется, что для определения кодировки исходников годится все что подпадет под регулярку coding[:=]s*([-w.]+)
Например, #This Python file uses the following encoding: utf-8 !!!

Ссылки

http://legacy.python.org/dev/peps/pep-0263/

Автор: Евгений Курочкин

Строки символов Unicode в Python 2 и Python 3

Винсент: Знаешь, что самое забавное в Европе?
Джулс: Что?
Винсент: Такие маленькие отличия. Там вроде все то же самое, что и здесь, но чуть-чуть отличается.

«Криминальное чтиво»

Как известно, ключевым отличием Python 3.x от Python 2.x является переориентация языка и стандартных библиотек со строк байтов на строки символов Unicode. Когда я решил повнимательнее посмотреть на это различие, пришлось копнуть вглубь и вширь, а результаты моих раскопок я оформил в статью, которую предлагаю вашему вниманию.

В Python 2В Python 3
строковый литерал получает тип strстроковый литерал получает тип str
тип str представляет собой строку байтовтип str представляет собой строку символов Unicode
для представления строки символов Unicode используется тип unicodeдля представления строки байтов используется тип bytes
для представления строки unicode в нужной кодировке используется метод unicode.encode(кодировка), возвращающий строку байтов strдля представления строки str в нужной кодировке используется метод str.encode(кодировка), возвращающий строку байтов bytes
для преобразования строки байтов str в строку unicode используется метод str.decode(кодировка), возвращающий строку unicodeдля преобразования строки байтов bytes в строку str используется метод bytes.decode(кодировка), возвращающий строку str
системная кодировка по умолчанию, она же кодировка исходных файлов по умолчанию, ASCIIсистемная кодировка по умолчанию, она же кодировка исходных файлов по умолчанию, UTF-8
при записи в файл строки по умолчанию преобразуются в системную кодировку по умолчаниюпри записи в файл строки по умолчанию преобразуются в кодировку, определяемую пользовательскими настройками
идентификаторы в программе содержат только символы ASCII, не могут использовать никаких других букв, кроме латинскихидентификаторы в программе содержат символы Unicode, могут использовать буквы других алфавитов, кроме латинского

Мои эксперименты я ставлю под ОС Windows 7 в стандартной консоли.

Для разминки, несколько манипуляций со строковыми литералами и переменными в Python 2:


>>> s = 'Hello'
>>> s
'Hello'
>>> type(s)

>>> u = s.decode('ascii')
>>> u
u'Hello'
>>> type(u)


И в Python 3:


>>> s = 'Hello'
>>> s
'Hello'
>>> type(s)

>>> b = s.encode('ascii')
>>> b
b'Hello'
>>> type(b)


Теперь посмотрим на кодировки, используемые в Python 2 и Python 3 по умолчанию:

  • Системная кодировка по умолчанию (system default encoding), возвращается функцией sys.getdefaultencoding(). Это кодировка исходных текстов Python по умолчанию, она же используется по умолчанию для кодирования и декодирования строк unicode.
  • Кодировки стандартных потоков ввода, вывода и ошибок — файловых объектов, атрибуты sys.stdin.encoding, sys.stdout.encoding и sys.stderr.encoding, соответственно. Строки символов Unicode, посылаемые в канал вывода, преобразуются в соответствующую кодировку (или в системную кодировку по умолчанию, если атрибут encoding is None).
  • Кодировка имен файлов в файловой системе, возвращается функцией sys.getfilesystemencoding(). Имена файлов, представленные в программе на Python как строки символов Unicode, преобразуются в эту кодировку для взаимодействия с файловой системой (или в системную кодировку по умолчанию, если sys.getfilesystemencoding() возвращает None).
  • Кодировка для текстовых данных, заданная пользовательскими настройками, возвращается функцией locale.getpreferredencoding(False).

Кодировки в Python 2:


>>> import sys, locale
>>> sys.getdefaultencoding()
'ascii'
>>> sys.stdin.encoding
'cp866'
>>> sys.stdout.encoding
'cp866'
>>> sys.stderr.encoding
'cp866'
>>> sys.getfilesystemencoding()
'mbcs'
>>> locale.getpreferredencoding(False)
'cp1251'

Кодировки в Python 3:


>>> import sys, locale
>>> sys.getdefaultencoding()
'utf-8'
>>> sys.stdin.encoding
'cp866'
>>> sys.stdout.encoding
'cp866'
>>> sys.stderr.encoding
'cp866'
>>> sys.getfilesystemencoding()
'mbcs'
>>> locale.getpreferredencoding(False)
'cp1251'

Как видим, системной кодировкой по умолчанию для Pyt
hon 2 является ascii, а для Python 3 — utf-8. И это единственное обнаруженное различие.

Нам, русским, очень «повезло» с обилием кириллических кодировок в Windows. Работая в консоли Windows, по умолчанию мы имеем дело с кириллической кодировкой cp866. Работая с текстовым файлом в Блокноте, по умолчанию мы работаем в кириллической кодировке cp1251. Имена файлов, использующие русские символы, в файловой системе Windows представлены в кодировке mbcs (multi-byte character set), — это двухбайтовая кодировка, которая позволяет представить подмножество символов Unicode (UTF-16?).

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

Фрагмент интерактивного сеанса Python 2:


>>> u = u'Привет world'
>>> type(u)

>>> u
u'u041fu0440u0438u0432u0435u0442 world'
>>> print u, u.encode('cp866')
Привет world Привет world

Что я только что сделал?

Команды вводятся через стандартный входной поток, использующий кодировку cp866 (sys.stdin.encoding). Таким образом, в первом предложении присваивания литерал u'Привет world' преобразуется в строку unicode из кодировки cp866 и полученная строка unicode присваивается переменной u. Далее я проверил тип и значение переменной u и увидел, что русские буквы представлены двухбайтовыми кодами Unicode. Наконец, предложение print посылает в стандартный выходной поток данную строку unicode и строку str, полученную преобразованием строки u в кодировку cp866. Результат вывода обеих строк одинаков, поскольку строка unicode неявно преобразуется при выводе в стандартый выходной поток в кодировку cp866 (sys.stdout.encoding).

В Python 3 получим такой результат (предлагаю интерпретировать его самостоятельно):


>>> s = 'Привет world'
>>> type(s)

>>> s
'Привет world'
>>> print(s, s.encode('cp866'))
Привет world b'x8fxe0xa8xa2xa5xe2 world'

Идем дальше.

В Python 2 для использования в скриптах не-ASCII символов нужно явно указывать кодировку исходного файла, поскольку системная кодировка по умолчанию, ascii, подразумевает использование в файле только символов ASCII. А в Python 3 исходный файл по умолчанию содержит символы Unicode в кодировке utf-8, что позволяет использовать в нем практически любые символы без явного указания кодировки.

Файл hello.py в кодировке UTF-8 для Python 2:


# -*- coding: utf-8 -*-

s = u'Привет world!'
print type(s), s, s.encode('cp866')

Выполню его в консоли Windows:


C:_sandbox> c:Python27python.exe hello.py
Привет world! Привет world!

Файл hello3.py в кодировке UTF-8 для Python 3:


# нас устраивает кодировка по умолчанию utf-8

s = 'Привет world!'
print(type(s), s, s.encode('cp866'))

Выполню его в консоли Windows:


C:_sandbox> c:Python33python.exe hello3.py
Привет world! b'x8fxe0xa8xa2xa5xe2 world!'

Разница с работой скрипта hello.py в том, что теперь к кодировке стандартного потока вывода приводится значение типа str, и строка байтов bytes не интерпретируется как строка читабельных символов.

Следующий скрипт helloname.py демонстрирует ввод и вывод кириллических символов в Python 2, используя для их хранения строки unicode:


# -*- coding: utf-8 -*-

import sys

def uraw_input(prompt):
return unicode(raw_input(prompt.encode(sys.stdout.encoding)), sys.stdin.encoding)

name = uraw_input(u'Привет! Ваше имя? ')
print u'Привет, %s!' % name

Выполню скрипт в консоли Windows:


C:_devGOLD>c:Python27python.exe helloname.py
Привет! Ваше имя? Андрей
Привет, Андрей!

Посмотрим теперь, что происходит при записи строк символов Unicode в файл. Если в Python 2 явно не преобразовывать выводимые в файл строки unicode в строки str с нужной кодировкой, то получим ошибку.


# -*- coding: UTF-8 -*-

with open('hello.txt', 'w') as f:
print 'File encoding:', f.encoding
f.write(u'Привет world!')

Выполню скрипт в консоли Windows:


C:_sandbox> c:Python27python.exe hellofile.py
File encoding: None
Traceback (most recent call last):
File "hellofile.py", line 5, in
f.write(u'╨Я╤А╨╕╨▓╨╡╤В world!')
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-5: ordinal not in range(128)

При записи в файл Python 2 пытается преобразовать строку unicode в системную кодировку по умолчанию, ascii, поскольку атрибут encoding открытого нами файла имеет значение None. В кодировке ascii нельзя представить кириллические символы, из-за чего и возникает ошибка.

Во избежание ошибок, при записи строк
unicode
в файл нужно явно приводить их к желаемой кодировке:


# -*- coding: UTF-8 -*-

with open('hello.txt', 'w') as f:
print 'File encoding:', f.encoding
f.write(u'Привет world!'.encode('cp866'))

Выполню исправленный скрипт в консоли Windows:


C:_sandbox> c:Python27python.exe hellofile.py
File encoding: None

C:_sandbox> type hello.txt
Привет world!

Аналогичный эксперимент с Python 3 показывает, что строки пишутся в файл в кодировке, определяемой локалью пользователя!


with open('hello3.txt', 'w') as f:
print('File encoding:', f.encoding)
f.write('Привет world!')

Выполняю скрипт в консоли Windows:


C:_sandbox> c:Python33python.exe hellofile3.py
File encoding: cp1251

Выше мы видели, что на русифицированной Windows функция locale.getpreferredencoding(False) возвращает 'cp1251'. Именно с этой кодировкой открывается новый файл по умолчанию, и, как следствие, к ней приводятся строки, записываемые в этот файл.

С кодировкой файла cp1251 запись в него смешанной латино-кириллической строки проходит на ура, а вот попытка записи кандзи вместе с кириллицей приводит к уже знакомой нам ошибке:


with open('hello31.txt', 'w') as f:
print('File encoding:', f.encoding)
f.write('Привет 世界!')

Выполняю скрипт в консоли Windows:


C:_sandbox> c:Python33python.exe hellofile31.py
File encoding: cp1251
Traceback (most recent call last):
File "hellofile31.py", line 3, in
f.write('Привет u4e16u754c!')
File "c:Python33libencodingscp1251.py", line 19, in encode
return codecs.charmap_encode(input,self.errors,encoding_table)[0]
UnicodeEncodeError: 'charmap' codec can't encode characters in position 7-8: character maps to

Кодировка cp1251 не кодирует кандзи!

Хорошая новость в том, что в Python 3, в отличие от Python 2, при открытии файла можно явно указать кодировку файла. В эту кодировку и будут преобразовываться строки str при записи в файл; из этой кодировки будут преобразовываться в str читаемые из файла строки байтов.

Укажу явно кодировку открываемых файлов в скрипте hello32.py:


with open('hello31.txt', 'w', encoding='utf-8') as f:
print('File encoding:', f.encoding)
f.write('Привет world!')

with open('hello32.txt', 'w', encoding='utf-8') as f:
print('File encoding:', f.encoding)
f.write('Привет 世界!')

with open('hello31.txt', encoding='utf-8') as f:
print('File encoding:', f.encoding)
print(f.read())

Выполняю скрипт в консоли Windows:


C:_sandbox> c:Python33python.exe hellofile32.py
File encoding: utf-8
File encoding: utf-8
File encoding: utf-8
Привет world!

Как видим, скрипт пишет в файл и читает из файла строки в кодировке utf-8.

В заключение, экзотический пример кода. Вследствие того, что в Python 3 системной кодировкой по умолчанию является utf-8, в Python 3 можно использовать в идентификаторах не только латиницу, но и другие символы Unicode:


>>> def привет_5_раз(имя):
... for i in range(5):
... print('Привет,', имя)
...
>>> привет_5_раз('Медвет')
Привет, Медвет
Привет, Медвет
Привет, Медвет
Привет, Медвет
Привет, Медвет

Сопровождать такой код и вносить в него изменения интернациональной команде разработчиков будет проблематично!

Проделанные сравнительные эксперименты не дали мне достаточно оснований, чтобы решительно встать на одну из сторон в священной войне между защитниками Python 2 и энтузиастами Python 3 :). Хотя Unicode-ориентированность Python 3 и то, как это сказывается на прикладном программировании, мне нравится.

Автор: Andrei Trofimov
Дата публикации: 2014-07-10T17:55:00.000+11:00