Tag Archives: python

Побудова “скриньок з вусами” львівських квартир що здаються на сьогодні

Я ще минулого року помітив що в питаннях про Python на StackOverflow обговорюють якісь панди. Це, як виявилось обгортка навколо matplotlib, numpy і подібних гарних речей. А ще, лазячи по своїх документах в Google знайшов скачану вже позаминулого року стіну групи пошуку нерухомості вконтакті. І так співпало що я і мій колега-аналітик зараз шукаємо квартиру у Львові. Я йому показав цей файл, і він загорівся бажанням проаналізувати ще якийсь сайт оголошень.

При всій повазі до lun.ua, але тут я прорекламую dom.ria.com. Передовсім, там є українська версія. А ще, можливість скачати результати пошуку як електронну таблицю, хоч і в xls форматі, і лише одну сторінку.

В python читати xls вміє бібліотека xlrd, тому треба доставити ще й її. Pandas взагалі має багато необов’язкових залежностей:

sudo pip3.5 install jupyter pandas xlrd matplotlib
jupyter notebook # дуже модний графічний інтерпретатор

Якщо все поставити як вище і запустити “jupyter”, то можна робити обчислення в отакому документі: https://github.com/bunyk/mypandas/blob/master/dom.ria/dom.ria.ipynb

І можна побудувати графік скринька з вусами:


От, недаремно я деякі лекції з АнДану все таки не проспав! Хоча, який висновок робити з цього графіка – не знаю. Знаю лише що половина квартир потрапляють всередину прямокутника.

А ось гістограми по цінах для однокімнатних і двокімнатних:

Однокімнатні

Однокімнатні

Двокімнатні

Двокімнатні

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


Filed under: Інструменти, Кодерство, Павутина Tagged: графіка, математика, Python

Проста схема перетворення інтерактивної процедурної програми з goto в функціональну рекурсивну

Власне вся передмова помістилась в заголовок. Хоча може для цього “паттерну” є коротша назва.

Існує клас інтерактивних програм які очікують вводу користувача, потім залежно від того вводу щось роблять, потім знову очікують вводу і так далі. Наприклад якась така програма “вгадай число”:

import random

def game():
    print('Як тебе звуть?')
    user = input()

    print('Привіт,', user)
    print('Давай пограємо гру відгадай число?')

    while True:
        number = random.randint(1, 10)
        print('Я загадав число від 1 до 10.')
        print('Спробуєш вгадати?')

        while True:
            print('Вводь свій варіант:')
            guess = input()
            if guess == number:
                print('Ого, так швидко вгадав. Грати ще раз?')
                while True:
                    answer = input()
                    if answer in ('так', 'ні'):
                        break
                    print('Ну то так чи ні?');

                if (answer == 'ні'):
                    print('Ну тоді бувай!')
                    return
                if (answer == 'так'):
                    break
            else:
                print('Ні, моє число ' +
                    ('більше' if (scope.guess < scope.number) else 'менше')
                    + ', пробуй ще'
                )
game()

JavaScript має з такими програмами проблему, бо він ніколи не зупиняється очікуючи на ввід користувача (якщо не рахувати функції alert() та компанії, він викликає функції які обробляють події, як от подію вводу. Щоб написати подібну програму для браузера, ми повинні реалізувати в ньому щось на зразок “консолі”:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
</head>
<body>
    <pre id="console"></pre>
    <input type="text" id="input" />

<script>
(function() {
    var input = document.getElementById('input');
    var output = document.getElementById('console');

    var write = function(text) {
        // Write text to console
        output.innerHTML += text;
    };
    var writeln = function(text) {
        // Write line to console
        write(text + 'n');
    };
    
    // Register default callback of echoing input
    var input_callbacks = [function(input) {
        writeln('> ' + input);
    }];

    var on_input = function(callback) {
        // Register input callback

        input_callbacks[1] = callback;
    }

    input.onkeyup = function(e) {
        // Call all callbacks on input
        if (e.keyCode == 13) { // Enter pressed
            for(var i=0; i < input_callbacks.length; i++) {
                input_callbacks[i](input.value);
            }
            input.value = '';
        }
    };

    // Module exports:
    window.CLI = {
        write: write,
        writeln: writeln,
        on_input: on_input
    };
}());
</script>

<script>
// Ну а тут буде наша програма для "консолі". 
</script>
</body>
</html>

Ми маємо модуль CLI, який містить три функції. write та writeln дописують до елемента “консоль” переданий їм текст, а функція передана в on_input, буде викликатись отримуючи вміст елемента input коли в ньому натиснуть Enter. Проблема якраз в тому що буде викликатись окрема функція, якій передадуть ввід, і нема функції яка б той ввід просто повернула. Як написати для такої консолі інтерактивну гру “вгадай число”?

Тут на допомогу може прийти цікаве визначення алгоритму, яке я знайшов в книжці “Мистецтво програмування” Дональда Кнута (ні, я її не осилив, не зміг запам’ятати набір інструкцій MIX, хоча може варто пошукати якийсь емулятор цієї машини, щоб та книжка веселіше проходилась).

Так от, Кнут пише що будь-яку програму можна задати фунцією P(step, scope), яка приймає набір аргументів scope (що може бути одним аргументом – словником). Таким чином ми уникаємо зміни стану і оператора присвоєння, викликаючи функцію рекурсивно з новини аргументами.

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

    i = 0
1:  writeln i
    i = i + 1
    goto 1

Можна переписати на JavaScript рекурсивно так:

    function exec(step, scope) {
        CLI.writeln(scope.i);
        exec(step, {i: scope.i+1});
    }

    exec(1, {x: 0});

Варто також зауважити що JavaScript не має оптимізації хвостової рекурсії, тому така програма в Firefox зупиняється вивівши число 11085, і пожалівшись в консоль повідомленням “too much recursion”.

Тобто правило таке – як тільки ми хочемо змінити стан – ми викликаємо нашу функцію яка задає програму передавши їй цей новий стан. Ми також можемо робити умовні і безумовні переходи, викликаючи нашу функцію з різними значеннями параметра step. Подивимось як ми зможемо переписати програму гри “вгадай число”, яку описали вище на JavaScript. Але для цього спершу перепишемо її на наш діалект BASIC:

1:  writeln 'Як тебе звуть?'
    input user
2:  writeln 'Привіт,' + user
    writeln 'Давай пограємо гру "Відгадай число"?'
3:  writeln 'Я загадав число від 1 до 10.'
    number = randint(1, 10)
    writeln 'Спробуєш вгадати?'
4:  writeln 'Вводь свій варіант:'
    input guess
5:  if guess == number then 9
6:  if guess < number then 8
7:  writeln 'Ні, моє число менше, пробуй ще' 
    goto 4
8:  writeln 'Ні, моє число більше, пробуй ще'
    goto 4
9:  writeln 'Ого, так швидко вгадав. Грати ще раз?')
    input answer
10: if anwser == 'так' then 3
11: if answer == 'ні' then 13
12: writeln 'Ну то так чи ні?'
    input answer
    goto 10
13: writeln 'Ну тоді бувай!'

Тепер переписуємо цю програму на JavaScript за такими правилами:

  1. Кожен набір операторів що починається міткою ми записуємо в блок:
    if (step == 1 /*мітка*/) {
        // Код сюди
        return;
    };
    
  2. Кожне присвоєння відбувається в scope:
    if (...) {
        scope.number = Math.floor(Math.random() * 10) + 1;
        ...
        return;
    };
    
  3. Кожен блок що закінчується goto закінчується викликом exec:
    if (...) {
        // Код сюди
        exec(4 /* куди послало goto */, scope);
        return;
    };
    
  4. Кожен блок що закінчується if then:
    if (...) {
        // Код сюди
        if (...) {
            exec(3 /* куди послав then */, scope);
        } else {
            exec(11 /* перехід до наступного блоку */, scope);
        }
        return;
    };
    
  5. Кожен блок що закінчується input закінчується викликом exec для наступного кроку в callback:
    if (...) {
        // код сюди
        CLI.on_input(function(input) {
            scope.guess = input;
            exec(5, scope);
        });
        return;
    }
    
  6. Програма починається без змінних і з першого кроку:
    exec(1, {});
    

Таким чином з “бейсіка” на JavaScript програму можна переписати так:

function exec(step, scope) {
    // 1:  writeln 'Як тебе звуть?'
    //     input user
    if (step == 1) {
        CLI.writeln('Як тебе звуть?');
        CLI.on_input(function(input) {
            scope.user = input;
            exec(2, scope);
        });
        return;
    };
    // 2:  writeln 'Привіт,' + user
    //     writeln 'Давай пограємо гру "Відгадай число"?'
    if (step == 2) {
        CLI.writeln('Привіт, ' + scope.user);
        CLI.writeln('Давай пограємо гру "Відгадай число"?');
        exec(3, scope);
        return;
    };
    // 3:  writeln 'Я загадав число від 1 до 10.'
    //     number = randint(1, 10)
    //     writeln 'Спробуєш вгадати?'
    if (step == 3) {
        CLI.writeln('Я загадав число від 1 до 10.');
        scope.number = Math.floor(Math.random() * 10) + 1;
        CLI.writeln('Спробуєш вгадати?');
        exec(4, scope);
        return;
    };
    // 4:  writeln 'Вводь свій варіант:'
    //     input guess
    if (step == 4) {
        CLI.writeln('Вводь свій варіант:');
        CLI.on_input(function(input) {
            scope.guess = input;
            exec(5, scope);
        });
        return;
    };
    // 5:  if guess == number then 9
    if (step == 5) {
        if (scope.guess == scope.number) {
            exec(9, scope)
        } else {
            exec(6, scope)
        };
        return;
    };

    // 6:  if guess < number then 8
    if (step == 6) {
        if (scope.guess < scope.number) {
            exec(8, scope)
        } else {
            exec(7, scope)
        };

        return;
    };

    // 7:  writeln 'Ні, моє число менше, пробуй ще' 
    //     goto 4
    if (step == 7) {
        CLI.writeln('Ні, моє число менше, пробуй ще');
        exec(4, scope)
        return;
    };

    // 8:  writeln 'Ні, моє число більше, пробуй ще'
    //     goto 4
    if (step == 8) {
        CLI.writeln('Ні, моє число більше, пробуй ще');
        exec(4, scope)
        return;
    };

    // 9:  writeln 'Ого, так швидко вгадав. Грати ще раз?')
    //     input answer
    if (step == 9) {
        CLI.writeln('Ого, так швидко вгадав. Грати ще раз?');
        CLI.on_input(function(input) {
            scope.answer = input;
            exec(10, scope);
        });
        return;
    };

    // 10: if anwser == 'так' then 3
    if (step == 10) {
        if (scope.answer == 'так') {
            exec(3, scope)
        } else {
            exec(11, scope)
        };

        return;
    };

    // 11: if answer == 'ні' then 13
    if (step == 11) {
        if (scope.answer == 'ні') {
            exec(13, scope)
        } else {
            exec(12, scope)
        };
        return;
    };

    // 12: writeln 'Ну то так чи ні?'
    //     input answer
    //     goto 10
    if (step == 12) {
        CLI.writeln('Ну то так чи ні?');
        CLI.on_input(function(input) {
            scope.answer = input;
            exec(10, scope);
        });
        return;
    };

    // 13: writeln 'Ну тоді бувай!'
    if (step == 13) {
        CLI.writeln('Ну тоді бувай!');
        CLI.on_input(function() {}); 
        return;
    };
}

exec(1, {});

Можна звісно коротше, якщо робити умовний оператор без переходів в інший блок:

function exec(step, scope) { 
    console.log(step, scope);
    if (step == 1) {
        CLI.writeln('Як тебе звуть?');
        CLI.on_input(function(input) {
            scope.user = input;
            exec(2, scope);
        });
        return;
    }
    if (step == 2) {
        CLI.writeln('Привіт,' + scope.user);
        CLI.writeln('Давай пограємо гру відгадай число?');
        exec(3, scope);
        return;
    }
    if (step == 3) {
        CLI.writeln('Я загадав число від 1 до 10.');
        scope.number = Math.floor(Math.random() * 10) + 1;
        CLI.writeln('Спробуєш вгадати?');
        exec(4, scope);
        return;
    }
    if (step == 4) {
        CLI.writeln('Вводь свій варіант:');
        CLI.on_input(function(input) {
            scope.guess = input;
            exec(5, scope);
        });
        return;
    }
    if (step == 5) {
        if (scope.guess == scope.number) {
            CLI.writeln('Ого, так швидко вгадав. Грати ще раз?')
            CLI.on_input(function(input) {
                scope.answer = input;
                exec(6, scope);
            });
            return;
        }
        CLI.writeln('Ні, моє число ' +
            (scope.guess < scope.number ? 'більше': 'менше')
            + ', пробуй ще');
        exec(4, scope);
        return;
    }
    if (step == 6) {
        if (scope.answer == 'так') {
            exec(3, scope);
            return;
        }
        if (scope.answer == 'ні') {
            CLI.writeln('Ну тоді бувай!');
            CLI.on_input(function(){});
            return;
        }
        CLI.writeln('Ну то так чи ні?');
        CLI.on_input(function(input) {
            scope.answer = input;
            exec(6, scope);
        });
        return;
    }

};

exec(1, {});

Які з цього висновки? Ми можемо уникнути зайвих вкладень якщо нам треба зробити ланцюзок колбеків – запит, обробка відповіді, новий запит що залежить від результатів, обробка відповіді і т.д. Головне, аби це не був ланцюжок з тисяч колбеків, бо нарвемось на переповнення стеку. Тоді доведеться писати власну оптимізацію хвостової рекурсії.


Filed under: Кодерство Tagged: книжки, JavaScript, розробка, Python

Встановлення Python 3.5 з джерельного коду, встановлення Django

Ок, продовжу спроби підготуватись до DjangoGirls так, щоб там ми вчили найпередовіші технології. :)

Такі експерименти краще робити у захищеному середовищі, тому бажано щоб у вас були VirtualBox та Vagrant:

sudo apt-get install virtualbox vagrant

Поки вони ставляться, раджу коротко ознайомитись як користуватись тим Vagrant-ом.

В директорії з кодом створюємо такий файл:

# -*- mode: ruby -*-
# vi: set ft=ruby :

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "ubuntu/trusty64"
  config.vm.network "forwarded_port", guest: 8000, host: 8000
end

Це поки що він такий. Пізніше поміняю, і вся інсталяція має більш автоматизуватись. Тепер команда vagrant up дозволяє підняти чисте середовище. vagrant ssh – зайти в його термінал.

Ми хочемо Python 3.5, а його інакше як з сорсів не отримаєш, тому качаємо з сайту: https://www.python.org/downloads/

sudo apt-get update
sudo apt-get install build-essential libsqlite3-dev sqlite3 bzip2 libbz2-dev
sudo apt-get install libreadline-dev libncurses5-dev tk-dev libssl-dev
wget -c https://www.python.org/ftp/python/3.5.0/Python-3.5.0.tar.xz
tar xJf Python-3.5.0.tar.xz
cd Python-3.5.0
./configure
make
sudo make install

Тепер нарешті можна створювати віртуальне середовище (всередині віртуальної машини, ага), і ставити Django:

mkdir djangogirls
cd djangogirls
python3.5 -m venv myenv
source myenv/bin/activate
pip install django

Та-дааммм!!!

Successfully installed django-1.9

Не знав що вже є Django 1.9. Хоча, звісно що не знав, його вчора випустили. Кажуть там змінили дизайн адмінки. Давайте швиденько подивимось:

django-admin startproject mysite .
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver
Адмінка як адмінка.

Адмінка як адмінка.


Filed under: Інструменти, Кодерство Tagged: linux, Python

EBNF Parser Kata

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

На моєму домашньому модемі відвалився DSL, то я вчора ввечері не мав чим зайнятись. (Хоча звісно є купа важливіших речей) Зате біля мене була роздруківка книжечки Ніклауса Вірта “Compiler Construction“, то я взявся перекладати його парсер EBNF що ілюструє метод рекурсивного спуску з Oberon на Python. Вийшло ніби незле:

# ebnf.py
import re

class Symbol(object):
    instances = {}
    def __init__(self, name, pattern):
        self.pattern = pattern
        self.regexp = re.compile(pattern)
        self.instances[name] = self

    def match(self, text):
        text = text.lstrip()
        m = self.regexp.match(text)
        if m:
            return m.group(), text[len(m.group()):]

    @classmethod
    def get_next(cls, text):
        if not text.strip():
            return 'empty', '', ''
        for name, sym in cls.instances.items():
            m = sym.match(text)
            if m:
                return name, m[0], m[1]
        raise UnknownSymbolError('Unknown symbol: %r' % text[:50] + (
            '...' if len(text) > 50 else ''
        ))

Symbol('ident', 'w+')
Symbol('literal', '"[^"]*"')
Symbol('eql', '=')
Symbol('lparen', '(')
Symbol('rparen', ')')
Symbol('lbrak', '[')
Symbol('rbrak', ']')
Symbol('lbrace', '{')
Symbol('rbrace', '}')
Symbol('bar', '|')
Symbol('period', '.')

EBNF = '''
syntax = {production}.
production = identifier "=" expression ".".
expression = term {"|" term}.
term = factor {factor}.
factor = identifier | string | "(" expression ")" | "[" expression "]" | "{" expression "}".
'''

def parse_ebnf(text):
    sym, sym_text, remaining = '', '', text
    
    def next():
        nonlocal sym, sym_text, remaining
        sym, sym_text, remaining = Symbol.get_next(remaining)

    next()
    
    def syntax():
        res = ['syntax']
        while sym == 'ident':
            res.append(production())
        return res

    def production():
        name = sym_text
        next()
        if sym == 'eql':
            next()
        else:
            raise ExpectedEqualityError
        exp = expression()
        if sym == 'period':
            next()
        else:
            raise NoPeriodError
        return ['production', name, exp]

    def expression():
        res = ['expression', term()]
        while sym == 'bar':
            next()
            res.append(term())
        return res

    def term():
        res = ['term']
        while sym in ('ident', 'literal', 'lparen', 'lbrak', 'lbrace'):
            res.append(factor())
        if len(res) == 1:
            raise NoFactorError
        return res

    def factor():
        if sym == 'ident':
            res = [sym, sym_text]
            next()
            return res
        elif sym == 'literal':
            res = [sym, sym_text]
            next()
            return res
        elif sym == 'lparen':
            next()
            exp = expression()
            if sym == 'rparen':
                next()
            else:
                raise NoRParenError
            return ['(', exp]
        elif sym == 'lbrak':
            next()
            exp = expression()
            if sym == 'rbrak':
                next()
            else:
                raise NoRBrakError
            return ['[', exp]
        elif sym == 'lbrace':
            next()
            exp = expression()
            if sym == 'rbrace':
                next()
            else:
                raise NoRBraceError
            return ['{', exp]
        else:
            raise RuntimeError(
                "term should be called only when "
                "sym in ('ident', 'literal', 'lparen', 'lbrak', 'lbrace')"
            )

    return syntax()

class UnknownSymbolError(SyntaxError):
    pass

class NoPeriodError(SyntaxError):
    pass

class NoRParenError(SyntaxError):
    pass

class NoRBrakError(SyntaxError):
    pass

class NoRBraceError(SyntaxError):
    pass

class NoFactorError(SyntaxError):
    pass

class ExpectedEqualityError(SyntaxError):
    pass

Трохи TDD, трохи дописування тестів після:

# test.py
from unittest import TestCase, main, skip

from ebnf import Symbol
from ebnf import parse_ebnf, EBNF
from ebnf import print_tree
from ebnf import (
    NoPeriodError, NoRParenError, UnknownSymbolError,
    NoRBrakError, NoRBraceError, NoFactorError
)

class TestSymbol(TestCase):
    def test_paren(self):
        self.assertEqual(
            Symbol.get_next('(asdf)'),
            ('lparen', '(', 'asdf)')
        )
    def test_ident(self):
        self.assertEqual(
            Symbol.get_next('asdf'),
            ('ident', 'asdf', '')
        )
    def test_bar(self):
        self.assertEqual(
            Symbol.get_next('|asdf'),
            ('bar', '|', 'asdf')
        )

class TestEBNF(TestCase):
    def test_self(self):
        tree = parse_ebnf(EBNF)
        self.assertEqual(
            parse_ebnf(EBNF),
            ['syntax',
             ['production',
              'syntax',
              ['expression',
               ['term', ['{', ['expression', ['term', ['ident', 'production']]]]]]],
             ['production',
              'production',
              ['expression',
               ['term',
                ['ident', 'identifier'],
                ['literal', '"="'],
                ['ident', 'expression'],
                ['literal', '"."']]]],
             ['production',
              'expression',
              ['expression',
               ['term',
                ['ident', 'term'],
                ['{', ['expression', ['term', ['literal', '"|"'], ['ident', 'term']]]]]]],
             ['production',
              'term',
              ['expression',
               ['term',
                ['ident', 'factor'],
                ['{', ['expression', ['term', ['ident', 'factor']]]]]]],
             ['production',
              'factor',
              ['expression',
               ['term', ['ident', 'identifier']],
               ['term', ['ident', 'string']],
               ['term', ['literal', '"("'], ['ident', 'expression'], ['literal', '")"']],
               ['term', ['literal', '"["'], ['ident', 'expression'], ['literal', '"]"']],
               ['term', ['literal', '"{"'], ['ident', 'expression'], ['literal', '"}"']]]]]
        )

    def test_without_period(self):
        with self.assertRaises(NoPeriodError):
            parse_ebnf('symbol = "asdf"')

    def test_without_rparen(self):
        with self.assertRaises(NoRParenError):
            parse_ebnf('symbol = ("asdf"')

    def test_without_rbrak(self):
        with self.assertRaises(NoRBrakError):
            parse_ebnf('symbol = ["asdf"')

    def test_without_rbrace(self):
        with self.assertRaises(NoRBraceError):
            parse_ebnf('symbol = {literal')

    def test_unknown_symbol(self):
        with self.assertRaises(UnknownSymbolError):
            parse_ebnf('!symbol = "asdf"')

    def test_no_factor(self):
        with self.assertRaises(NoFactorError):
            parse_ebnf('sym = ||')

    def test_for_binary(self):
        self.assertEqual(
            parse_ebnf('''
                digit = "0"|"1".
                number = digit {digit}.
            '''),
            ['syntax',
             ['production',
              'digit',
              ['expression', ['term', ['literal', '"0"']], ['term', ['literal', '"1"']]]],
             ['production',
              'number',
              ['expression',
               ['term',
                ['ident', 'digit'],
                ['{', ['expression', ['term', ['ident', 'digit']]]]]]]]
        )


if __name__ == '__main__':
    main()

І я зрозумів дві речі: що найбільше на світі (якщо не враховувати сну) люблю писати код, і що таке множина first_1(k). Якщо коротко, то це множина символів з яких може починатись рядок що виводиться з нетерміналу k. Наприклад:

digit = "0" | "1"
number = digit {digit}

first(number) = {"0", "1"}

І ми знаємо що парсер може прочитати не будь-яку граматику, а лише ту, в якої для кожного виразу на зразок

definition = exp1 | exp2

Виконується умова:

first_1(exp1) \cap first_1(exp2) = \varnothing

Таким чином отака граматика багатозначна:

sum = number | sum ("+" | "-") sum

Бо вираз 1 – 10 + 11 можна розпарсити як (1 – 10) + 11 або як 1 – (10 + 11). А все тому, що first(number) = first(sum).

Її можна переписати так щоб в правилі для суми не було двох варіантів що починаються з однакових символів.

sum = number {("+" | "-") number}

Так вся сума розгортатиметься зразу і не буде проблем з тим яку операцію виконувати швидше.

Тепер би ще змусити його автоматично генерувати код парсера за EBNF і вийде власний YACC. :)


Filed under: Кодерство Tagged: Python

EBNF Parser Kata

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

На моєму домашньому модемі відвалився DSL, то я вчора ввечері не мав чим зайнятись. (Хоча звісно є купа важливіших речей) Зате біля мене була роздруківка книжечки Ніклауса Вірта “Compiler Construction“, то я взявся перекладати його парсер EBNF що ілюструє метод рекурсивного спуску з Oberon на Python. Вийшло ніби незле:

# ebnf.py
import re

class Symbol(object):
    instances = {}
    def __init__(self, name, pattern):
        self.pattern = pattern
        self.regexp = re.compile(pattern)
        self.instances[name] = self

    def match(self, text):
        text = text.lstrip()
        m = self.regexp.match(text)
        if m:
            return m.group(), text[len(m.group()):]

    @classmethod
    def get_next(cls, text):
        if not text.strip():
            return 'empty', '', ''
        for name, sym in cls.instances.items():
            m = sym.match(text)
            if m:
                return name, m[0], m[1]
        raise UnknownSymbolError('Unknown symbol: %r' % text[:50] + (
            '...' if len(text) > 50 else ''
        ))

Symbol('ident', 'w+')
Symbol('literal', '"[^"]*"')
Symbol('eql', '=')
Symbol('lparen', '(')
Symbol('rparen', ')')
Symbol('lbrak', '[')
Symbol('rbrak', ']')
Symbol('lbrace', '{')
Symbol('rbrace', '}')
Symbol('bar', '|')
Symbol('period', '.')

EBNF = '''
syntax = {production}.
production = identifier "=" expression ".".
expression = term {"|" term}.
term = factor {factor}.
factor = identifier | string | "(" expression ")" | "[" expression "]" | "{" expression "}".
'''

def parse_ebnf(text):
    sym, sym_text, remaining = '', '', text
    
    def next():
        nonlocal sym, sym_text, remaining
        sym, sym_text, remaining = Symbol.get_next(remaining)

    next()
    
    def syntax():
        res = ['syntax']
        while sym == 'ident':
            res.append(production())
        return res

    def production():
        name = sym_text
        next()
        if sym == 'eql':
            next()
        else:
            raise ExpectedEqualityError
        exp = expression()
        if sym == 'period':
            next()
        else:
            raise NoPeriodError
        return ['production', name, exp]

    def expression():
        res = ['expression', term()]
        while sym == 'bar':
            next()
            res.append(term())
        return res

    def term():
        res = ['term']
        while sym in ('ident', 'literal', 'lparen', 'lbrak', 'lbrace'):
            res.append(factor())
        if len(res) == 1:
            raise NoFactorError
        return res

    def factor():
        if sym == 'ident':
            res = [sym, sym_text]
            next()
            return res
        elif sym == 'literal':
            res = [sym, sym_text]
            next()
            return res
        elif sym == 'lparen':
            next()
            exp = expression()
            if sym == 'rparen':
                next()
            else:
                raise NoRParenError
            return ['(', exp]
        elif sym == 'lbrak':
            next()
            exp = expression()
            if sym == 'rbrak':
                next()
            else:
                raise NoRBrakError
            return ['[', exp]
        elif sym == 'lbrace':
            next()
            exp = expression()
            if sym == 'rbrace':
                next()
            else:
                raise NoRBraceError
            return ['{', exp]
        else:
            raise RuntimeError(
                "term should be called only when "
                "sym in ('ident', 'literal', 'lparen', 'lbrak', 'lbrace')"
            )

    return syntax()

class UnknownSymbolError(SyntaxError):
    pass

class NoPeriodError(SyntaxError):
    pass

class NoRParenError(SyntaxError):
    pass

class NoRBrakError(SyntaxError):
    pass

class NoRBraceError(SyntaxError):
    pass

class NoFactorError(SyntaxError):
    pass

class ExpectedEqualityError(SyntaxError):
    pass

Трохи TDD, трохи дописування тестів після:

# test.py
from unittest import TestCase, main, skip

from ebnf import Symbol
from ebnf import parse_ebnf, EBNF
from ebnf import print_tree
from ebnf import (
    NoPeriodError, NoRParenError, UnknownSymbolError,
    NoRBrakError, NoRBraceError, NoFactorError
)

class TestSymbol(TestCase):
    def test_paren(self):
        self.assertEqual(
            Symbol.get_next('(asdf)'),
            ('lparen', '(', 'asdf)')
        )
    def test_ident(self):
        self.assertEqual(
            Symbol.get_next('asdf'),
            ('ident', 'asdf', '')
        )
    def test_bar(self):
        self.assertEqual(
            Symbol.get_next('|asdf'),
            ('bar', '|', 'asdf')
        )

class TestEBNF(TestCase):
    def test_self(self):
        tree = parse_ebnf(EBNF)
        self.assertEqual(
            parse_ebnf(EBNF),
            ['syntax',
             ['production',
              'syntax',
              ['expression',
               ['term', ['{', ['expression', ['term', ['ident', 'production']]]]]]],
             ['production',
              'production',
              ['expression',
               ['term',
                ['ident', 'identifier'],
                ['literal', '"="'],
                ['ident', 'expression'],
                ['literal', '"."']]]],
             ['production',
              'expression',
              ['expression',
               ['term',
                ['ident', 'term'],
                ['{', ['expression', ['term', ['literal', '"|"'], ['ident', 'term']]]]]]],
             ['production',
              'term',
              ['expression',
               ['term',
                ['ident', 'factor'],
                ['{', ['expression', ['term', ['ident', 'factor']]]]]]],
             ['production',
              'factor',
              ['expression',
               ['term', ['ident', 'identifier']],
               ['term', ['ident', 'string']],
               ['term', ['literal', '"("'], ['ident', 'expression'], ['literal', '")"']],
               ['term', ['literal', '"["'], ['ident', 'expression'], ['literal', '"]"']],
               ['term', ['literal', '"{"'], ['ident', 'expression'], ['literal', '"}"']]]]]
        )

    def test_without_period(self):
        with self.assertRaises(NoPeriodError):
            parse_ebnf('symbol = "asdf"')

    def test_without_rparen(self):
        with self.assertRaises(NoRParenError):
            parse_ebnf('symbol = ("asdf"')

    def test_without_rbrak(self):
        with self.assertRaises(NoRBrakError):
            parse_ebnf('symbol = ["asdf"')

    def test_without_rbrace(self):
        with self.assertRaises(NoRBraceError):
            parse_ebnf('symbol = {literal')

    def test_unknown_symbol(self):
        with self.assertRaises(UnknownSymbolError):
            parse_ebnf('!symbol = "asdf"')

    def test_no_factor(self):
        with self.assertRaises(NoFactorError):
            parse_ebnf('sym = ||')

    def test_for_binary(self):
        self.assertEqual(
            parse_ebnf('''
                digit = "0"|"1".
                number = digit {digit}.
            '''),
            ['syntax',
             ['production',
              'digit',
              ['expression', ['term', ['literal', '"0"']], ['term', ['literal', '"1"']]]],
             ['production',
              'number',
              ['expression',
               ['term',
                ['ident', 'digit'],
                ['{', ['expression', ['term', ['ident', 'digit']]]]]]]]
        )


if __name__ == '__main__':
    main()

І я зрозумів дві речі: що найбільше на світі (якщо не враховувати сну) люблю писати код, і що таке множина first_1(k). Якщо коротко, то це множина символів з яких може починатись рядок що виводиться з нетерміналу k. Наприклад:

digit = "0" | "1"
number = digit {digit}

first(number) = {"0", "1"}

І ми знаємо що парсер може прочитати не будь-яку граматику, а лише ту, в якої для кожного виразу на зразок

definition = exp1 | exp2

Виконується умова:

first_1(exp1) \cap first_1(exp2) = \varnothing

Таким чином отака граматика багатозначна:

sum = number | sum ("+" | "-") sum

Бо вираз 1 – 10 + 11 можна розпарсити як (1 – 10) + 11 або як 1 – (10 + 11). А все тому, що first(number) = first(sum).

Її можна переписати так щоб в правилі для суми не було двох варіантів що починаються з однакових символів.

sum = number {("+" | "-") number}

Так вся сума розгортатиметься зразу і не буде проблем з тим яку операцію виконувати швидше.

Тепер би ще змусити його автоматично генерувати код парсера за EBNF і вийде власний YACC. :)


Filed under: Кодерство Tagged: Python

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

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

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

Глюкоза

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

Користуючись нею, ми можемо перетворити формулу SMILES, на список координат атомів:

import pybel

smile = raw_input('Enter SMILE molecule:')
molecule = pybel.readstring('smi', smile)
molecule.make3D()

for atom in molecule.atoms:
    print atom.type, ' '.join(map(str, atom.coords))

SMILES можна знайти в статтях вікіпедії про різні речовини. Ось наприклад глюкоза: OC[C@H]1OC(O)[C@H](O)[C@@H](O)[C@@H]1O. І її координати:

O3 3.08232699168 1.41136753836 1.97383867659
C3 2.63605783234 0.116724346362 2.37092125466
C3 2.87897272901 -0.854338624684 1.21070216538
H 2.47296741411 -0.36351143938 0.319191621385
O3 4.29331227545 -1.03408976253 1.03052197614
C3 2.18198168708 -2.2062426467 1.4123437219
H 2.52686439483 -2.70828022178 2.32628341407
O3 0.757046712076 -2.07375697965 1.48816465135
C3 2.4834103428 -3.09613160782 0.198042289604
H 2.03158506981 -2.66605342385 -0.704362586236
O3 1.84366255476 -4.36331585678 0.373985237387
C3 3.99532722341 -3.24522583591 0.00523555683618
H 4.41250004827 -3.77444425583 0.871020029013
O3 4.29626886035 -4.03252960027 -1.15195954921
C3 4.62954125722 -1.84273135567 -0.0947112115177
H 5.71887138656 -1.95252927548 -0.108137148969
O3 4.3079363412 -1.18316983652 -1.31789119536
HO 2.91473185309 2.02688340774 2.71060639162
H 1.56956736943 0.19992674022 2.5985040442
H 3.17829013287 -0.18365904297 3.27321534863
HO 0.532102901531 -1.64213847492 2.33074495499
HO 0.907383263524 -4.15051515566 0.55676359202
HO 3.89297302828 -3.59188323274 -1.91963488602
HO 3.3506978517 -1.05986229308 -1.37211748251

Ок, залишилось написати програму що бере оці координати і створює таку картинку як у цій публікації (увага, ввесь код звідси і аж до кінця публікації – це одна програма):

#! /usr/bin/python3
from random import random

import pyglet
from pyglet.window import key, Window
from pyglet.gl import *
from pyglet.gl.glu import *

window = Window()

Об’єкт наступного класу просто буде повертати кортеж з кольором для кожної назви атома. Деякі атоми ми задамо вручну, щодо правильної палітри – дивіться статтю CPK coloring.

class Palette(object):
    def __init__(self):
        self.colors = {
            'H': (0.0, 0.5, 0.5),
            'HO': (1.0, 0.5, 0.5),
            'C3': (0.1, 0.1, 0.1),
            'Car': (0.1, 0.1, 0.1),
            'O3': (1.0, 0.0, 0.0),
        }

    def get_color(self, name):
        if name not in self.colors:
            print(name)
            self.colors[name] = (random(), random(), random())
        return self.colors[name]

palette = Palette()

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

class Molecule(object):
    def __init__(self, fn):
        self.atoms = []
        with open(fn) as f:
            for l in f:
                el, x, y, z = l.split()
                self.atoms.append(
                    (el, float(x), float(y), float(z))
                )

    def draw(self):
        for atom in self.atoms: # для кожного атома
            glPushMatrix() # зберегти матрицю моделі
            glTranslatef(*atom[1:]) # змістити матрицю моделі в координати атома
            # намалювати сферу радіусу 1 і кольору відповідного типу атома
            draw_sphere(1, palette.get_color(atom[0]))
            glPopMatrix() # завантажити збережену матрицю моделі

molecule = Molecule('glucose.dat') # створити молекулу глюкози

def draw_sphere(radius, color):
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
    
    # довго пояснювати що таке колір матеріалу, я й сам не до кінця знаю.
    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, (GLfloat * 3)(*color))
    glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION,
        (GLfloat * 3)(*map(lambda x: x/2, color))
    )
    # glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, (GLfloat * 3)(*color))

    sphere = gluNewQuadric()
    gluSphere(sphere, radius, 50, 50) # 50, 50 - це кількість меридіанів та паралелей. 
    # якщо потрібно багато атомів - зменшіть їх кількість для збільшення продуктивності.

Тепер займемось власне перемальовуванням екрану:

@window.event
def on_draw():
    update_frame(0)

rotation = 0 # Глобальна зміна з поточним поворотом моделі
def update_frame(dt):
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity() # завантажити матрицю ідентичності 

    global rotation
    rotation += dt * 10 # чим більше часу пройшло - тим більше повертаємо
    glRotatef(rotation, 0, 1, 0) # навколо осі y
    molecule.draw() # і малюємо нашу молекулу.

При зміні розмірів вікна (і при його створенні) ініціалізуємо всілякі налаштування OpenGL:

@window.event
def on_resize(width, height):
    glClearColor(0.0, 0.3, 0.0, 0.0) # задаємо колір фону

    glEnable(GL_DEPTH_TEST) # вмикаємо буфер глибини

    glEnable(GL_LIGHTING)
    glEnable(GL_LIGHT0)
    glLightf(GL_LIGHT0, GL_POSITION, 1, 5, 4) # ставимо одне світло

    glViewport(0, 0, width, height)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(45, width / height, .1, 1000) # перспективна проекція з кутом 45
    gluLookAt( # ставимо камеру і націлюємо її в цент сцени
     1, 4, 15, # eye
     0, 0, 0, # target
     0, 1, 0  # up
    )
    glMatrixMode(GL_MODELVIEW) 
    return pyglet.event.EVENT_HANDLED

При натисканні клавіш “вліво” і “вправо” оновлюємо кадр, повернувши трішки модель. А також оновлюємо 50 разів на секунду. І запускаємо цикл подій:

@window.event
def on_key_press(symbol, modifiers):
    if symbol == key.LEFT:
        update_frame(-1)
    elif symbol == key.RIGHT:
        update_frame(1)

pyglet.clock.schedule_interval(update_frame, 0.02)

pyglet.app.run()

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

Ах, і стаття з якої взято інформацію про те як отримати координати для атомів молекули: Patrick Fuller – Molecules in Blender


Filed under: Графіка, Кодерство Tagged: освіта, OpenGL, Python

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

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

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

Глюкоза

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

Користуючись нею, ми можемо перетворити формулу SMILES, на список координат атомів:

import pybel

smile = raw_input('Enter SMILE molecule:')
molecule = pybel.readstring('smi', smile)
molecule.make3D()

for atom in molecule.atoms:
    print atom.type, ' '.join(map(str, atom.coords))

SMILES можна знайти в статтях вікіпедії про різні речовини. Ось наприклад глюкоза: OC[C@H]1OC(O)[C@H](O)[C@@H](O)[C@@H]1O. І її координати:

O3 3.08232699168 1.41136753836 1.97383867659
C3 2.63605783234 0.116724346362 2.37092125466
C3 2.87897272901 -0.854338624684 1.21070216538
H 2.47296741411 -0.36351143938 0.319191621385
O3 4.29331227545 -1.03408976253 1.03052197614
C3 2.18198168708 -2.2062426467 1.4123437219
H 2.52686439483 -2.70828022178 2.32628341407
O3 0.757046712076 -2.07375697965 1.48816465135
C3 2.4834103428 -3.09613160782 0.198042289604
H 2.03158506981 -2.66605342385 -0.704362586236
O3 1.84366255476 -4.36331585678 0.373985237387
C3 3.99532722341 -3.24522583591 0.00523555683618
H 4.41250004827 -3.77444425583 0.871020029013
O3 4.29626886035 -4.03252960027 -1.15195954921
C3 4.62954125722 -1.84273135567 -0.0947112115177
H 5.71887138656 -1.95252927548 -0.108137148969
O3 4.3079363412 -1.18316983652 -1.31789119536
HO 2.91473185309 2.02688340774 2.71060639162
H 1.56956736943 0.19992674022 2.5985040442
H 3.17829013287 -0.18365904297 3.27321534863
HO 0.532102901531 -1.64213847492 2.33074495499
HO 0.907383263524 -4.15051515566 0.55676359202
HO 3.89297302828 -3.59188323274 -1.91963488602
HO 3.3506978517 -1.05986229308 -1.37211748251

Ок, залишилось написати програму що бере оці координати і створює таку картинку як у цій публікації (увага, ввесь код звідси і аж до кінця публікації – це одна програма):

#! /usr/bin/python3
from random import random

import pyglet
from pyglet.window import key, Window
from pyglet.gl import *
from pyglet.gl.glu import *

window = Window()

Об’єкт наступного класу просто буде повертати кортеж з кольором для кожної назви атома. Деякі атоми ми задамо вручну, щодо правильної палітри – дивіться статтю CPK coloring.

class Palette(object):
    def __init__(self):
        self.colors = {
            'H': (0.0, 0.5, 0.5),
            'HO': (1.0, 0.5, 0.5),
            'C3': (0.1, 0.1, 0.1),
            'Car': (0.1, 0.1, 0.1),
            'O3': (1.0, 0.0, 0.0),
        }

    def get_color(self, name):
        if name not in self.colors:
            print(name)
            self.colors[name] = (random(), random(), random())
        return self.colors[name]

palette = Palette()

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

class Molecule(object):
    def __init__(self, fn):
        self.atoms = []
        with open(fn) as f:
            for l in f:
                el, x, y, z = l.split()
                self.atoms.append(
                    (el, float(x), float(y), float(z))
                )

    def draw(self):
        for atom in self.atoms: # для кожного атома
            glPushMatrix() # зберегти матрицю моделі
            glTranslatef(*atom[1:]) # змістити матрицю моделі в координати атома
            # намалювати сферу радіусу 1 і кольору відповідного типу атома
            draw_sphere(1, palette.get_color(atom[0]))
            glPopMatrix() # завантажити збережену матрицю моделі

molecule = Molecule('glucose.dat') # створити молекулу глюкози

def draw_sphere(radius, color):
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
    
    # довго пояснювати що таке колір матеріалу, я й сам не до кінця знаю.
    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, (GLfloat * 3)(*color))
    glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION,
        (GLfloat * 3)(*map(lambda x: x/2, color))
    )
    # glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, (GLfloat * 3)(*color))

    sphere = gluNewQuadric()
    gluSphere(sphere, radius, 50, 50) # 50, 50 - це кількість меридіанів та паралелей. 
    # якщо потрібно багато атомів - зменшіть їх кількість для збільшення продуктивності.

Тепер займемось власне перемальовуванням екрану:

@window.event
def on_draw():
    update_frame(0)

rotation = 0 # Глобальна зміна з поточним поворотом моделі
def update_frame(dt):
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity() # завантажити матрицю ідентичності 

    global rotation
    rotation += dt * 10 # чим більше часу пройшло - тим більше повертаємо
    glRotatef(rotation, 0, 1, 0) # навколо осі y
    molecule.draw() # і малюємо нашу молекулу.

При зміні розмірів вікна (і при його створенні) ініціалізуємо всілякі налаштування OpenGL:

@window.event
def on_resize(width, height):
    glClearColor(0.0, 0.3, 0.0, 0.0) # задаємо колір фону

    glEnable(GL_DEPTH_TEST) # вмикаємо буфер глибини

    glEnable(GL_LIGHTING)
    glEnable(GL_LIGHT0)
    glLightf(GL_LIGHT0, GL_POSITION, 1, 5, 4) # ставимо одне світло

    glViewport(0, 0, width, height)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(45, width / height, .1, 1000) # перспективна проекція з кутом 45
    gluLookAt( # ставимо камеру і націлюємо її в цент сцени
     1, 4, 15, # eye
     0, 0, 0, # target
     0, 1, 0  # up
    )
    glMatrixMode(GL_MODELVIEW) 
    return pyglet.event.EVENT_HANDLED

При натисканні клавіш “вліво” і “вправо” оновлюємо кадр, повернувши трішки модель. А також оновлюємо 50 разів на секунду. І запускаємо цикл подій:

@window.event
def on_key_press(symbol, modifiers):
    if symbol == key.LEFT:
        update_frame(-1)
    elif symbol == key.RIGHT:
        update_frame(1)

pyglet.clock.schedule_interval(update_frame, 0.02)

pyglet.app.run()

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

Ах, і стаття з якої взято інформацію про те як отримати координати для атомів молекули: Patrick Fuller – Molecules in Blender


Filed under: Графіка, Кодерство Tagged: освіта, OpenGL, Python

OpenGL в Python

Мене якось запитали про це, але без підготовки пояснити було важко, крім того мета була амбітна – намалювати молекулу, тому вийшло не так добре як би хотілось. Спробую написати короткий покроковий вступ в цю тему, який приблизно слідує послідовності в старих уроках 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 Або взагалі сама з допомогою інтернету звикне вчитись.


Filed under: Графіка, Кодерство Tagged: OpenGL, Python

OpenGL в Python

Мене якось запитали про це, але без підготовки пояснити було важко, крім того мета була амбітна – намалювати молекулу, тому вийшло не так добре як би хотілось. Спробую написати короткий покроковий вступ в цю тему, який приблизно слідує послідовності в старих уроках 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 Або взагалі сама з допомогою інтернету звикне вчитись.


Filed under: Графіка, Кодерство Tagged: OpenGL, Python

Анонс Lvivpy4

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

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

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

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

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

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

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


Filed under: Кодерство, Нещоденник Tagged: Python