顯示具有 python 標籤的文章。 顯示所有文章
顯示具有 python 標籤的文章。 顯示所有文章

2025/03/03

Pyinstaller With Tobix Pywine

Python 可以透過 PyInstaller 將執行檔編譯成 Windows 可以使用的 exe,很好用,不同的 python 版本編譯出來的 exe 對目前的 Windows 有不同的支援度:

Python 版本 Windows 7 Windows 8 Windows 10 Windows 11
Python 3.6 ✅ (最後支援)
Python 3.7
Python 3.8
Python 3.9
Python 3.10 ⚠️ (可能有問題) ⚠️ (可能有問題)
Python 3.11 ❌ (不支援) ❌ (不支援)
Python 3.12 ❌ (不支援) ❌ (不支援)

所以編譯的時候要考量 python 的版本,Pyinstaller 只有支援 Windows,在 Linux like 的環境要執行必須透過 Wine 這種指令驅動,如果你要跑 CI/CD 沒那麼方便,後來我選擇了 tobix/pywine - Docker Image | Docker Hub 這個 docker 專案,只要選擇你要的 python 版本,如 tobix/pywine:3.8 ,將外部的 python 檔案 mount 進去 container,執行 docker exec -it builder wine pyinstaller.exe -F xxx.py 便可以編譯了,如果有相關要安裝的套件,也可以事先執行 docker exec -it builder wine python.exe -m pip install -r /tmp/requirements.txt

2024/10/15

Asynchronous Crawler In PHP, Node.js And Python

久久就要複習一下爬蟲寫法,不然不常用就生疏了,針對三種語言寫個 async 的版本吧。

node.js

const axios = require('axios');  
const urls = [  
    'https://jsonplaceholder.typicode.com/todos/1',  
    'https://jsonplaceholder.typicode.com/todos/2',  
    'https://jsonplaceholder.typicode.com/todos/3',  
];  
  
(async () => {  
    try {  
        const requests = urls.map(url => axios.get(url));  
        const responses = await Promise.all(requests);  
  
        responses.forEach(response => {  
            console.log(response.data.id);  
        });  
    } catch (error) {  
        console.error(error.message);  
    }  
})();

Python

import aiohttp  
import asyncio  
  
urls = [  
    'https://jsonplaceholder.typicode.com/todos/1',  
    'https://jsonplaceholder.typicode.com/todos/2',  
    'https://jsonplaceholder.typicode.com/todos/3',  
]  
  
  
async def fetch_url(session, url):  
    async with session.get(url) as response:  
        return await response.json()  
  
  
async def main():  
    async with aiohttp.ClientSession() as session:  
        tasks = [fetch_url(session, url) for url in urls]  
        responses = await asyncio.gather(*tasks)  
  
        for responses in responses:  
            print(responses.get('id'))  
  
  
if __name__ == '__main__':  
    asyncio.run(main())

PHP

<?php

use GuzzleHttp\Client;
use GuzzleHttp\Promise\Utils;

include "vendor/autoload.php";

$urls = [
    'https://jsonplaceholder.typicode.com/todos/1',
    'https://jsonplaceholder.typicode.com/todos/2',
    'https://jsonplaceholder.typicode.com/todos/3',
];

$client = new Client([
    'verify' => false,
]);
$promises = [];

foreach ($urls as $url) {
    $promises[] = $client->getAsync($url);
}

$results = Utils::all($promises)->wait();

foreach ($results as $result) {
    echo json_decode($result->getBody()->getContents(), true)['id'], PHP_EOL;
}

2023/03/21

Mock Open Write On Python

在 python 的測試中,有時候 mock 只要 mock a.b.c 就可以成功,但我們使用了 with 的情況下,就必須借助 __enter__ 這個魔術方法來測試了,我們介紹兩種寫法。

my_open.py

def write(path, context):
    with open(path) as f:
        f.write(context)

test_my_open.py

import unittest
import my_open

from unittest.mock import mock_open, patch


class MyTestCase(unittest.TestCase):
    def test_my_open_1(self):
        with patch('builtins.open', mock_open()) as o:
            path = '/tmp/test.txt'
            context = 'hello'
            my_open.write(path, context)

            o.assert_called_once_with(path)
            o().write.assert_called_once_with(context)

    @patch('builtins.open')
    def test_my_open_2(self, mock_o: mock_open):
        path = '/tmp/test.txt'
        context = 'hello'
        my_open.write(path, context)

        mock_o.assert_called_once_with(path)
        handle = mock_o.return_value.__enter__.return_value
        handle.write.assert_called_once_with(context)


if __name__ == '__main__':
    unittest.main()

2023/02/07

Python unittest

最近在幫公司開發,使用 python 製作出 Windows 使用的小工具,順便 k 了一下單元測試,把遇到過的技巧做個筆記。

chan.py

import os
import shutil

import requests as requests


class Chan:
    def add(self, number1: int, number2: int) -> int:
        return number1 + number2

    def raise_method(self, result: str) -> str:
        if result != 'raise':
            return result

        raise Exception('exception raised')

    def a(self) -> str:
        return self.b()

    def b(self) -> str:
        return 'b'

    def move_file(self, from_path, to_path) -> object:
        return os.rename(from_path, to_path)

    def get_url(self, url) -> int:
        response = requests.get(url)

        return response.status_code

    def my_open(self, path) -> str:
        with open(path) as f:
            return f.read()

    def copy_twice(self, source: str, dest_dir: str) -> None:
        base_path = os.path.dirname(__file__)
        shutil.copyfile(source, os.path.join(base_path, dest_dir, 'first'))
        shutil.copyfile(source, os.path.join(base_path, dest_dir, 'second'))

    def scan_folder(self, path) -> list:
        files = []
        for item in os.listdir(os.path.join(path)):
            if os.path.isfile(os.path.join(path, item)):
                files.append(os.path.join(path, item))

        return files

tests/test_chan.py

import os.path
import unittest
from unittest.mock import patch, MagicMock, mock_open, call

from chan import Chan


def mock_response(*args, **kwargs) -> object:
    class Response:
        status_code = 500

    return Response()


class MyTestCase(unittest.TestCase):
    base_path: str

    def setUp(self) -> None:
        self.base_path = os.path.dirname(os.path.dirname(__file__))

    def test_add(self) -> None:
        chan = Chan()
        actual = chan.add(1, 2)
        self.assertEqual(3, actual)

    def test_raise_should_not_happened(self) -> None:
        chan = Chan()
        actual = chan.raise_method('test')
        self.assertEqual('test', actual)

    def test_raise_should_happened(self) -> None:
        chan = Chan()
        self.assertRaises(Exception, chan.raise_method, 'raise')

    def test_raise_error_message(self) -> None:
        chan = Chan()
        with self.assertRaises(Exception) as error:
            chan.raise_method('raise')

        self.assertEqual('exception raised', str(error.exception))

    @patch.object(Chan, 'b', MagicMock(return_value='c'))
    def test_mock_method(self) -> None:
        chan = Chan()
        actual = chan.a()
        self.assertEqual('c', actual)

    @patch.object(Chan, 'b')
    def test_mock_method_called_once_by_injection(self, mock_b: MagicMock) -> None:
        mock_b.return_value = 'c'
        chan = Chan()
        actual = chan.a()
        self.assertEqual('c', actual)
        self.assertTrue(mock_b.called)

    def test_mock_method_called_once_by_with(self) -> None:
        with patch.object(Chan, 'b') as check:
            check.return_value = 'c'
            chan = Chan()
            actual = chan.a()
            self.assertEqual('c', actual)
            check.assert_called_once()

    @patch('chan.os.rename', MagicMock(return_value='moved'))
    def test_os_method(self) -> None:
        chan = Chan()
        actual = chan.move_file('1.txt', '2.txt')
        self.assertEqual('moved', actual)

    @patch('chan.requests.get', MagicMock(side_effect=mock_response))
    def test_requests_get(self) -> None:
        chan = Chan()
        actual = chan.get_url('https://www.google.com')
        self.assertEqual(500, actual)

    @patch('chan.open', mock_open(read_data='ok'))
    def test_open(self) -> None:
        chan = Chan()
        actual = chan.my_open('path')
        self.assertEqual('ok', actual)

    @patch('chan.open', new_callable=mock_open, read_data='ok')
    def test_open_by_injection(self, m) -> None:
        chan = Chan()
        actual = chan.my_open('path')
        self.assertEqual('ok', actual)
        self.assertTrue(m.called)

    @patch('chan.shutil.copyfile')
    def test_copy_twice(self, mock_copyfile: MagicMock) -> None:
        chan = Chan()
        source = 'test.zip'
        chan.copy_twice(source, 'test_path')

        expected = [call(source, os.path.join(self.base_path, 'test_path', 'first')),
                    call(source, os.path.join(self.base_path, 'test_path', 'second'))]

        self.assertEqual(expected, mock_copyfile.call_args_list)
        self.assertEqual(2, mock_copyfile.call_count)

    @patch('chan.os.path.isfile')
    @patch('chan.os.listdir')
    def test_scan_folder(self, mock_listdir: MagicMock, mock_isfile: MagicMock) -> None:
        mock_listdir.return_value = ['dir1', 'file1', 'dir2', 'file2']
        mock_isfile.side_effect = [False, True, False, True]

        chan = Chan()
        test_dir = 'test_dir'
        actual = chan.scan_folder('test_dir')
        expected = [os.path.join(test_dir, 'file1'), os.path.join(test_dir, 'file2')]
        self.assertEqual(expected, actual)


if __name__ == '__main__':
    unittest.main()

2021/12/03

Imitate Laravel Collection Sum Funtion in PHP, Python, Node.js

Laravel 包含了許多好用的套件,Collection 應該是最好用的,將陣列導進去以後可以做各種花式操作,自己嘗試在 PHP、Python、Node.js 上模擬一下作法。

PHP

<?php

class Collection
{
    private array $collections;

    public function __construct(array $collections)
    {
        $this->collections = $collections;
    }

    public function sum(Closure $callable = null): int
    {
        $callbackIn = ($callable === null) ? $this->isNotCallback() : $this->isCallback($callable);

        return array_reduce($this->collections, fn($carry, $item) => $carry + $callbackIn($item), 0);
    }

    private function isNotCallback(): Closure
    {
        return fn(int $value): int => $value;
    }

    private function isCallback(Closure $callable): Closure
    {
        return fn(int $value): int => $callable($value);
    }
}

$collection = new Collection([1, 2, 3]);
$cal = fn($item): int => $item + 5;

echo $collection->sum(), PHP_EOL;
echo $collection->sum($cal);

Python

from functools import reduce
from typing import Callable


class Collection(object):
    collections: list

    def __init__(self, collections: list):
        self.collections = collections

    def sum(self, callback=None) -> int:
        callback_in = self.is_not_callable() if callback is None else self.is_callable(callback)

        return reduce(lambda carry, item: carry + callback_in(item), self.collections, 0)

    @staticmethod
    def is_not_callable() -> Callable[[int], int]:
        return lambda x: x

    @staticmethod
    def is_callable(callback) -> Callable[[int], Callable[[int], int]]:
        return lambda x: callback(x)


def call_me() -> Callable[[int], any]:
    return lambda x: x + 5


collection = Collection([1, 2, 3])
print(collection.sum())
print(collection.sum(call_me()))

Node.js

class Collection {
    private collections: any[];

    constructor(collections: any[]) {
        this.collections = collections;
    }

    public sum(callback: Function = null) {
        const callbackIn = (callback === null) ? this.isNotCallable() : this.isCallable(callback);

        return this.collections.reduce((carry, item) => carry + callbackIn(item), 0);
    }

    private isNotCallable(): Function {
        return (value: any) => value;
    }

    private isCallable(callback): Function {
        return (value: any) => callback(value);
    }
}

const collections = new Collection([1, 2, 3]);
const cal = (value) => value + 5;

console.log(collections.sum());
console.log(collections.sum(cal));

2019/12/20

merge object

jQuery 有一個 $.extend 功能我覺得相當好用,假設你有兩個 object,{'name': 'chan', 'sport': 'basketball'},新的屬性是 {'sort': 'baseball'}, 透過 $.extend(object1, object2) 可以得到 {'name': 'chan', 'sport': 'baseball'},今天要在三個語言實現這個功能,但我要另外加一個內容是,如果新的內容是空字串就不覆蓋。

PHP
<?php  
  
$data = [  
    'name' => 'chan',  
    'sport' => 'basketball'  
];  
$newData = [  
    'name' => '',  
    'sport' => 'baseball'  
];  
$mergeObject = function($object1, $object2) {  
    return json_encode(  
        array_merge(  
            $object1,  
            array_filter(  
                $object2,  
                function($item) {  
                    return $item != '';  
                } 
            )  
        )  
    );  
}; 
  
echo $mergeObject($data, $newData); // {"name":"chan","sport":"baseball"}  
python
# coding=utf-8  
  
import json  
  
data = {  
    'name': 'chan',  
    'sport': 'basketball'  
}  
new_data = {  
    'name': '',  
    'sport': 'baseball'  
}  
  
  
def merge_object(object1, object2):  
    x = object1.copy()  
    n = {}
  
    for key, value in object2.items():  
        if value != '':  
            n[key] = value  
  
    x.update(n)  
  
    return json.dumps(x)  
  
  
print(merge_object(data, new_data)) # {"sport": "baseball", "name": "chan"}
node.js
let data = {  
    'name': 'chan',  
    'sport': 'basketball'  
};  
let newData = {  
    'name': '',  
    'sport': 'baseball'  
};  
let objectFilter = (object1, object2) => {  
    let n = {};  
  
    for (let i in object2) {  
        let value = object2[i];  
  
        if (value !== '') {  
            n[i] = value;  
        }  
    }  
  
    return JSON.stringify(Object.assign(object1, n));  
};  
  
console.log(objectFilter(data, newData)); // {"name":"chan","sport":"baseball"}

2018/07/16

非同步的爬蟲寫法 Python Node.js

PHP, Python, Node.js 都可以寫爬蟲,但如果你如果確定目標為一次多頁型的抓取,那一次併發會比一次抓一頁有效率許多,譬如說你拿到一個網址,透過這個網址可以解出有 20 頁的資料要抓,如果在單一 process 裡面寫迴圈跑 20,程式執行邏輯上是 送出請求 -> 拿到資料(對或錯) -> 送出請求 這樣的循環,假設一個頁面要抓 3 秒,這個 process 最起碼要花 60 秒完成,如果你一次併發 20 個請求,就是 3 秒完成,假設你為了資源一次併發限制為 10 個請求,也僅僅只需要 6 秒完成,跟 60 秒差距是很大的,PHP 原生除非裝其他的相關套件,否則沒有這種異步的寫法,聽說 PHP 7 已經內建有 Thread,在目前還不算普遍的情況下,我們就先不探討,以下示範 Python 跟 Node.js 的作法

job.php
<?php

$num = $_GET['num'];
$seconds = 3;

if (isset($_GET['seconds'])) {
    $seconds = $_GET['seconds'];
}

sleep($seconds);
echo $num.':'.$seconds;

我先在 server 上寫一段簡單的程式碼,他可以指定你回應延遲時間,這樣可以測出非同步的效果

crawler.py
# -*- coding: utf-8 -*-

from gevent import monkey
monkey.patch_all()
from gevent.pool import Pool
import urllib2


def download(url):
    response = urllib2.urlopen(url).read()
    print(response)
    return response

seconds = [1, 3, 2]
urls = [
    "http://chan15.info/job.php?num=%s&seconds=%s" % (i, seconds[i - 1]) for i in range(1, 4)
]
pool = Pool(10)
result = pool.map(download, urls)
print(result)

這是 python 的部份,我利用 gevent 的套件來實現 multithread,一次跑三次請求,number 順序為 1, 2, 3,而秒數延遲為 1, 3, 2,也就是說 number 2 跑最久,但我需要得到正確的 number 順序為 1, 2, 3,這樣的結果才是正確的

$ time python crawler.py
1:1
3:2
2:3
['1:1', '2:3', '3:2']

real    0m3.291s
user    0m0.248s
sys     0m0.040s

我們可以看出返回時間的確是 1, 3, 2,最後結果為 1, 2, 3,秒數為 0m3.291s,正確的順序以及併發時間

crawler.js
const util = require('util');
const request = require('request');

const getUrl = async (url) => {
    return new Promise((resolve, reject) => {
        request(url, (err, res, body) => {
            console.log(body);
            resolve(body);
        });
    })
};

const main = async () => {
    const url = 'http://chan15.info/job.php?num=%s&seconds=%s';
    const numbers = [1, 2, 3];
    const seconds = [1, 3, 2];
    const jobs = [];

    numbers.forEach((second, index) => {
        jobs.push(getUrl(util.format(url, second, seconds[index])));
    });

    Promise.all(jobs).then((result) => {
        console.log(result);
    });
};

main();

node.js 部份我使用了 request 去做請求,node.js 本來就是 async 的設計,所以搭配 async / await 跟 promise 的寫法即可

$ time node crawler.js 
1:1
3:2
2:3
[ '1:1', '2:3', '3:2' ]

real    0m3.320s
user    0m0.294s
sys     0m0.024s

執行結果跟 python 是一樣的

crawler.js 批次的寫法
const util = require('util');
const request = require('request');
const batch = require( 'batch-promise'  );

const url = 'http://chan15.info/job.php?num=%s&seconds=%s';
const numbers = [1, 2, 3];
const jobs = [];

numbers.forEach((second, index) => {
    jobs.push((resolve, reject) => {
        const target = util.format(url, second, 3)
        request(target, (err, res, body) => {
            console.log(body);
            resolve(body);
        });
    });
});

batch(jobs, 10).then((result) => {
    console.log(result);
});

多利用了 batch 這個套件

2018/07/13

pyenv

pyenv 安裝網址

curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash

# 在 .bashrc 加入這些內容
export PATH="~/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

常用指令:

  • pyenv update 更新軟體
  • pyenv install --list 列出可安裝的版本
  • pyenv install VERSION 安裝該版本
  • pyenv version 看現在使用的是那一個版本
  • pyenv versions 查看有哪些版本可以使用
  • pyenv local VERSION 指定現在用的版本或虛擬環境
  • pyenv virtualenv VERSION VIRTUAL_NAME 指定 python 版本開啟虛擬環境

2018/06/19

靜態變數

雖然現在提倡物件導向,但偶爾也會有一些特殊情況會重複性的呼叫一些 function,如果在一個數量為 10 的迴圈裡面呼叫一個 function,那該 function 就會被 call 十次,假設該 function 的某些結果,例如說撈取資料是同一件事的話,其實沒必要也呼叫十次,PHP 的 static 可以很有效率的解決這件事情

PHP
<?php

function getData($number)
{
    static $dataFromDatabase;

    if (!isset($dataFromDatabase)) {
        echo 'init', PHP_EOL;
        $dataFromDatabase = 'This is data';
    }

    return sprintf(
        '%s %s',
        $dataFromDatabase,
        $number
    );
}

for ($i = 0; $i < 10; $i++) {
    echo getData($i), PHP_EOL;
}

執行結果

init
This is data 0
This is data 1
This is data 2
This is data 3
This is data 4
This is data 5
This is data 6
This is data 7
This is data 8
This is data 9

假如把 static $dataFromDatabase; 拿掉就會看到十次 init,對固定資料來講的話就是很沒必要的資源浪費,python 以及 node.js 沒有這樣的宣告方式,但有轉彎的作法

python
# -*- coding: utf-8 -*-

def get_data(number):
    if not hasattr(get_data, 'data_from_database'):
        print('init')
        get_data.data_from_database = 'This is data'

    return '%s %s' % (get_data.data_from_database, number)

for i in range(10):
    print(get_data(i))
node.js
function getData(number) {
    if (getData.dataFromDatabase === undefined) {
        console.log('init');
        getData.dataFromDatabase = 'This is data';
    }

    return `${getData.dataFromDatabase} ${number}`;
}

for (let i = 0; i < 10; i++) {
    console.log(getData(i));
}

2018/05/08

Virtualenv

現代開發軟體幾乎都會安裝使用一些其他人寫好的套件,目前幾個主流語言也都有 package manager,python 大部分使用 easy_install 以及 pip,python 有別於其他語言,他的套件都裝在 global,因此如果你的 server 有多個專案在運行的話會看到一堆 package,這樣很不立於管理,我們可以使用 virtualenv 對專案進行隔離,另外使用 virtualenvwrapper 進行隔離管理

安裝套件
$ pip install virtualenv
$ pip install virtualenvwrapper
.bashrc
export WORKON_HOME=/envs
export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python
export VIRTUALENVWRAPPER_VIRTUALENV=/usr/local/bin/virtualenv
source /usr/local/bin/virtualenvwrapper.sh

將相關設定放置於 ~/.bashrc,進入 server 後即可直接使用,這邊的路徑要視當時 server 狀況做修改

建立專案
$ mkvirtualenv -p /usr/bin/python --no-site-packages CHAN

-p 的部份為指定 python 版本的 alias 位置,所以可以指定 2 或 3 當執行用的版本

建立環境後應該就直接進入該虛擬環境了,此時可以用 pip list 看到這個環境只有簡單的幾個必要套件存在而已

其他重要指令
workon # 列出所有虛擬環境
lsvirtualenv # 列出所有虛擬環境
lssitepackages # 列出該虛擬環境已安裝套件
rmvirtualenv NAME # 移除虛擬環境
cpvirtualenv NAME NEW_NAME # 複製虛擬環境
workon NAME # 進入虛擬環境
deactivate # 脫離虛擬環境

2018/04/20

Date Time Control On PHP Python Node.js

寫程式時,時間的使用是頻率很高的事情,簡單的像是拿取現在的時間、timestamp,稍微複雜的像是計算兩個時間的差距,今天來探討一些常見的例子

  1. 現在的時間
  2. 現在的 timestamp
  3. 將時間轉換為 timestamp
  4. 從 timestamp 取得時間
  5. 格式化時間
  6. 時間位移
  7. 時間差異

PHP

<?php

// 現在的時間
// PHP 沒有函式可以直接印出現在的值,一般都是用 date 加上需要的格式
echo date('Y-m-d H:i:s'), PHP_EOL; // 2018-04-20 10:03:22

// 現在的 timestamp
echo time(), PHP_EOL; // 1524189866

// 將時間轉換為 timestamp
echo strtotime('2018-04-20'), PHP_EOL; // 1524153600

// 從 timestamp 取得時間
echo date('Y-m-d H:i:s', 1524153600), PHP_EOL; // 2018-04-20 00:00:00

// 格式化時間
// 基本上就是使用 date 再去指定內容,常見的就是 Ymd His
// 其他的部份可以參考 http://php.net/date

// 時間位移
// 範例為 2018-04-20 12:00:00,往前移動一天又一小時
$datetimeString = '2018-04-20 12:00:00';
$datetime = date('Y-m-d H:i:s', strtotime($datetimeString));
echo $datetime, PHP_EOL; // 2018-04-20 12:00:00

// strtotime 很強大,其他應用可以參考 http://php.net/strtotime
$datetime = date('Y-m-d H:i:s', strtotime('-1 day -1 hour', strtotime($datetimeString)));
echo $datetime, PHP_EOL; // 2018-04-19 11:00:00

// 時間差異
// 基本上就是轉 timestamp 再去換算
$dateBegin = '2018-04-19';
$dateEnd = '2018-04-20';
echo strtotime($dateEnd) - strtotime($dateBegin), PHP_EOL; // 86400

Python

python 常用的方法有 timedatetime,接下來的範例用 datetime 直接做掉

# -*- coding: utf-8 -*-

import datetime

# 現在的時間
print(datetime.datetime.now()) # 2018-04-20 10:18:19.916229

# 現在的 timestamp
print(datetime.datetime.now().strftime('%s')) # 1524190730

# 將時間轉換為 timestamp
print(datetime.datetime(2018, 4, 20).strftime('%s')) # 1524153600

# 從 timestamp 取得時間
print(datetime.datetime.fromtimestamp(1524153600)) # 2018-04-20 00:00:00

# 格式化時間
# 其他文字格式可以參考 http://tinyurl.com/ydz294lw
print(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')) #2018-04-20 10:23:30

# 時間位移
datetime_string = '2018-04-20 12:00:00'
convert_datetime = datetime.datetime.strptime(datetime_string, '%Y-%m-%d %H:%M:%S')
print(convert_datetime) # 2018-04-20 12:00:00

# timedelta 很強大,其他可以參考 http://tinyurl.com/yc83qt4h
convert_datetime = convert_datetime - datetime.timedelta(days=1, hours=1)
print(convert_datetime) # 2018-04-19 11:00:00

# 時間差異
date_begin = '2018-04-19'
date_end = '2018-04-20'
begin = int(datetime.datetime.strptime(date_begin, '%Y-%m-%d').strftime('%s'))
end = int(datetime.datetime.strptime(date_end, '%Y-%m-%d').strftime('%s'))
print(end - begin) # 86400

Node.js

坦白說,如果你從網路上查詢 nodejs 或 jsavascript 對時間的控制大部分的人都會推薦你去使用套件,像是 moment.js,因為 JS 對時間的控制沒有那麼直覺跟友善,要花很多功夫才能達成,但我們今天就是研究 native 的東西,我們就用 native 的方法來完成

// 現在的時間
let date = new Date();
console.log(date.toString()); // Fri Apr 20 2018 11:32:22 GMT+0800

// 現在的 timestamp
console.log(date.getTime()); // 1524192512090

// 將時間轉換為 timestamp
const dateString = '2018-04-20';
date = new Date(Date.parse(dateString));
console.log(date.toString()); // Fri Apr 20 2018 08:00:00 GMT+0800 (CST)

// 從 timestamp 取得時間
date = new Date(1524192512090);
console.log(date.toString()); // Fri Apr 20 2018 10:48:32 GMT+0800 (CST)

// 格式化時間
// 這個就真的沒招了,不用套件的話得自己取值組合
date = new Date(1524192512090);
formatDate = date.getFullYear() + '-' + ('0' + (date.getMonth() + 1)).slice(-2) +
    '-' + ('0' + date.getDate()).slice(-2) + ' ' + date.getHours() + ':' +
    date.getMinutes() + ':' + date.getSeconds();
console.log(formatDate); // 2018-04-20 10:48:32

// 時間位移
const datetimeString = '2018-04-20 12:00:00';
const convertToDate = new Date(Date.parse(datetimeString));
const otherDate = new Date(
    convertToDate.getFullYear(), convertToDate.getMonth(), convertToDate.getDate() - 1,
    convertToDate.getHours() - 1, convertToDate.getMinutes(), convertToDate.getSeconds()
);
console.log(convertToDate.toString()); // Fri Apr 20 2018 12:00:00 GMT+0800 (CST)
console.log(otherDate.toString()); // Thu Apr 19 2018 11:00:00 GMT+0800 (CST)

// 時間差異
const dateBegin = '2018-04-19';
const dateEnd = '2018-04-20';
const begin = new Date(Date.parse(dateBegin));
const end = new Date(Date.parse(dateEnd));
console.log((end.getTime() - begin.getTime()) / 1000); // 86400

相信看完上面的範例以後,大家應該都會去裝 moment.js 了 XDDDD

2018/04/13

PHP, Python, Node.js CRUD On MySQL

紀錄一下這三個語言對 MySQL 做 CRUD 的方法當作筆記用,不會使用 ORM 或者其他功能強大複雜的套件,畢竟如果有使用 framework 通常都有附,這邊使用最簡潔的方法完成。

MySQL

先建立表單來做 CRUD 使用。

CREATE TABLE `examples` (
	`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
	`name` VARCHAR(50) NOT NULL,
	`created_at` DATETIME NOT NULL,
	PRIMARY KEY (`id`)
)
COLLATE='utf8mb4_unicode_ci';

PHP - PDO

PHP 現在一定是使用 PDO,實做方式如下。

<?php

$username = 'root';
$password = 123456;

try {
    $name = 'Chan';
    $createdAt = date('Y-m-d H:i:s');

    // 設定
    $dbh = new \PDO('mysql:host=localhost;dbname=demo;charset=utf8', $username, $password);

    // 寫入
    $sql = "INSERT INTO `examples` (`name`, `created_at`) VALUES (:name, :created_at)";
    $sth = $dbh->prepare($sql);
    $sth->bindParam(':name', $name, \PDO::PARAM_STR);
    $sth->bindParam(':created_at', $createdAt, \PDO::PARAM_STR);
    $sth->execute();

    // 提取多筆
    $sql = "SELECT * FROM `examples`";
    $sth = $dbh->prepare($sql);
    $sth->execute();
    $rows = $sth->fetchAll(\PDO::FETCH_ASSOC);
    var_dump($sth->rowCount());   
    var_dump($rows);

    // 提取單筆
    $sql = "SELECT * FROM `examples`";
    $sth = $dbh->prepare($sql);
    $sth->execute();
    $rows = $sth->fetch(\PDO::FETCH_ASSOC);
    var_dump($rows);
} catch (Exception $e) {
    var_dump($e-getMessage());
} finally {
    $sth = null;
    $dbh = null;
}

其中 PDO::PARAM_STR 的部份是讓你確定傳入文字的型別,可以有效防止 SQL Injection,常使用的有:

  1. PDO::PARAM_STR
  2. PDO::PARAM_INT
  3. PDO::PARAM_BOOL

其他可以使用的部份可以看這邊,更新跟刪除的部份就是把 INSERT 的內容改成 UPDATEDELETE,就不多寫範例了。

Python - MySQLdb

Python 我們使用 MySQLdb 來操作,怎麼安裝網路上有很多教學,這邊也不示範了。

# -*- coding: utf-8 -*-

import MySQLdb
import datetime

username = 'root'
password = '123456'

try:
    # 設定
    db = MySQLdb.connect(host='localhost', db='demo', user=username, passwd=password)
    cursor = db.cursor()

    # 寫入
    values = (
        'Chan',
        datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    )
    sql = "INSERT INTO `examples` (`name`, `created_at`) VALUES (%s, %s)"
    cursor.execute(sql, values)
    db.commit()

    # 提取多筆
    sql = "SELECT * FROM `examples`"
    cursor.execute(sql)
    rows = cursor.fetchall()
    print(cursor.rowcount)
    print(rows)

    # 提取單筆
    sql = "SELECT * FROM `examples`"
    cursor.execute(sql)
    row = cursor.fetchone()
    print(row)
except Exception as e:
    print(str(e))
finally:
    db.close()
這個套件沒有回傳 key name,所以如果你想要對應欄位名稱的話可能要這樣使用

# 提取多筆
sql = "SELECT * FROM `examples`"
cursor.execute(sql)
rows = cursor.fetchall()

for row in rows:
    pk, name, date = row
    print(pk)
    print(name)
    print(date)

此套件傳遞參數的方法很多,可以參考 Python best practice and securest to connect to MySQL and execute queries 這篇。

Node.js - mysql2

node.js 部份我選用了 mysql2,他語法基本上跟 mysql 一樣,多了些功能跟據說效能有提昇,我是沒實測,但就用最新的。

const mysql = require('mysql2');
const datetime = require('node-datetime');

try {
    // 設定
    const connection = mysql.createConnection({
        host: 'localhost',
        user: 'root',
        password: '123456',
        database: 'demo'
    });

    // 寫入
    const name = 'Chan';
    const dt = datetime.create();
    const createdAt = dt.format('Y-m-d H:M:S');
    let sql = 'INSERT INTO `examples` (`name`, `created_at`) VALUES (?, ?)';
    connection.execute(sql, [name, createdAt], (err, rows, fields) => {
        if (err) {
            console.log(err);
        }
    });

    // 讀取
    sql = 'SELECT * FROM `examples`';
    connection.execute(sql, (err, rows, fields) => {
        console.log(rows);
    });

    connection.end();
} catch (e) {
    console.log(e.message);
}

這個套件沒有封裝多筆或單筆的功能,要取單筆就是拿取陣列第一筆資料,上面的程式碼是 sync 模式執行的,所以其實是有機會讀到沒寫入的資料,除非你使用 callback 或 promise,示範一下 promise 的作法。

const mysql = require('mysql2/promise');
const datetime = require('node-datetime');

async function main() {
    try {
        const connection = await mysql.createConnection({
            host: 'localhost',
            user: 'root',
            password: '123456',
            database: 'demo'
        });

        // 寫入
        const name = 'Chan';
        const dt = datetime.create();
        const createdAt = dt.format('Y-m-d H:M:S');
        let sql = 'INSERT INTO `examples` (`name`, `created_at`) VALUES (?, ?)';
        await connection.execute(sql, [name, createdAt]);

        // 讀取
        sql = 'SELECT * FROM `examples`';
        const [rows] = await connection.execute(sql);
        console.log(rows);

        connection.end();
    } catch (e) {
        console.log(e.message);
    }
}

main();