Блоґ одного кібера

Історія хвороби контуженого інформаційним вибухом

Posts Tagged ‘Python

Пишемо переглядач молекул з Pyglet

with 2 comments

Я хотів створити серію уроків про графіку в OpenGL по слідах NeHe, але отримав іншу пропозицію, і пріоритети змінились. Ну й графіка в наш час людей не так цікавить. Але так як задачу я почав робити, просто витирати її з списку проектів буде не цікаво, краще опублікувати те що є і перенести в список закінчених проектів. Чим я зараз й займусь.

Ідея програми – намалювати атоми сферами різних кольорів і розмістити їх в різних місцях простору, таким чином отримавши молекулу. Для цього нам треба знати координати. Для цього ми використаємо Open Babel – хімічну експертну систему. Ось інструкції з інсталяції, apt-get install python-openbabel якщо кому лінь їх читати.

Глюкоза

Молекула глюкози

Користуючись нею, ми можемо перетворити формулу SMILES, на список координат атомів:
Прочитати решту цієї замітки »

Written by bunyk

Липень 24, 2015 at 18:30

Оприлюднено в Графіка, Кодерство

Tagged with , ,

OpenGL в Python

with 4 comments

Мене якось запитали про це, але без підготовки пояснити було важко, крім того мета була амбітна – намалювати молекулу, тому вийшло не так добре як би хотілось. Спробую написати короткий покроковий вступ в цю тему, який приблизно слідує послідовності в старих уроках Nehe (так, я чув що вони застаріли, але для нового OpenGL з шейдерами я якихось гарних послідовних уроків не бачив).

Інсталяція та перше вікно

Найперше що потрібно графічним програмам – вікно. Щоб створити вікно, нам треба якусь бібліотеку, наприклад PyQt, PySide, PyGtk, WxPython чи PyGame – їх купа. Потрібно також щоб це вікно підтримувало контекст OpenGL (тобто могло дозволити відеокарті виводити свої дані в область вікна). З цим може справитись багато бібліотек, але ми виберемо Pyglet. Тому що в ній мало зайвого, і вона ставиться традиційно:

pip install pyglet

Ну, і як годиться – почнемо з найпростішої програми:

import pyglet

window = pyglet.window.Window(width=640, height=480, caption="Hello OpenGL!")
pyglet.app.run()

Отримаємо вікно заданої ширини та висоти, і з заданим заголовком:

Наше перше вікно

Наше перше вікно

Елементарно, правда?

Фарби

Давайте ще зафарбуємо вікно в білий колір. Для цього потрібно знати що кольори задаються переважно інтенсивністю світла в моделі RGB (червоний, зелений, голубий), числами від 0 до 1. Тобто білий – це 1.0, 1.0, 1.0, сірий – 0.5, 0.5, 0.5, і т.п. Детальніше на вікіпедії.

import pyglet
from pyglet.gl import * # імпортуємо всі функції OpenGL
# вони починатимуться з префіксів gl або glu, тому простір імен надто не засмічуватимуть

window = pyglet.window.Window(width=640, height=480, caption="Hello OpenGL!")

# я не буду довго пояснювати що таке декоратор. Просто знайте, що 
# @window.event позначає функції що відповідають за обробку подій

@window.event
def on_draw(): 
    # викликатиметься, коли операційна система вирішить що вікно треба перемалювати
    # наприклад, коли ми забрали вікно що було над нашим, або вперше виводимо його на екран  

    glClearColor(1.0, 1.0, 1.0, 1.0) # Задати колір яким ми будемо очищати екран. 
    # Четверте число - прозорість.
    # Я його сам не дуже розумію, але обов’язково треба чотири параметри.

    glClear(GL_COLOR_BUFFER_BIT) # очистити буфер кольору 
    # (бувають і інші буфери, але про це пізніше)

pyglet.app.run()

To be continued

В цьому уроці я хотів ще написати про те як намалювати трикутник, але часу мало (тобто є інші пріорітети). Зате ми створили вікно і навчились змінювати його колір. Ну й краще напевне писати менше але частіше. Якщо пілотний епізод цього курсу буде популярний – подумаю чи випускати перший сезон.

P.S. Май на увазі, якщо ти не хочеш навчити свою дівчину програмувати – вона може знайти когось хто схоче. :P Або взагалі сама з допомогою інтернету звикне вчитись.

Written by bunyk

Липень 16, 2015 at 02:45

Оприлюднено в Графіка, Кодерство

Tagged with ,

Анонс Lvivpy4

with 19 comments

Попередні три я пропустив бо не читаю новини, але от на цьому буду, тому що там я буду нести людям світло науки і знання.

Цитую сам себе:

Ви дізнаєтесь що таке компонент, інтерфейс, інваріант, утиліта, фабрика, адаптер (це не тільки шаблони), реєстр компонентів, і купу інших розумних слів. А також як можна застосовувати штуки що ці слова позначають щоб побудувати систему яку можна розширювати новими компонентами, і реалізувати “статичну типізацію” та “слабку типізацію”.

Одним словом те що ви могли почитати в трактаті про ZCA, тільки тепер в авторській озвучці, + можна буде задавати питання, і побачити мене в 3D. Головне руками не чіпати! :)

Тому якщо в кого виникне таке бажання – заходьте в Офіс Lohika Systems, Львів, вул. Лемківська 15а, 2-й поверх, 30-го травня. Реєстрація: http://www.meetup.com/uapycon/events/222342688/

І не переживайте, там доповідаю не тільки я.

Робота і життя після відпустки починається шалено (правда якщо врахувати що під час відпустки я більшість часу лише спав, їв і дихав), я навіть 10% всього цікавого зараз не розповів, але те що розповів – одне з найголовнішого. :)

Written by bunyk

Травень 20, 2015 at 23:50

Оприлюднено в Кодерство, Нещоденник

Tagged with

Випадковий ідентифікатор в Python

with 4 comments

Можна отримати так:

import random
def random_id(length=6):
   return ''.join(
        random.choice(string.lowercase)
        for i in range(length)
    )

###############
>>> random_id()
'kqxmua'

Якщо треба особливо оформлений, як от IP, чи MAC-адреса, то можна зробити перетворення:

def asmac(val):
    """Convert a byte string to a MAC address string.  """
    return ':'.join('%02X' % ord(c) for c in val)

def random_mac():
    return asmac(random_id())

###################
>>> random_mac()
'78:71:6A:72:6E:63'

Але такі ідентифікатори як “kqxmua” нормальній людині важко запам’ятати, бо вони не асоціюються з жодними поняттями. Ну окрім частинки “ua”, але й то вона туди випадково потрапила. Проте, в Linux можна легко отримати випадкове слово, бо в ньому є словник:

def random_word():
    return random.choice(
        open('/usr/share/dict/words').readlines() # жертиме пам’ять! 
    ).strip()

#################
'.join(random_word() for i in range(5))
'hermitical, Canter, Paryavi, mergences, Mind'

Хоча я знайомий лише з “hermitical” та “mind”, але асоціації вже легше побудувати, правда?

Written by bunyk

Березень 18, 2015 at 12:34

Оприлюднено в Всяке, Кодерство

Tagged with ,

Зберегти всю стіну групи VK в таблицю

leave a comment »

Якось під час чергового наближення економічної кризи захотілось проаналізувати ціну нерухомості в Львові. А ще, в той же час я наткнувся на документацію API Вконтакті. А так як своє житло я шукав в тому числі і в тій соціальній мережі, то вирішив проаналізувати наприклад спільноту vk.com/nomakler.

Ну, проаналізувати це легко сказати – важче зробити. Як витягти з повідомлення ціну, і як відрізнити попит від пропозиції? Га?

Але є половинний результат – ФСБ мусило поділитись частиною своєї бази даних розміром в 40450 оголошень. Тут можна її завантажити як tsv, xls чи інший зручний для вас формат. Може комусь, хто захоче збільшити конкуренцію серед львівськи маклерів/ріелторів знадобиться.

А якщо кому потрібна інформація з інших груп – ось скрипт. Викликаєте функцію save2tsv з назвою групи і назвою файлу в який писати – і чекаєте поки завантажиться.

import requests
import json
from pprint import pprint
from itertools import islice
from datetime import datetime

from butils.csv_wrapper import UnicodeWriter

class APIError(Exception):
    pass

def vk(method, **kwargs):
    '''
        https://vk.com/dev/methods
    '''
    r = requests.get(
        'https://api.vk.com/method/%s' % method,
        params=kwargs
    )
    js = json.loads(r.text)
    if js.get('error'):
        raise APIError(js['error']['error_msg'])

    return js['response']


def get_users(ids, known_users={}):
    request_ids = [i for i in ids if i not in known_users]
    if request_ids:
        user_ids=','.join(str(i) for i in request_ids if i > 0)
        if user_ids:
            users = vk('users.get', user_ids=user_ids)
        else:
            users = []
        group_ids=','.join(str(-i) for i in request_ids if i < 0)
        if group_ids:
            groups = vk('groups.getById', group_ids=group_ids)
        else:
            groups = []
        for user in users:
            known_users[int(user['uid'])] = dict(
                first_name = user['first_name'],
                last_name = user['last_name'],
            )
        for group in groups:
            known_users[-int(group['gid'])] = dict(
                first_name = group['name'],
                last_name = group['gid']
            )
    return known_users


def get_wall(domain):
    count = 50
    offset = 0
    def get_with_offset(offset):
        nonlocal count
        print('get_with_offset(%s)' % offset)
        total = vk('wall.get',
            domain=domain,
            count=1,
        )[0]
        off = total - offset - count
        if off < 0:
            count = count + off
            off = 0
        return vk('wall.get',
            domain=domain,
            count=count,
            offset=off,
        )[1:][::-1] # remove first and reverse

    while True:
        posts = get_with_offset(offset)
        offset += count
        users = get_users(p['from_id'] for p in posts)
        for p in posts:
            yield p, users[p['from_id']]
        if count < 50:
            return


def save2tsv(domain, dst):
    with UnicodeWriter(dst, encoding='utf-8', delimiter='\t') as writer:
        writer.writerow((
            'ID',
            'Datetime',
            'First name',
            'Last name',
            'Text',
            'Type',
            'Comments',
            'Reposts',
            'Likes',
        ))
        for p, user in get_wall(domain):
            writer.writerow(list(map(str, (
                p['id'], # ID
                datetime.fromtimestamp(p['date']), # Datetime
                user['first_name'], # First name
                user['last_name'], # Last name
                p['text'], # Text
                p['post_type'], # Type
                p['comments']['count'], # Comments
                p['reposts']['count'], # Reposts
                p['likes']['count'], # Likes
            ))))

# save2tsv('nomakler', 'nomakler.tsv')

Written by bunyk

Березень 8, 2015 at 17:38

Оприлюднено в Кодерство, Павутина

Tagged with

Пишемо простий keylogger для Linux

with 6 comments

Кейлоггер це така шкідлива корисна програма яка записує всі натискання клавіш користувачем в будь-яких інших програмах операційної системи. І сьогодні ми вивчатимемо як таке написати. Якщо ви навчитесь працювати з файлами в Linux – ви зможете все. Серйозно. Тому я колись планую детально вивчити і написати про файли там.

Все в лінуксі файл, і клавіатура – теж. Щоб знати який – відкриваємо /proc/bus/input/devices. Нас цікавить абзац в якому написано про клавіатуру, і він зазвичай містить рядок EV=120013, тому можете пошукати його. Коли знайшли абзац, читаємо в ньому рядок H: Handlers=sysrq kbd event3.

Можна дістати однією командою:

cat /proc/bus/input/devices | grep EV=120013 -B 2 | grep event

Слово event3 означає що нам треба читати файл пристрою /dev/input/event3.

Так як в заголовку було слово простий, ми спростимо собі життя і поставимо деяку бібліотеку:

pip-2.7 install evdev

Прочитати решту цієї замітки »

Written by bunyk

Березень 8, 2015 at 00:29

Оприлюднено в Кодерство

Tagged with ,

Простий HTTP клієнт і сервер на сокетах

with 12 comments

В Python є мільйони бібліотек для роботи з HTTP, але так як HTTP працює через TCP/IP, то всі вони працюють використовуючи штуку яка називається socket. Сокет – це дуже низькорівнева річ, що нагадує файл (але не пітонівський об’єкт файлу, і навіть не сішний, а доступ до файлу через дескриптор, за допомогою викликів операційної системи open, write та read).

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

Клієнт

# coding=utf-8

import socket
import sys

def main():
    get(sys.argv[1])

def get(url):
    # Парсимо url на домен і GET запит в межах домену
    if url.startswith('http://'):
        url = url[len('http://'):]
    domain, query = url.split('/', 1)

    # створюємо сокет
    clientsocket = socket.socket(
        socket.AF_INET, socket.SOCK_STREAM
    )
    # AF_INET - аднесна сім’я сокета - інтернет (бувають юнікс і всілякі інші)
    # SOCK_STREAM - потоковий сокет (TCP). Бувають дейтаграмні (UDP).

    # з'єднуємось з 80-тим портом сервера:
    clientsocket.connect((domain, 80))

    # відправляємо туди всі дані запиту (поки не відправляться)
    # метод send() відправляє певну кількість байт і повертає ціле число - 
    # скільки відправив. Далі треба вручну досилати, нас ця зайва робота
    # не цікавить.
    clientsocket.sendall(query_template % (query, domain))

    while True:
        # отримуємо по 4096 байт даних
        # кажуть для максимальної продуктивності треба просити невелику 
        # степінь двійки байт
        data = clientsocket.recv(4096)

        # в stderr пишемо склільки насправді отримали (це цікаво)
        sys.stderr.write('DEBUG: Got %s bytes\n' % len(data))

        if len(data) == 0: # якщо даних більше нема - 
            break # то можна закінчувати

        # в stdout - наші дані
        sys.stdout.write(data)


    clientsocket.close() # закриваємо сокет

query_template = '''GET /%s HTTP/1.1
Host: %s
User-Agent: python
Connection: close

'''.replace('\n', '\r\n')
# В HTTP рядки заголовків розділяються \r\n
# Кінець заголовків позначається порожнім рядком, тому якщо його забути
# сервер буде довго чекати поки ви закінчите, а потім пришле
# 408 Request Timeout
# Connection: close - каже йому що можна закрити сокет після того як дані отримано.


if __name__ == '__main__':
    main()

Тест:

$~ python client.py https://bunyk.wordpress.com/
DEBUG: Got 387 bytes
HTTP/1.1 301 Moved Permanently
Server: nginx
Date: Fri, 06 Mar 2015 07:39:41 GMT
Content-Type: text/html
Content-Length: 178
Connection: close
Location: https://bunyk.wordpress.com/
X-ac: 1.fra _sat

<html>
<head><title>301 Moved Permanently</title></head>
<body bgcolor="white">
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx</center>
</body>
</html>
DEBUG: Got 0 bytes

На жаль в наш час мало що можна отримати, бо всі хочуть перенаправити тебе на https, а він складніший за http, але й по самій відповіді 301 бачимо що клієнт працює.

Тепер складніше, але не набагато:

Сервер

Сервер, на відміну від клієнта, який створює з’єднання, посилає запит, читає відповідь і закриває з’єднання, повинен відкрити порт, і постійно очікувати з’єднань на ньому. Якщо з’єднання відбувається – прочитати запит і послати відповідь.

# coding=utf-8

import socket

def main():
    HttpServer(8080).run()

class HttpServer(object):
    def __init__(self, port=8000):
        # створюємо абсолютно такий самий сокет як і в клієнта
        self.socket = socket.socket(
            socket.AF_INET, socket.SOCK_STREAM
        )
        # але замість того аби приєднуватись до сокета на чужому 
        # сервері - приєднуємось до сокета на нашому:
        self.socket.bind(('', port))
        # і очікуємо з’єднання 
        # 5 - розмір черги з’єднань
        self.socket.listen(5)
        print 'Serving at', port

    
    def run(self):
        try:
            while True:
                # прийняти наступне з’єднання
                (conn, address) = self.socket.accept()
                print 'Connection from', address
                data = conn.recv(1024)
                # прочитати 1024 байт запиту 
                # (припустимо що цього буде досить)

                # послати назад заголовки відповіді HTTP 200 OK, 
                # і вміст - отриманий запит
                conn.send(http_ok(data))
                # І закриваємо з’єднання з клієнтом
                conn.close()
        except KeyboardInterrupt:
            print 'Bye!'

    def __del__(self):
        # Сокет треба закрити, щоб при наступному запуску
        # нам не сказали що він вже зайнятий.
        self.socket.shutdown(socket.SHUT_RDWR)
        self.socket.close()


def http_ok(content):
    return (
        'HTTP/1.0 200 OK\r\n'
        'Content-Type: text/html\r\n\r\n'
        '<html><body><pre>%s</pre></body></html>'
        % content
    )

if __name__ == '__main__':
    main()

Тепер, коли ми запустимо сервер, і відкриємо в браузері localhost:8080, то побачимо там щось схоже на:

GET / HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:35.0) Gecko/20100101 Firefox/35.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: __utma=111872281.1988111138.1417337610.1419706230.1419710582.13; __utmz=111872281.1417337610.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)
Connection: keep-alive

Цей чарівний lo-інтерфейс

Найбільш радісним для мене було відкриття того, що якщо ми перейдемо за адресою 127.123.123.123:8080, чи будь-якою іншою виду 127.*.*.* (окрім 127.255.255.255), ми отримаємо відповідь:

GET / HTTP/1.1
Host: 127.123.123.123:8080

А це означає що ми можемо одним нашим сервером імітувати 16 хостів. HTTP передає заголовок HOST, саме тому, що один сервер може обробляти дані кількох хостів (які називаються віртуальними), і йому потрібно їх якось розрізняти.

Тут з’являється інша проблема – якщо ми хочемо імітувати кілька тисяч хостів, але не HTTP, а наприклад SNMP. В SNMP такого поля як адреса хоста на який ми послали запит нема, а так як всі потрапляють на один і той самий localhost, то й відрізнити їх нема як. Зате звісно таке поле є в пакету IP, і здається є спосіб до заголовків цього протоколу дістатись, використовуючи дещо, що називається raw socket. Проте це чорна магія якою я поки що ще не оволодів, тому залишу це до наступного разу.

Written by bunyk

Березень 7, 2015 at 00:46

Оприлюднено в Кодерство, Павутина

Tagged with ,

Підписатися

Get every new post delivered to your Inbox.

Join 290 other followers