éĄ¯į¤ēå…ˇæœ‰ 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 å¸¸į”¨įš„æ–šæŗ•æœ‰ time 莟 datetime,æŽĨä¸‹äž†įš„į¯„äž‹į”¨ 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 įš„å…§åŽšæ”šæˆ UPDATE 莟 DELETE,å°ąä¸å¤šå¯Ģį¯„äž‹äē†。

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();