常用 debug 工具筆記


logger

import logging

logging.basicConfig(filename="") 
logging.error('...')

elapsed_time

import time (單位:sec)

start = time.time()
elapsed_time = time.time() - start 

timestamp

import time

time.strftime("%Y-%m-%d %H:%M:%s", time.localtime(time.time()))

wait

import time

time.sleep(<second>)

send GET request

import requests

requests.get(<url>)

JavaScript

  • contain
    1. logger
    2. timestamp
    3. elapsed_time
    4. mailer
    5. facebook login
/* veck's module for web application */

var log = require('npmlog')
    , fs = require('fs')
    , mailer = require('nodemailer');

log.__proto__.setLogFile = function(path){
    log.stream = fs.createWriteStream(path, {flags: 'a'});  
}

/* timestamp */
log.__proto__.getLogTime = function(){
    var date = new Date();
    var day = date.getFullYear() +'/'+ date.getDate() +'/'+ date.getMonth();
    var time = date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds() + '.' + date.getMilliseconds();
    return day + ' ' + time;
}
log.__proto__.getLogDate = function(){
    var date = new Date();
    return date.getFullYear() +'/'+ date.getDate() +'/'+ date.getMonth();   
}

module.exports = log;

/*******************************************************/

/* elapsed time */
// Date.now() // return milliseconds (10^-3)
start = Date.now()
elasped = (Date.now - start)/1000

/*******************************************************/

/* mailer */
var transporter = mailer.createTransport({
    service: 'ses',
    host: 'email-smtp.us-west-2.amazonaws.com',
    auth: {
      user: '<username>',
        pass: '<password>'
    }
});
var message = '恭喜您已成功註,請點選以下連結完成註冊認證!';
transporter.sendMail({
    from: 'fbukevin@gmail.com',
  to: confirm.mail.address,
    subject: '註冊認證信',
    text: message
});

/*******************************************************/

/* facebook login */

/*
    TODO: 包成一個 module
 */    

var express = require('express');
var router = express.Router();
var log = require('log_tool');

log.setLogFile('log/auth.js.log');
log.info(log.getLogTime(), 'router index.js logging successfully');
function getRequestIP(req){
  return req.header('x-forwarded-for') || req.connection.remoteAddress;
}

function logging(req){
  log.info(log.getLogTime(), 'Request IP: ' + getRequestIP(req));
}

var passport = require('passport')
  , util = require('util')
  , FacebookStrategy = require('passport-facebook').Strategy
  , logger = require('morgan')
  , session = require('express-session')
  , bodyParser = require("body-parser")
  , cookieParser = require("cookie-parser")
  , methodOverride = require('method-override');

var FACEBOOK_APP_ID = "<FACEBOOK_APP_ID>"
var FACEBOOK_APP_SECRET = "<FACEBOOK_APP_SECRET>";

router.use(session({ secret: 'keyboard cat' }));
router.use(passport.initialize());
router.use(passport.session());

passport.serializeUser(function(user, done) {
  done(null, user);
});

passport.deserializeUser(function(obj, done) {
  done(null, obj);
});

passport.use(new FacebookStrategy({
    clientID: FACEBOOK_APP_ID,
    clientSecret: FACEBOOK_APP_SECRET,
    callbackURL: "http://localhost:3000/auth/facebook/callback",
    profileFields: ['id', 'displayName', 'photos']
  },
  function(accessToken, refreshToken, profile, done) {   
    process.nextTick(function () {
      return done(null, profile);
    });
  }
));

[Python] Requests + Google Finance API

本文是要介紹如何使用 Pythonrequest 模組來呼叫 Google Finance API 並解析取得的股價內容。

有關 requests 模組的介紹,請參考:http://docs.python-requests.org/en/latest/user/quickstart/

首先當然就是要安裝 request 模組啦!

pip install requests

然後就可以開始寫程式了

>>> import requests
>>> r = requests.get("https://finance.google.com/finance/info", params = {"client":"ig", "q":"TPE:2330"})

這段是以 HTTP GET 方法呼叫 Google Finance 的 API,可以得到不同國家區域證券交易所提供的即時股價資訊,requests 物件的 get 方法可以給予兩個參數,第一個參數是資源的 URI,第二個參數是相關設定,項目很多種,這裡我們用來設定 GET 傳輸所夾帶的資料,有 client (通常設定 “ig") 和 q (query string 的意思,格式為 <證券交易所代碼>:<股票代碼>),本例整個發送的資源敘述會變成:

如果成功取得回應資源,我們可以用 r.text 來查看回應內容本文,用 print() 可以印的比較漂亮點(人類可讀):

>>> print(r.text)         #pretty output
// [
{
"id": "674465"
,"t" : "2330"
,"e" : "TPE"
,"l" : "138.00"
,"l_fix" : "138.00"
,"l_cur" : "NT$138.00"
,"s": "0"
,"ltt":"1:30PM GMT+8"
,"lt" : "Oct 28, 1:30PM GMT+8"
,"lt_dts" : "2015-10-28T13:30:02Z"
,"c" : "-1.00"
,"c_fix" : "-1.00"
,"cp" : "-0.72"
,"cp_fix" : "-0.72"
,"ccol" : "chr"
,"pcls_fix" : "139"
}
]

Parse Response String

假如回傳的資料內容確定是 JSON 格式內容,你可以用 r.json() 來 parse 出內容,但是注意回傳的內容,雖然長得很像 JSON 的格式,但是在整個字串最前面多了兩個 ‘/’,這導致這個物件並不是 JSON 物件,呼叫 r.json() 會出錯誤,因此我們需要先處理掉這兩個反斜線。

由於 JSON 的字串物件無法直接替換掉字串指定索引的內容,所以我採用複製子字串的方式來取得不帶有反斜線的其他內容:

>>> s = r.text[4:]         #copy from "["
>>> import json
>>> o = json.loads(s)
>>> o[0]['l']
u'138.00'
>>> import decimal
>>> d = decimal.Decimal(o[0]['l'])
Decimal('138.00')
>>> int(d)
138
>>> float(d)
138.0

補充一下,requests.get() 帶的第二個參數,用 paramsdata 是不同的項目喔,如果用 data,會得到 Bad request

[Python] Executable Python

繼 node.js 後,我也想讓 Python 的程式碼可以不需要輸入 “python" 就可以執行,找到大部分的作法,這是在 UNIX/Linux 和 Mac 上的說明

  1. 寫個簡單的 exe.py
#!/usr/bin/env python
if __name__ == "__main__":
print("Execute Python without typing \"python\")
  1. 在終端機輸入 chmod +x exe.py 修改權限
  2. 答啦!
 ~/Desktop -->./exe.py 
Execute Python without typing "python"

甚至也不用打 “./" 了!

螢幕快照 2015-01-29 上午3.08.56.png

如果想連附檔名都不要,有篇教學說可以把檔名改成 exe,也就是拿掉附檔名,而系統會自己知道要用程式碼第一行指定的那個直譯器來執行這個檔案 ~/Desktop -->exe

當然,你也可以把 exe 加入 PATH 中,這樣就可以到處都直接執行它了~

有個問題是,Python 利用 pip 來管理和發佈套件,在 node.js 那邊我們用 npm install -g 就可以安裝成 global command,不需要另外設定權限,那如果今天需要設定權限,那給人安裝後,是不是應該寫 shell script 來設定呢?

還沒研究有沒有人用 pip 來安裝成 command tool,pip 好像主要是拿來安裝 Python module package,如果用 shell script 來設定路徑好像就不是 pip 的用途了

原文參考:http://sebug.net/paper/python/ch03s05.html

Python USB Library – pyusb

Website: http://walac.github.io/pyusb/
GitHub: https://github.com/walac/pyusb/

目前教學只有教在 Ubuntu 和 Windows 上安裝的方法,我以 Ubuntu 為例,首先需要先有 python (應該是 builtin 了),然後要安裝 libusb-1.0-0

$ sudo apt-get install libusb-1.0-0

然後下載 library,解壓縮以後,進入資料夾,輸入 sudo python setup install,就完成安裝了

簡單的測試 USB 連線,你需要先知道各個 USB 裝置的 Vendor IDProduct ID,可以用 lsusb 來查看:

~/Desktop$ lsusb
Bus 002 Device 003: ID 046d:c016 Logitech, Inc. Optical Wheel Mouse
Bus 002 Device 002: ID 8087:0020 Intel Corp. Integrated Rate Matching Hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 024: ID 0b05:5481 ASUSTek Computer, Inc. 
Bus 001 Device 002: ID 8087:0020 Intel Corp. Integrated Rate Matching Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

以第一個裝置 Logitech, Inc. Optical Wheel Mouse 為例,在 ID 後面的 046d:c016 分別對應到 Vendor ID 和 Product ID,接著我們進入 interactive shell,首先要 import usb,接著我們利用 usb 模組下的 core 類別的 find 方法,把剛才的兩個 id 給它 dev = usb.core.find(idVendor=0x046d, idProduct=0xc016)

>>> import usb
>>> dev = usb.core.find(idVendor=0x046d, idProduct=0xc016)

然後我們就可以印出 dev 的資訊,看看是不是有抓到我們指定的 USB 裝置:

>>> dev
<DEVICE ID 046d:c016 on Bus 002 Address 003>
螢幕快照 2014-09-25 下午3.45.16.png

Python WebSocket [Code Read and Demo]

Source Code: https://github.com/isnowfy/python-websocket
Tutorial: http://www.isnowfy.com/python-websocket-server/

Project 中的 index.html 就扮演著 Client 的角色,用瀏覽器執行後,他會發 request 給 server,其中主要建立 WebSocket 連線的 JavaScript 片段如下:

  function openConnection() {
      conn = new WebSocket('ws://localhost:7000/');   // 建立連線
      conn.onopen = function () {
        alert("open");
        conn.send("hello");
      };

      conn.onmessage = function (event) {
        alert(event.data);
        /* TODO: 做連線建立後想做的事情 */
      };

      conn.onclose = function (event) {
      };

      conn.onerror = function(event){
        alert(event.data);
      }
    }    

  setTimeout(function () {
    openConnection();
  }, 1000);

onopen, onclose, onerror 這三個方法比較直觀,就是在連線建立、連線關閉和連線出錯時,要執行的內容
onmessage 則是在連線成功建立以後要做的事情

而 server 就是 ws.py,使用 terminal 開啟以後,使用 python ws.py,就會去聽指定的 port (作者這邊預設為 7000),在 ws.py 中的 main 裡面的 select(socket_list, [], []) 會監看 socket_list 中的 socket 變化,一旦 client 端發出 request,server 端接收到以後,select 會知道,並回傳這個 socket,此 socket 會被丟入 WebSocket.handshake 中去解析認證,接著就 response 連線資訊給 client,如此就建立起連線了:

def main(handle=process):
    port = 7000     # 定義要監聽的 port
    try:
        server.bind(('', port))     # 綁定服務
        server.listen(100)          # 設定最大監聽連線數目 (可能有多個連線同時來,排隊等著回應)
    except Exception, e:
        print e
        exit()
    socket_list.add(server)         # 將新的監聽物件加入監聽列表中
    print 'server start on port %d' % port
    while True:                     # 開始監聽
        r, w, e = select(socket_list, [], [])   # 利用 select 來監控監聽物件的變化
        for sock in r:
            if sock == server:
                conn, addr = sock.accept()      # 取出物件連線資訊,包含 socket 物件和 client 位址
                if WebSocket.handshake(conn):   # 處理連線資訊
                    socket_list.add(conn)       # 保持 socket 監聽 (即保持連線)
            else:                                # 例外處理
                data = WebSocket.recv(sock)
                if not data:
                    socket_list.remove(sock)
                else:
                    handle(sock, data)

Server 接收到 HTTP request 後,會取出 Sec-WebSocket-Key 的值,並加上一串 magic string 258EAFA5-E914-47DA-95CA-C5AB0DC85B11,接著使用 SHA-1 加密後在進行 BASE-64 編碼,最後將結果放到 response 封包中的 Sec-WebSocket-Accept,返回給 client,如此便完成連線,這就是 WebSocket 基本的連線建立原理,因此在 handshake 中會是如此:

    def handshake(conn):
        key = None 
        data = conn.recv(8192)      # 從收到 request 的 socket 中取得資料,8192 是 buffer size
        if not len(data):
            return False
        for line in data.split('\r\n\r\n')[0].split('\r\n')[1:]:
            k, v = line.split(': ')     # 取出 Sec-WebSocket-Key
            if k == 'Sec-WebSocket-Key':
                key = base64.b64encode(hashlib.sha1(v + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11').digest())
        if not key:
            conn.close()
            return False
        response = 'HTTP/1.1 101 Switching Protocols\r\n'\
                   'Upgrade: websocket\r\n'\
                   'Connection: Upgrade\r\n'\
                   'Sec-WebSocket-Accept:' + key + '\r\n\r\n'
        conn.send(response)
        return True

Class WebSocket 中的 recv 和 send,以及全域方法 sendall 和 process 是在例外處理時會被呼叫的,這部份要自己客製化你要的處理方式

補充:
“258EAFA5-E914-47DA-95CA-C5AB0DC85B11″ 這個魔術字串,在 RFC 文件中有說明,它是一個 Globally Unique Identifier,詳可見 RFC6455(WebSocket) 與 RFC4122(Universal Unique Identifier)

[Python] MapReduce on Hadoop – mrjob

用 Python 來寫 Hadoop 的 MapReduce 程式

  • 安裝:pip install mrjob
  • 一個官網最簡單的 word_count.py 範例:
from mrjob.job import MRJob

class MRWordFrequencyCount(MRJob):
    def mapper(self, _, line):
        yield "chars", len(line)
        yield "words", len(line.split())
        yield "lines", 1

    def reducer(self, key, values):
        yield key, sum(values)


if __name__ == '__main__':
    MRWordFrequencyCount.run()
  • 執行:python word_count.py input.txt
no configs found; falling back on auto-configuration
no configs found; falling back on auto-configuration
creating tmp directory /var/folders/bz/z8d70ds17y59ggw906xdqrvw0000gn/T/mrjob1.veck.20140804.113920.115769
writing to /var/folders/bz/z8d70ds17y59ggw906xdqrvw0000gn/T/mrjob1.veck.20140804.113920.115769/step-0-mapper_part-00000
Counters from step 1:
  (no counters found)
writing to /var/folders/bz/z8d70ds17y59ggw906xdqrvw0000gn/T/mrjob1.veck.20140804.113920.115769/step-0-mapper-sorted
> sort /var/folders/bz/z8d70ds17y59ggw906xdqrvw0000gn/T/mrjob1.veck.20140804.113920.115769/step-0-mapper_part-00000
writing to /var/folders/bz/z8d70ds17y59ggw906xdqrvw0000gn/T/mrjob1.veck.20140804.113920.115769/step-0-reducer_part-00000
Counters from step 1:
  (no counters found)
Moving /var/folders/bz/z8d70ds17y59ggw906xdqrvw0000gn/T/mrjob1.veck.20140804.113920.115769/step-0-reducer_part-00000 -> /var/folders/bz/z8d70ds17y59ggw906xdqrvw0000gn/T/mrjob1.veck.20140804.113920.115769/output/part-00000
Streaming final output from /var/folders/bz/z8d70ds17y59ggw906xdqrvw0000gn/T/mrjob1.veck.20140804.113920.115769/output
"chars"    21
"lines"    1
"words"    5
removing tmp directory /var/folders/bz/z8d70ds17y59ggw906xdqrvw0000gn/T/mrjob1.veck.20140804.113920.115769

其中統計結果:

"chars" 21
"lines"    1
"words"    5

程式碼中有個 MRJob,這個物件的 class 定義了你建立的 jobsteps 中會用到的方法:mapper, combiner, reducer,不一定全部都要有,但每個 step 至少要有三者其中之一,也就是說,你的 job 可以只有一個 mapper、一個 combiner 或一個 reducer

mapper(): 吃一個 key 和一個 value 參數,並且產生許多 key-value pair,就跟原本 Java 的 Mapper 一樣
reducer(): 吃一個 key 和一個 iterable value,也會產生許多 key-value pair,同原本 Java 的 Reducer

而最後的兩行程式碼,用來驅動我們的 job:

if __name__ == '__main__':
    MRWordCounter.run()  # where MRWordCounter is your job class

mrjob 預設是將 job 跑在 single python process,這樣比較方便 debug,但這樣不是分散式運算,要切換成其他的執行模式,可以加上 -r,共有三種不同模式:-r local, -r hadoop, -r emr

  1. -r local: 執行在多個子行程上 (虛擬分散式)
  2. -r hadoop: 執行在 hadoop 叢集上 (完全分散式)
  3. -r emr: 執行在 Elastic MapReduce 上

假如你的 input 檔案存放在 HDFS 或 S3 上(with EMR):

$ python my_job.py -r emr s3://my-inputs/input.txt
$ python my_job.py -r hadoop hdfs://my_home/input.txt

假如你有多個 step (通常)在一個 job 中,你可以透過覆寫 steps() 方法來定義 steps,建立 mrjob.step.MRStepstep list,例如以下範例:

from mrjob.job import MRJob
from mrjob.step import MRStep
import re

WORD_RE = re.compile(r"[\w']+")

class MRMostUsedWord(MRJob):

    # 覆寫 steps()
    def steps(self):
        return [
          # 定義 MRStep 1
          MRStep(mapper=self.mapper_get_words,    # 指定此 step 的 mapper 為 mapper_get_words
                 combiner=self.combiner_count_words,# 指定此 step 的 combiner 為 combiner_count_words
                 reducer=self.reducer_count_words), # 指定此 step 的 reducer 為 reducer_count_words
          # 定義 MRStep 2
          MRStep(reducer=self.reducer_find_max_word)
        ]

    def mapper_get_words(self, _, line):
        # yield each word in the line
        for word in WORD_RE.findall(line):
            yield (word.lower(), 1)

    def combiner_count_words(self, word, counts):
        # optimization: sum the words we've seen so far
        yield (word, sum(counts))

    def reducer_count_words(self, word, counts):
        # send all (num_occurrences, word) pairs to the same reducer.
        # num_occurrences is so we can easily use Python's max() function.
        yield None, (sum(counts), word)

    # discard the key; it is just None
    def reducer_find_max_word(self, _, word_count_pairs):
        # each item of word_count_pairs is (count, word),
        # so yielding one results in key=counts, value=word
        yield max(word_count_pairs)


if __name__ == '__main__':
    MRMostUsedWord.run()

[Python] Generator

generator function 是在一個 function 中使用了 yield 這個關鍵字來回傳函數結果
但與一般 function 不同的在於,yield 回傳以後,不會就結束 function,而是暫停函數的處理狀態
直到你呼叫 .next() 這個 function,就會再次執行 function

yield = return + pause

例如我們可以這樣用:

def foo(n):
    for i in range(1, n):
        yield i

我們可以這樣用這個 function:

y = foo(10)
y.next()

此時,會回傳 1,接著我們可以再 y.next() 一次,會得到 2,一直到 y.next() 回傳 10 以後,function 才算結束執行

這種用法可以用來優化 iterable function
以下方程式碼來說,要取得小於 n 的所有數列
我們建立了一個 list 來存放結果,並且逐一將符合小於 n 的數加入到 list 中
最後才一次回傳這個 list

# Build and return a list
def firstn(n):
    num, nums = 0, []
    while num < n:
        nums.append(num)
        num += 1
    return nums

def mysum(iterable_object):
        sum = 0
        for i in iterable_object:
          sum += i
       return sum

sum_of_first_n = mysum(firstn(1000000))

這樣的運作方式需要把一整個 list 存在記憶體中
如果項目太多,會導致記憶體浪費(太多被佔用的空間無法釋放給其他行程使用)或是所需記憶體不足的情況

generator 讓我們可以的 function 可以不用一次取得所有的 list 來累加
而可以取得一個就加一次

# a generator that yields items instead of returning a list
def firstn(n):
    num = 0
    while num < n:
        yield num
        num += 1

def mysum(iterable_object):
    sum = 0
    for i in iterable_object:           # 每次執行至此,就會 yield 一個新值
        sum += i
    return sum

sum_of_first_n = mysum(firstn(1000000))