- PVSM.RU - https://www.pvsm.ru -
Сидел вечером дома, думал чем бы заняться. А: у Python есть отладчик, но в нём совершенно некрасивое приглашение ко вводу. Дай‐ка я впилю туда powerline [1]. Дело казалось бы совершенно плёвое: нужно просто создать свой подкласс pdb.Pdb [2] со своим свойством [3], да?
def use_powerline_prompt(cls):
'''Decorator that installs powerline prompt to the class
'''
@property
def prompt(self):
try:
powerline = self.powerline
except AttributeError:
powerline = PDBPowerline()
powerline.setup(self)
self.powerline = powerline
return powerline.render(side='left')
@prompt.setter
def prompt(self, _):
pass
cls.prompt = prompt
return cls
Нет. На Python-3 такой код ещё работать, но на Python-2 нас уже поджидает проблема: для вывода необходимо превратить юникодную строку [4] в набор байт [5], что требует указания кодировки. Ну, это просто:
encoding = get_preferred_output_encoding()
def prompt(self):
…
ret = powerline.render(side='left')
if not isinstance(ret, str):
# Python-2
ret = ret.encode(encoding)
return ret
. Это просто и это работает… пока пользователь не установит pdbpp [6]. Теперь нас приветствуют ряд ошибок, связанных с тем, что pdbpp может использовать pyrepl, а pyrepl не работает с Unicode (при чём будет ли использоваться pyrepl как‐то зависит от значения $TERM
: при TERM=xterm-256color
я получаю ошибки от pyrepl, а при TERM=
или TERM=konsole-256color
— нет и всё работает нормально). Ошибки, связанные с тем, что в приглашении кто‐то не хочет видеть Unicode, не новы — ещё IPython пытался запретить Unicode в rewrite prompt (эта то, что вы увидите, если вы включите autocall в IPython и наберёте int 42
). Но здесь всё гораздо хуже: pyrepl использует from __future__ import unicode_literals
[7], при этом делая с использованием обычных строк (превращённых этим импортом в юникодные) различные операции на строке приглашения, в явном виде конвертируемой в str
.
Итак, вот что нам получается нужно:
unicode
, который бы конвертировался в str
без выбрасывания ошибок на не‐ASCII символах (конвертация осуществляется просто в виде str(prompt)
). Эта часть очень проста: нужно переопределить методы __str__
и __new__
(без второго можно, в принципе, и обойтись, но так удобнее при конвертации в этот класс из следующего и для возможности явного указания кодировки, которая будет использована).str
, в который бы и конвертировался предыдущий класс. Здесь переопределения двух методов категорически недостаточно:
__new__
нужен для удобного сохранения кодировки и отсутствие необходимости в явном преобразовании unicode
→str
.__contains__
и несколько других методов должны работать с юникодными аргументами так, будто текущий класс есть unicode
(для неюникодных аргументов ничего менять не нужно). Дело в том, что при наличиии unicode_literals
'n' in prompt
выбрасывает исключение, если prompt
— байтовая строка с не‐ASCII символами, так как пытается привести prompt
к unicode
, а не наоборот.find
и схожие функции должны работать с юникодными аргументами так, будто это байтовые строки в текущей кодировке. Это нужно, чтобы они выдавали правильные индексы, но при этом не валились с ошибками из‐за конвертации байтовой строки в юникодную (а здесь‐то почему конвертация не обратная?).__len__
должен выдавать длину строки в юникодных codepoint’ах. Эта часть нужна, чтобы pyrepl, считающий, где заканчивается приглашение (и ставящий курсор соответственно), не ошибся и не сделал гиганский пробел между приглашением и курсором. Подозреваю, что нужно на самом деле использовать не codepoint’ы, а ширину строки в экранных ячейках (то, что делает, к примеру, strdisplaywidth() [8] в Vim).__add__
должен возвращать наш первый класс‐наследник unicode
при прибавлении к юникодной строке. __radd__
должен делать то же самое. Сложение байтовых строк должно давать наш класс‐наследник str
. Подробнее в следующем пункте.__getslice__
(внимание: __getitem__
не катит, str
использует deprecated __getslice__
для срезов) должен возвращать объект того же самого класса, поскольку pyrepl в самом конце складывает пустую юникодную строку, срез от текущего класса и другой срез от него же. И если эту часть обойти вниманием, то опять получим какую‐то из UnicodeError [9].В результате получатся следующие два уродца:
class PowerlineRenderBytesResult(bytes):
def __new__(cls, s, encoding=None):
encoding = encoding or s.encoding
self = bytes.__new__(cls, s.encode(encoding) if isinstance(s, unicode) else s)
self.encoding = encoding
return self
for meth in (
'__contains__',
'partition', 'rpartition',
'split', 'rsplit',
'count', 'join',
):
exec((
'def {0}(self, *args):n'
' if any((isinstance(arg, unicode) for arg in args)):n'
' return self.__unicode__().{0}(*args)n'
' else:n'
' return bytes.{0}(self, *args)'
).format(meth))
for meth in (
'find', 'rfind',
'index', 'rindex',
):
exec((
'def {0}(self, *args):n'
' if any((isinstance(arg, unicode) for arg in args)):n'
' args = [arg.encode(self.encoding) if isinstance(arg, unicode) else arg for arg in args]n'
' return bytes.{0}(self, *args)'
).format(meth))
def __len__(self):
return len(self.decode(self.encoding))
def __getitem__(self, *args):
return PowerlineRenderBytesResult(bytes.__getitem__(self, *args), encoding=self.encoding)
def __getslice__(self, *args):
return PowerlineRenderBytesResult(bytes.__getslice__(self, *args), encoding=self.encoding)
@staticmethod
def add(encoding, *args):
if any((isinstance(arg, unicode) for arg in args)):
return ''.join((
arg
if isinstance(arg, unicode)
else arg.decode(encoding)
for arg in args
))
else:
return PowerlineRenderBytesResult(b''.join(args), encoding=encoding)
def __add__(self, other):
return self.add(self.encoding, self, other)
def __radd__(self, other):
return self.add(self.encoding, other, self)
def __unicode__(self):
return PowerlineRenderResult(self)
class PowerlineRenderResult(unicode):
def __new__(cls, s, encoding=None):
encoding = (
encoding
or getattr(s, 'encoding', None)
or get_preferred_output_encoding()
)
if isinstance(s, unicode):
self = unicode.__new__(cls, s)
else:
self = unicode.__new__(cls, s, encoding, 'replace')
self.encoding = encoding
return self
def __str__(self):
return PowerlineRenderBytesResult(self)
(в Python2 bytes is str
).
Результат на github пока есть только в моей ветке [10], позже будет в develop
основного репозитория.
Разумеется, результат не ограничен только pyrepl, а может применяться в различных местах, куда вам нельзя подсунуть не‐ASCII строку, но очень хочется.
Автор: ZyXI
Источник [11]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/80986
Ссылки в тексте:
[1] powerline: https://github.com/powerline/powerline
[2] pdb.Pdb: https://docs.python.org/2.7/library/pdb.html#pdb.Pdb
[3] свойством: https://docs.python.org/2.7/library/functions.html#property
[4] юникодную строку: https://docs.python.org/2.7/library/functions.html#unicode
[5] набор байт: https://docs.python.org/2.7/library/functions.html#str
[6] pdbpp: https://pypi.python.org/pypi/pdbpp/0.7.2
[7] from __future__ import unicode_literals
: https://docs.python.org/2.7/library/__future__.html
[8] strdisplaywidth(): http://vimpluginloader.sourceforge.net/doc/eval.txt.html#strdisplaywidth.28.29
[9] UnicodeError: https://docs.python.org/2.7/library/exceptions.html#exceptions.UnicodeError
[10] моей ветке: https://github.com/ZyX-I/powerline/blob/pdb/powerline/bindings/pdb/__init__.py
[11] Источник: http://habrahabr.ru/post/249129/
Нажмите здесь для печати.