56 заметок с тегом

python

Позднее Ctrl + ↑

Уходим с телефоном, «МакБук» выключается

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

Идея выглядела просто: надо посмотреть присутствует ли в эфире мой смартфон, если нету, значит я куда-то ушёл (я без смартфона не хожу). Я сначала это на «Баше» сделал и всё даже работало, но случилось неожиданное: Mac OS время от времени стала падать с серым экраном смерти. Тогда я взял в руки Пайтон. Я даже запрограммировал сканирование эфира, но выяснилась неприятная штука — через какое-то время моя «Нокия» автоматически выключала видимость устройства и никакими уговорами не удалось убедить её этого не делать. Пришлось делать проверку соединением со смартфоном (не знаю как это сажает батарею, не успел ещё проверить).

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

Шаг №1.

Нужно узнать так называемый «адрес» вашего устройства и привязать его к компьютеру. Тут ничего сложного нет, заходим в настройки «Блютуз» на «Маке» (они находятся в «Системных настройках»), привязываем его как обычно и переписываем себе 12 букв, разделённых минусами, это и есть адрес:

Настройки Bluetooth (80.68КиБ)

Шаг №2.

С этого шага потребуется уметь пользоваться терминалом. Создаём запускаемый файл /usr/local/bin/bt-lock следующего содержания:

#!/usr/bin/python

import objc
import sys
from subprocess import call

objc.loadBundle('IOBluetooth', globals(), bundle_path=u'/System/Library/Frameworks/IOBluetooth.framework')

def CheckDevice(address):
    dev = IOBluetoothDevice.withAddressString_(address)

    if dev.isConnected():
        return True

    conn = dev.openConnection()

    if conn == -536870185: # Bluetooth is off
        return True

    if conn == 0:
        dev.closeConnection()
        return True

    return False


cmdtorun=(r'/System/Library/CoreServices/Menu Extras/User.menu/Contents/Resources/CGSession', r'-suspend')

try:
    if not CheckDevice(sys.argv[1]):
        call(cmdtorun)
except IndexError:
    print("Usage: bt-lock <device address>")

В скобках замечу, чтобы сделать файл запускаемым нужно в терминале выполнить следующую команду: chmod a+x /usr/local/bin/bt-lock.

Шаг 3.

Создаём файл ~/Library/LaunchAgents/ru.bolknote.Bluetooth-lock.crontab.plist с таким вот содержимым:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>ru.bolknote.Bluetooth-lock.crontab</string>

  <key>Program</key>
  <string>/usr/local/bin/bt-lock</string>

  <key>ProgramArguments</key>
  <array>
    <string>/usr/local/bin/bt-lock</string>
    <string>а̲д̲р̲е̲с̲ ̲в̲а̲ш̲е̲г̲о̲ ̲у̲с̲т̲р̲о̲й̲с̲т̲в̲а̲</string>
  </array>

  <key>Nice</key>
  <integer>20</integer>

  <key>StartInterval</key>
  <integer>30</integer>

  <key>RunAtLoad</key>
  <true/>
</dict>
</plist>

И выполняем в терминале команду «launchctl load ru.bolknote.Bluetooth-lock.crontab.plist». Это всё.

Чтобы отключить всю эту марахайку, достаточно выполнить в терминале команду «launchctl unload ru.bolknote.Bluetooth-lock.crontab.plist» или выключить Bluetooth на ноуте.

Решение только для «Мака», как работать из Пайтона с Блютузом под другие операционные системы, я не знаю.

Bluetooth в Mac OS из Python

Я тут учусь работать с Блютузом из Пайтона на «Маке», пока сделал сканирование всех видимых в эфире устройств:

import objc
import time
from Foundation import NSDate, NSDefaultRunLoopMode
from AppKit import NSApplication, NSAnyEventMask

objc.loadBundle('IOBluetooth', globals(), bundle_path=u'/System/Library/Frameworks/IOBluetooth.framework')

class DeviceInquiry(NSObject): 
    def init(self):
        self = super(DeviceInquiry, self).init()
        self._inquiry = IOBluetoothDeviceInquiry.inquiryWithDelegate_(self)    
        self._stop = False
        
        return self
        
    def getfounddevices(self):
        self._inquiry.start()

        app = NSApplication.sharedApplication()
        app.setActivationPolicy_(2)        
        while not self._stop:
            time.sleep(.01)
            app.nextEventMatchingMask_untilDate_inMode_dequeue_(NSAnyEventMask,\
            NSDate.dateWithTimeIntervalSinceNow_(1), NSDefaultRunLoopMode, True)

        return tuple((dev.getName(), dev.getAddressString()) for dev in self._inquiry.foundDevices())

    @objc.typedSelector("v@:@@")
    def deviceInquiryDeviceFound_device_(self, inquiry, device):
        self._stop = True 

    @objc.typedSelector("v@:@iB")
    def deviceInquiryComplete_error_aborted_(self, inquiry, err, aborted):
        self._stop = True

print DeviceInquiry.new().getfounddevices()

Модуль pyobjc (в маковском Пайтоне он уже есть) — это мост между Пайтоном и Обжектив-Си, т. е. в реальности вызывается «кокосовое» АПИ «Мака».

Последняя строчка — пример использования, выдаётся список списков, состоящих из двух элементов — имени устройства и его «адреса» (это шесть шестнадцатеричных чисел, разделённые минусом).

Магические методы

Что мне никогда не нравилось в Пайтоне, так это «магические» методы и свойства. Сейчас их существует за сотню:

bolk@Bolk ~  $ wget -O- http://docs.python.org/reference/datamodel.html 2>&- | egrep -o '__(\w+)__' | sort -u | wc -l
     108

Я, даже если очень постараюсь, вряд ли вспомню хотя бы половину. Всё «магическое» в Пайтоне начинается и заканчивается двумя подчёркиваниями, например, если сменить знак у объекта (сделать „-something“), то Пайтон попытается вызвать магический метод «__neg__». В более распространнённом ПХП всё примерно так же, но «магических» методов сильно меньше, их я уже помню на память все, но и возможности гораздо уже.

Запомнить все эти методы, наверное, реально при помощи тщательной зубрёжки, но сделать это естетственно, во время изучения языка, нереально, по моему мнению. Причина — неинтуитивность. Я давно высказывался на тему, почему хотя бы не расширить синтаксис и некоторые методы не разрешить называть как операции, то есть «/», «+», «[]» и так далее, смутно вспоминая, что в Си++, кажется, так и сделано.

Оказывается, так в точности сделано в языке «Скала»:

trait Ord{
    def < (that:Any):Boolean
    def<=(that:Any):Boolean = (this < that) || (this == that)
    def> (that:Any):Boolean = !(this <= that)
    def>=(that:Any):Boolean = !(this < that)
}

Это гораздо проще выучить, изучая язык, чем «__lt__», «__le__», «__gt__», «__ge__» (впрочем, когда перечисляешь в ряд несколько конструкций, кажется что ничего страшного в этом нет, но надо держать в голове, что их больше сотни).

У некоторых методов название упрощать не нужно, например «__unicode__» (преобразование к юникодной строке) всё равно дёргается стоит вызвать unicode(something) или «__del__» (деструктор) вызывается, в частности, если сделать «del something» (если ссылок больше нет). Они уже содержат название операции, просто оно буквенное. Есть методы, которые вряд ли вообще можно как-то проще назвать, например «__new__» и «__init__» — названия конструкторов для «новых» и «старых» объектов Пайтона.

Наверняка есть часть, которую понятнее не назовёшь. На ум сразу приходит метод «__hash__», думаю есть и ещё.

Но, бо́льшую часть, мне бы хотелось именовать как-то понятнее. Причём, вполне можно предложить синтаксис для левостоящих и правостоящих объектов, что-то вроде «def self+» и «def +self».

Банкоматы «Ак Барс Банка»

У меня зарплатная карточка «Ак Барс Банка», он вряд ли известен за пределами нашей республики, но в Татарстане широко распространён. Этот банк входит в организацию «Объединённая расчётная система», в которую входят ещё несколько банков, в банкоматах которых карты «Ак Барса» обслуживаются на льготных условиях.

Список банков-участников есть на сайте, но там их много, а банкоматы в нашем городе установлены далеко не у всех. Чтобы смотреть какие банкоматы «ОРСа» есть в Казани, я написал простую консольную программу на «Пайтоне», может кому-то ещё пригодится.

# coding: utf-8

import urllib
import re
import sys
import codecs
from itertools import ifilter

# Чтобы при перенаправлении в файл была кодировка UTF-8
sys.stdin = codecs.getreader('utf-8')(sys.stdin)
sys.stdout = codecs.getwriter('utf-8')(sys.stdout)

# Конфигурация
ORS_URL    = 'http://ors.ru/cashpoints/print.asp'
ORS_CITY   = u'Казань'
ORS_REGION = u'Республика Татарстан'

# Данные для оправления через POST
postdata = urllib.urlencode({
    'CITY': ORS_CITY.encode('cp1251'),
    'REGION': ORS_REGION.encode('cp1251'),
    'CURRENCY': 'RUR',
    'OPERATION': 'cash-out',
})

content = urllib.urlopen(ORS_URL, postdata).read().decode('cp1251')

# Ищем все ячейки таблицы и выбираем каждую вторую из шести
tds = re.finditer('<td[^>]*>(.+?)</td>', content, re.S | re.I)
tds = ifilter(lambda x: x[0] % 6 == 1, enumerate(tds))

# Берём только уникальные значения и рубим лишние символы   
for x in set(x[1].group(1) for x in tds):
    print(x.replace('&nbsp;', ' '))

Выводится список банков (регион и город задаётся в коде в очевидном месте), например, на настоящий момент список выглядит вот так:

ОАО Банк ЗЕНИТ 
ОАО "РГС Банк" 
АКБ "Абсолют Банк" (ЗАО) 
Быстробанк ОАО 
ОАО "АК БАРС" БАНК 
АКБ "РОСБАНК" (ОАО) 
НОМОС-БАНК (ОАО) 
АКБ МБРР (ОАО) 
ЗАО АИБ "Ипотека-Инвест" 
ОАО "АИКБ "Татфондбанк" 
АКБ "БТА-Казань" (ОАО) 
КБ "Юниаструм Банк" (ООО) 
ОАО АКБ «Авангард» 
ОАО КБ "Восточный"

ChinesePython

А вы знали, что существует ChinesePython? Выглядит как настоящий, но не радует.

Язык полностью переведён на китайский, включая ключевые слова, встроенные типы и так далее, код основан на Пайтоне 2.1.3. Вот пример того как смотрится программа на этом языке:

載入 系統
文件名 = 系統.參數[1:]

定義 修正行尾(文件):
    內文 = 打開(文件名).讀入()
    內文 = 內文.替換('\n\r','\n')
    傳回 內文

取 文件 自 文件名:
    寫 修正行尾(文件)

«載入 系統» — это «import sys». Эта же программа на традиционном Пайтоне выглядит вот так:

import sys
filenames = sys.argv[1:]

def fixline(filename):
    text = open(filename).read()
    text = text.replace('\n\r','\n')
    return text

for file in filenames:
    print fixline(file)

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

Ерунда на itertools и генераторах

Я люблю itertools и генераторы в Пайтоне, но как-то по работе пока не удаётся их применить, поэтому, чтобы не завять, приходится время от времени писать всякую ерунду.

Прочитал тут на «Хабре», что дробь 1/998001 даёт последовательность из чисел от нуля до 997. Вот я и написал код с использованием генераторов и itertools, который берёт эту дробь и выводит из неё все числа последовательно, пока не попадутся числа с разницей не равной единице.

from decimal import Decimal, getcontext
from itertools import groupby, islice, izip, takewhile, chain, imap

getcontext().prec = 3000

n = (
    groupby(
        enumerate(
            islice(str(1 / Decimal(998001)), 2, None)
        ),
        lambda n: n[0] // 3)
    )

n = (int(''.join(n[1] for n in n[1])) for n in n)

n = chain(*takewhile(lambda n: n[1] - n[0] == 1, izip(n, n)))

print ', '.join(imap(str, n))

Кстати, для вычисления дроби с точностью до трёхтысячного знака, используется модуль decimal.

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

from decimal import Decimal, getcontext

getcontext().prec = 3000

number, prev, out = str(1 / Decimal(998001)), -1, []

for i in xrange(2, len(number), 3):
    curr = int(number[i:i+3])

    if curr - prev != 1: break

    prev = curr
    out.append(str(curr))

print ', '.join(out)

Стеганография и пробел нулевой длины

Болею, не спится мне. Вспомнил старую свою идею.

В Юникоде есть такой символ замечательный — пробел нулевой длины (код 0x200B), на печать не выводится, понимается всеми современными браузерами и большинством редакторов. Интервала между буквами, как следует из названия, не даёт.

Идея простая, позволяет прятать в текст другой текст, так, чтобы первый не менялся, а второй всегда копировался вместе с первым. Основная мысль — пользуясь буквами исходного текста, как разделителем, ставим между ними столько пробелов нулевой длины, чтобы их число равнялось коду скрываемого символа.

Например. Дан текст: «Болк», в нём надо скрыть короткий текст: «yes». Я взял английские символы, чтобы не заморачиватсья с кодировкой.

Коды символов «yes» — 121, 101, 115. Значит текст приобретает следующий вид:

[121 символ пробела нулевой длины]Б[101 символ пробела нулевой длины]о[115 символов пробела нулевой длины]лк

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

​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​#​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​E​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​x​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​a​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​m​p​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​l​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​e​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​b​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​y​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​E​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​v​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​g​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​e​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​n​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​y Stepanischev
from itertools import groupby, izip_longest
import sys
import codecs

sys.stdin = codecs.getreader('utf-8')(sys.stdin)
sys.stdout = codecs.getwriter('utf-8')(sys.stdout)

toenc = 'Evgeny Stepanischev'
input = sys.stdin.read()

def decode(input):
    return ''.join(chr(31+len(list(x[1])))
            for x in groupby(input, lambda x: x == u"\u200b") if x[0])

def encode(input):
    if len(input) < len(toenc):
        raise ValueError()

    return ''.join(x[0] + x[1]
            for x in izip_longest((u"\u200b" * (ord(x)-31)
            for x in toenc), input, fillvalue=''))


print(encode(input) if input.find(u"\u200b") == -1 else decode(input))

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

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

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

Обработка данных интернет-клиента «Альфа-банка»

Накидал небольшую программу для обработки данных из интернет-клиента «Альфа-банка». Первым параметром указывается CSV-файл, выгруженный из клиента, на выходе три столбца — дата операции, сумма и описание.

Программа на Пайтоне:

# coding: utf-8
import sys
import re
from datetime import date
from itertools import islice

output = []

with open(sys.argv[1], 'r') as f:
    for line in islice(f, 1, None):
        n, detail, arr, flow = line.decode('cp1251').split(';')[4:8]

        if n[:4] == 'BERR' or n[:6] == 'CASHIN': continue
        flow = float(flow.replace(',', '.'))

        if flow > 0:
            dates = re.findall(r'\d{2}\.\d{2}\.\d{2}', detail, re.UNICODE)

            if dates:
                d = min(date(*(int(x) for x in d.split('.')[::-1])) for d in dates)
                output.append({
                    'detail': detail,
                    'flow': flow,
                    'date': d,
                })

for k in sorted(output, lambda x, y: cmp(x['date'], y['date'])):
    data = zip(('y', 'm', 'd',), k['date'].timetuple()[:3]) + k.items()

    print "%(d)02d.%(m)02d.%(y)02d %(flow)09s %(detail)s" % dict(data)

Кодировка rot13 в Пайтоне

В стандартном интерпретаторе Пайтона есть много такого, что иначе как «пасхальным яйцом» не назовёшь. Например, можно попробовать импортировать модули «__hello__», «this» или сделать «from __future__ import braces» (попробуйте), но следующее просто гениально, хотя, видимо, получилось случайно.

В файле программы на Пайтоне в специальном коментарии принято указывать кодировку файла. В случае с латиницей работает и без этого, если применяется UTF-8, то это делать обязательно. Интерпретатор поддерживает множество кодировок (честно сказать, я не знаю сколько именно) и одна из них — rot13. Используются те же самые кодировки, что и в методах encode и decode у строки. Я думаю после считывания файла программы именно они и вызываются.

Вот вполне валидная программа, которая выполняется и делает нечто осмысленное:

# coding: rot13

vzcbeg __uryyb__
cevag "Zl anzr vf Ritral Fgrcnavfpuri nxn Obyx!".rapbqr("rot13")

Кодируется только текст программы, поэтому-то мне и пришлось «rot13» написать как есть.

Сравнение объектов встроенных типов в Пайтоне

Некоторым ребятам показалось, что в заметке про «goatse operator» в Перле я поругал этот язык. Это не так, можно прочитать заметку от и до, чтобы в этом убедиться. Мне очень нравится Перл, но это язык не для современного продакшна. Давайте я что-нибудь забавное расскажу и о языке, на котором больше всего сейчас программирую — про Пайтон.

В Пайтоне всё что только можно — объект и нет примитивных типов. С объектами можно делать всё что хочется — сравнивать, умножать, производить битовые операции. Чтобы программист мог указать как объекту следует себя вести рядом с каким-то оператором, следует определить некоторые «магические» методы, например, «__cmp__», «__mul__» и какой-нибудь «__xor__».

Где-то внутри какие-то «магические» методы определены и для встроенных типов (которые тоже являются объектами, напоминаю), ведь надо же как-то сравнивать две строки или строку и число:

In [26]: "Это строка".__eq__
Out[26]: <method-wrapper '__eq__' of str object at 0x10136f770>

In [27]: "Это строка".__ne__
Out[27]: <method-wrapper '__ne__' of str object at 0x10139cf70>

Сравнивать между собой можно большинство встроенных типов — список с со словарём, строку с кортежем и так далее. Есть исключения (например, set нельзя сравнить ни с чем, кроме set и frozenset), но их мало. Какой же будет результат сравнения? Давайте попробуем:

In [44]: "">()
Out[44]: False

In [45]: ""<()
Out[45]: True

In [46]: []>()
Out[46]: False

In [47]: []<()
Out[47]: True

In [48]: []<{}
Out[48]: False

In [49]: []>{}
Out[49]: True

Даже пустые объекты разных типов не равны между собой, но какое-то правило определённо есть, не случайным же образом создатели языка выбирали что больше чего. Добавление значений в объекты ситуацию не меняет, не играет роли их число и значение. Я намеренно оставляю в стороне числа (типы int, float и complex) и лишь походя затрагиваю строки (str и unicode), там как там правила немного другие, но это неважно.

In [52]: [1,2,3,4,5,6]<(0,)>[100000]
Out[52]: True

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

True < {} < [] < object() < "" < () < u""
# b < d < l < o < s < t < u

Решение мне кажется, мягко говоря, неудачным, но уж какое выбрали. Я не вижу ничего страшного в том, чтобы сравнение встроенных типов всегда выдавало False (попробуйте сравнить между собой два None) или выдавать исключение при попытке их сравнения (кажется в 3-й версии Пайтона так и сделали).

Ранее Ctrl + ↓