簡介

這本 Python 備忘錄內容包含 Python 2.x 和 Python 3.x .

作業系統是 Mac OS X 10.10.5 .

在沒有啟用任何虛擬環境的情況下, 預設的 python 指令是 python2, 預設的 pip 指令是 pip2, 預設的 idle 指令是 idle2 .

不論是否啟用虛擬環境 , Vim 編輯器連結的環境是 Python2 .

目錄結構

~/Desktop/
    python-projects/
        .gitignore # .gitignore 檔案範本
        README.txt # README.txt 檔案範本
        __init__.py # __init__.py 檔案範本
        __main__.py # __main__.py 檔案範本
        cmdcopy.py # cmdcopy.py 檔案範本
        cmdls.py # cmdls.py 檔案範本
        cmdsum.py # cmdsum.py 檔案範本
        create-project.sh
        helloworld.py # helloworld.py 檔案範本
        requirements.txt # requirements.txt 檔案範本
        setup.py # setup.py 檔案範本
        start-idle.py
        test_lib.py # test_lib.py 檔案範本
        test_lib_cmdsum_case1.txt # test_lib_cmdsum_case1.txt 檔案範本
        test_lib_cmdsum_case2.txt # test_lib_cmdsum_case2.txt 檔案範本
        test_lib_cmdsum_case3.txt # test_lib_cmdsum_case3.txt 檔案範本
        test_ut_lib.py # test_ut_lib.py 檔案範本
        tox.ini # tox.ini 檔案範本
        project1/ # 專案名稱
            py2/ # 虛擬環境名稱
                src/ # 本地 Git Repository 名稱
                    .gitignore # 預設是來自檔案範本
                    README.txt # 預設是來自檔案範本
                    requirements.txt # 預設是來自檔案範本
                    setup.py # 預設是來自檔案範本 , 替換內容中關於專案名稱和直譯器版本資訊
                    tox.ini # 預設是來自檔案範本 , 替換內容中關於專案名稱
                    project1/ # Top Package 名稱
                        __init__.py # 預設是空白檔案
                        __main__.py # 預設是來自檔案範本
                        helloworld.py # 預設是來自檔案範本
                        lib/ # Sub Package 名稱
                            __init__.py # 預設是來自檔案範本
                            cmdcopy.py # 預設是來自檔案範本
                            cmdsum.py # 預設是來自檔案範本
                            cmdls.py # 預設是來自檔案範本
                    tests/ # pytest 測試腳本目錄
                        test_lib.py # 預設是來自檔案範本 , 替換內容中關於專案名稱
                        test_lib_cmdsum_case1.txt # 預設是來自檔案範本 , 替換內容中關於專案名稱
                        test_lib_cmdsum_case2.txt # 預設是來自檔案範本 , 替換內容中關於專案名稱
                        test_lib_cmdsum_case3.txt # 預設是來自檔案範本 , 替換內容中關於專案名稱
                    unittests/ # unittest 測試腳本目錄
                        test_ut_lib.py # 預設是來自檔案範本 , 替換內容中關於專案名稱
        project2/
            py2/ # 可以在 Python2 上執行
        project3/
            py3/ # 可以在 Python3 上執行
        project4/
            py6/ # 可以在 Python2 和 Python3 上執行

關於 .gitignore 範本

*.pyc

MANIFEST
dist/

*.egg-info/

build/

.cache/

.tox/

關於 README.txt 範本



關於 __init__.py 範本

from . import cmdcopy
from . import cmdsum
from . import cmdls

關於 __main__.py 範本

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


import sys
import argparse

from . import helloworld
from . import lib


def main():
    # add parser
    parser = argparse.ArgumentParser(description='ADD YOUR DESCRIPTION HERE')
    parser.add_argument('-v', '--verbose',
                        help='increase output verbosity',
                        required=False, action="count", default=0)
    parser.add_argument('-d', '--debug',
                        help='turn on debug mode',
                        required=False, action="store_true")

    # add subparsers
    subparsers = parser.add_subparsers(title='subcommands')

    # add subcommand - copy
    parser_copy = subparsers.add_parser('copy', help='copy file')
    parser_copy.add_argument('-i', '--input', help='input file name',
                             required=True)
    parser_copy.add_argument('-o', '--output', help='output file name',
                             required=True)
    parser_copy.set_defaults(func=lib.cmdcopy.do_copy)

    # add subcommand - sum
    parser_sum = subparsers.add_parser('sum',
                                       help='calculate the sum of numbers')
    parser_sum.add_argument('number', metavar='N', type=int, nargs='+',
                            help='a list of numbers for sum operation')
    parser_sum.set_defaults(func=lib.cmdsum.do_sum)

    # add subcommand - ls
    parser_ls = subparsers.add_parser('ls',
                                      help='list files or folders')
    parser_ls.add_argument('file', metavar='F', type=str, nargs='+',
                           help='a list of files or folders for ls operation')
    parser_ls.set_defaults(func=lib.cmdls.do_ls)

    # add subcommand - helloworld
    parser_helloworld = subparsers.add_parser('helloworld',
                                              help='print hello world')
    parser_helloworld.set_defaults(func=helloworld.show)

    # parse
    args = parser.parse_args()

    if len(sys.argv) == 1:
        parser.print_help()
    else:
        if args.debug:
            print "Turning on debug mode ..."
            print sys.argv
            print args
        else:
            print "Turning off debug mode ..."

        if args.verbose >= 2:
            print "Turning on verbose mode II ..."
        elif args.verbose == 1:
            print "Turning on verbose mode I ..."
        else:
            print "Turning off verbose mode ..."

        args.func(args)

if __name__ == "__main__":
    main()

關於 cmdcopy.py 範本

def do_copy(args):
    print "Copying", args.input, args.output

    from shutil import copyfile
    copyfile(args.input, args.output)

關於 cmdls.py 範本

def do_ls(args):
    print "Listing", args.file

    import subprocess
    print subprocess.check_output(['ls'] + args.file)

關於 cmdsum.py 範本

def do_sum(args):
    """Calculate the sum of numbers.

    ...

    >>> import argparse
    >>> args = argparse.Namespace()
    >>> args.number = []
    >>> do_sum(args)
    Calculating the sum of []
    0
    0

    >>> args.number = [1]
    >>> do_sum(args)
    Calculating the sum of [1]
    1
    1

    >>> args.number = [1,2]
    >>> do_sum(args)
    Calculating the sum of [1, 2]
    3
    3
    """

    print "Calculating the sum of", args.number
    print sum(args.number)
    return sum(args.number)


if __name__ == "__main__":
    import doctest
    doctest.testmod()

關於 create-project.sh

#!/bin/bash
# Usage ./create-project [name]
# 
# Environment Variable
# - PP_PYTHON_VERSION : 2 | 3 , default 2
# - PP_GITHUB : ON | OFF , default OFF
# _ PP_PYPI : ON | OFF , default OFF

# set variable $ID
if [ $# -lt 1 ]; then
    ID=pp$(date +%Y%m%d%H%M%S) # pp is short for python project
else
    ID=$1
fi

# set variable $PP_PYTHON_VERSION
[ -z $PP_PYTHON_VERSION ] && PP_PYTHON_VERSION=2

# set variable $PP_GITHUB
[ -z $PP_GITHUB ] && PP_GITHUB=OFF

# set variable $PP_PYPI
[ -z $PP_PYPI ] && PP_PYPI=OFF

# create project with name $ID
# - create local project directory
# - create github repository (optional)
# - create pipy package (optional)
if [ -d $ID ]; then
    echo "Error: project $ID already exists."
    exit 1
else
    if [ $PP_PYTHON_VERSION == 2 ]; then
        _python=python2
        _venv_name=$ID/py2
    else
        _python=python3
        _venv_name=$ID/py3
    fi
    _src_name=$_venv_name/src
    _top_pkg_name=$_src_name/$ID
    _sub_pkg_name=$_top_pkg_name/lib
    _test_name=$_src_name/tests
    _unittest_name=$_src_name/unittests

    mkdir $ID &&
    (
        if [ $PP_PYTHON_VERSION == 2 ]; then
                virtualenv $_venv_name
        else
                python3 -m venv $_venv_name
        fi
    ) &&
    (
        mkdir -p $_sub_pkg_name &&
        mkdir $_test_name &&
        mkdir $_unittest_name
    ) &&
    (
        touch $_src_name/{.gitignore,README.txt,requirements.txt,setup.py,tox.ini} &&
        touch $_top_pkg_name/{__init__.py,__main__.py,helloworld.py} &&
        touch $_sub_pkg_name/{__init__.py,cmdcopy.py,cmdls.py,cmdsum.py} &&
        touch $_test_name/{test_lib.py,test_lib_cmdsum_case1.txt,test_lib_cmdsum_case2.txt,test_lib_cmdsum_case3.txt} &&
        touch $_unittest_name/test_ut_lib.py
    ) &&
    (
        cp .gitignore $_src_name/.gitignore &&
        cp README.txt $_src_name/README.txt &&
        cp requirements.txt $_src_name/requirements.txt &&
        sed 's/PROJECT-ID/'$ID'/g' setup.py | sed 's/PROJECT-PYTHON-VERSION/'$PP_PYTHON_VERSION'/g' > $_src_name/setup.py &&
        sed 's/PROJECT-ID/'$ID'/g' tox.ini > $_src_name/tox.ini &&
        cp __main__.py $_top_pkg_name/__main__.py &&
        cp helloworld.py $_top_pkg_name/helloworld.py &&
        cp __init__.py $_sub_pkg_name/__init__.py &&
        cp cmdcopy.py $_sub_pkg_name/cmdcopy.py &&
        cp cmdls.py $_sub_pkg_name/cmdls.py &&
        cp cmdsum.py $_sub_pkg_name/cmdsum.py &&
        sed 's/PROJECT-ID/'$ID'/g' test_lib.py > $_test_name/test_lib.py &&
        sed 's/PROJECT-ID/'$ID'/g' test_lib_cmdsum_case1.txt > $_test_name/test_lib_cmdsum_case1.txt &&
        sed 's/PROJECT-ID/'$ID'/g' test_lib_cmdsum_case2.txt > $_test_name/test_lib_cmdsum_case2.txt &&
        sed 's/PROJECT-ID/'$ID'/g' test_lib_cmdsum_case3.txt > $_test_name/test_lib_cmdsum_case3.txt &&
        sed 's/PROJECT-ID/'$ID'/g' test_ut_lib.py > $_unittest_name/test_ut_lib.py
    ) &&
    (
        cd $_src_name && git init . && git add . && git commit -m "first commit" && cd -
    ) &&
    (
        if [ $PP_GITHUB == "ON" ]; then
            curl -i -u jinsenglin -d '{"name":"'$ID'"}' -X POST https://api.github.com/user/repos &&
            cd $_src_name && git remote add github [email protected]:jinsenglin/$ID.git && git push -u github master && cd -
        fi
    ) &&
    (
        if [ $PP_PYPI == "ON" ]; then
            cd $_src_name && $_python setup.py register && $_python setup.py sdist upload && cd -
        fi
    )
fi

關於 helloworld.py 範本

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


def show(args):
    print(__name__)

關於 requirements.txt 範本

pluggy==0.3.1
py==1.4.31
pytest==2.9.1
tox==2.3.1
virtualenv==15.0.1

關於 setup.py 範本

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


from setuptools import setup


long_desc = """Programming Exercise for Python is a collection of sample code
for demostration of concept and usage of Python Language."""

setup(name='PROJECT-ID',
      version='0.0.1',
      packages=['PROJECT-ID', 'PROJECT-ID.lib'],
      entry_points={'console_scripts': [
          'PROJECT-ID = PROJECT-ID.__main__:main',
      ]},
      author='Jim Lin',
      author_email='[email protected]',
      url='https://github.com/jinsenglin/PROJECT-ID/',
      description='Programming Exercise for Python',
      long_description=long_desc,
      license="Apache 2.0",
      classifiers=[
          'Programming Language :: Python :: PROJECT-PYTHON-VERSION'
      ],
      install_requires=[]
      )

為了使用 entry_points 參數 , 所以使用 from setuptools from setup 而非 from distutils.core import setup

關於 start-idle.py

#!/usr/bin/env python


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


from idlelib.PyShell import main


if __name__ == '__main__':
    main()

關於 test_lib.py 範本

from PROJECT-ID import lib


def test_do_sum():
    import argparse
    args = argparse.Namespace()
    args.number = []
    assert lib.cmdsum.do_sum(args) == 0

    args.number = [1]
    assert lib.cmdsum.do_sum(args) == 1

    args.number = [1, 2]
    assert lib.cmdsum.do_sum(args) == 3


def test_do_copy():
    pass


def test_do_ls():
    pass

關於 test_lib_cmdsum_case1.txt 範本

>>> import argparse
>>> args = argparse.Namespace()
>>> args.number = []
>>> from PROJECT-ID import lib 
>>> lib.cmdsum.do_sum(args)
Calculating the sum of []
0
0

關於 test_lib_cmdsum_case2.txt 範本

>>> import argparse
>>> args = argparse.Namespace()
>>> args.number = [1]
>>> from PROJECT-ID import lib 
>>> lib.cmdsum.do_sum(args)
Calculating the sum of [1]
1
1

關於 test_lib_cmdsum_case3.txt 範本

>>> import argparse
>>> args = argparse.Namespace()
>>> args.number = [1,2]
>>> from PROJECT-ID import lib 
>>> lib.cmdsum.do_sum(args)
Calculating the sum of [1, 2]
3
3

關於 test_ut_lib.py 範本

import unittest

from PROJECT-ID import lib


class TestLib(unittest.TestCase):
    def test_do_sums(self):
        import argparse
        args = argparse.Namespace()
        args.number = []
        assert lib.cmdsum.do_sum(args) == 0

        args.number = [1]
        assert lib.cmdsum.do_sum(args) == 1

        args.number = [1, 2]
        assert lib.cmdsum.do_sum(args) == 3

    def test_do_copy(self):
        pass

    def test_do_ls(self):
        pass

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

關於 tox.ini 範本

[tox]
envlist = py27

[testenv]
deps = -rrequirements.txt

commands = py.test {posargs:./PROJECT-ID ./tests}

建立專案

cd ~/Desktop/python-projects/ && ./create-project.sh

# e.g., 專案一
source ~/Desktop/python-projects/project1/py2/bin/activate

cd ~/Desktop/python-projects/project1/py2/src && pip install -r requirements.txt

編輯專案 (使用 IDLE)

# e.g., 專案一
source ~/Desktop/python-projects/project1/py2/bin/activate

cd ~/Desktop/python-projects/project1/py2/src && ~/Desktop/python-projects/start-idle.py


# e.g., 專案三
source ~/Desktop/python-projects/project1/py3/bin/activate

cd ~/Desktop/python-projects/project1/py3/src && ~/Desktop/python-projects/start-idle.py

編輯專案 (使用 PyCharm)

# e.g., 專案一
Open ~/Desktop/python-projects/project1/py2

測試專案

# e.g., 專案一
source ~/Desktop/python-projects/project1/py2/bin/activate

cd ~/Desktop/python-projects/project1/py2/src && python -m project1

測試專案 (使用 PyCharm)

# e.g., 專案一
# Run Configuration
# Name = project1
# Script = project1
# Script parameters = sum 1 2 3
# (預設) Environment variables = PYTHONUNBUFFERED=1
# (預設) Python 2.7.11 virtualenv at ~/Desktop/python-projects/project1/py2
# Interpreter options = -m
# Working directory = /Users/cclin/Desktop/python-projects/project1/py2/src
# 勾選 Add content roots to PYTHONPATH
# 勾選 Add source roots to PYTHONPATH

測試專案 (使用 doctest)

# e.g., 專案一
source ~/Desktop/python-projects/project1/py2/bin/activate

# 當 cmdsum.py 內使用 doctest.testmod()
python ~/Desktop/python-projects/project1/py2/src/project1/lib/cmdsum.py -v

# 當 cmdsum.py 內沒有使用 doctest.testmod()
python -m doctest ~/Desktop/python-projects/project1/py2/src/project1/lib/cmdsum.py -v

測試專案 (使用 unittest)

# e.g., 專案一
source ~/Desktop/python-projects/project1/py2/bin/activate

# 當 test_ut_lib.py 內使用 unittest.main()
export PYTHONPATH=~/Desktop/python-projects/project1/py2/src && python ~/Desktop/python-projects/project1/py2/src/unittests/test_ut_lib.py

# 當 test_ut_lib.py 內沒有使用 unittest.main() , 或當一次測試多個案例時
export PYTHONPATH=~/Desktop/python-projects/project1/py2/src && python -m unittest discover ~/Desktop/python-projects/project1/py2/src/unittests

測試專案 (使用 py.test)

# e.g., 專案一
source ~/Desktop/python-projects/project1/py2/bin/activate

cd ~/Desktop/python-projects/project1/py2/src/tests && export PYTHONPATH=~/Desktop/python-projects/project1/py2/src && py.test

測試專案 (使用 tox)

# e.g., 專案一
source ~/Desktop/python-projects/project1/py2/bin/activate

cd ~/Desktop/python-projects/project1/py2/src && tox

更新專案

cd ~/Desktop/python-projects/project1/py2/src
pip freeze > requirements.txt
git add .
git commit -m "0.0.2" # 在 setup.py 先變更版號
git push -u github master
python setup.py sdist upload

測試專案 (上線階段)

# 測試安裝套件
pip install $ID

# 測試安裝套件後的命令列
$ID
$ID -h
#ID copy -h
$ID copy -i input.json -o output.json
$ID -d copy -i input.json -o output.json
$ID -d -v copy -i input.json -o output.json
$ID -d -vv copy -i input.json -o output.json
$ID -vv copy -i input.json -o output.json
$ID -v copy -i input.json -o output.json

# 測試安裝套件後的命令列
python -m $ID
python -m $ID -h
python -m $ID copy -h
python -m $ID copy -i input.json -o output.json
python -m $ID -d copy -i input.json -o output.json
python -m $ID -d copy -v -i input.json -o output.json
python -m $ID -d copy -vv -i input.json -o output.json
python -m $ID -vv copy -i input.json -o output.json
python -m $ID -v copy -i input.json -o output.json

# 測試安裝套件後的自定函式
from $ID import helloworld
helloworld.show()

目錄結構再版

  • Convention 1: 專案根目錄本身是一個 git repository
    • 使用 github.com 提供的 Python.gitignore
      • 預設的虛擬環境目錄名稱是 venv 或 ENV
  • Convention 2: 專案根目錄名稱與套件根目錄名稱一致
    • 支援 python -m <套件名稱> 的執行方式
  • Convention 3: 專案所依賴的套件列在 requirements.txt 和 requirements-dev.txt
    • requirements-dev.txt 有時候也叫做 test-requirements.txt, 例如 OpenStack Horizon 專案
    • 使用我整理的樣板檔案 requirements.txt
    • 使用我整理的樣板檔案 requirements-dev.txt
  • Convention 4: 使用套件 setuptools 的 setup.py 和 setup.cfg 和 MANIFEST.in 和 README.rst 來描述該專案的可散佈套件資訊
  • Convention 5: 使用套件 tox 來管理虛擬環境和執行開發任務包含自動測試和自動產生文件
    • 自動測試規範目前主流套件是 pep8 (其次是 pylint, flake8)
    • 自動測試框架目前主流套件是 pytest (其次是 nose, nose2)
    • 自動產生文件目前主流套件是 sphinx
  • Convention 6: 依據 pytest 所建議的測試目錄結構
    • 建議一 an extra directory outside your actual application code (我選這個)
    • 建議二 inlining test directories into your application package

範例

專案根目錄 e.g., pysperm/
    # convention 1
    工具 git 自動產生的目錄 i.g., .git/
    工具 git 的組態檔案 i.e., .gitignore
    專案虛擬環境目錄 i.e., venv/

    # convention 2
    套件根目錄 e.g., pysperm/
        python 套件目錄必要檔案 i.e., __init__.py
        python 套件目錄必要檔案 i.e., __main__.py # 為了支援 python -m <套件名稱>

    # convention 3
    專案執行階段所依賴的套件清單 i.e., requirements.txt
    專案開發階段所依賴的套件清單 i.e., requirements-dev.txt

    # convention 4
    可散佈套件資訊描述檔案一 setup.py
    可散佈套件資訊描述檔案二 setup.cfg
    可散佈套件資訊描述檔案三 (用於 python setup.py sdist) MANIFEST.in
    可散佈套件資訊描述檔案四 (被 setup.cfg 引用) README.rst

    # convention 5
    套件 tox 的組態檔案 i.e., tox.ini
    套件 tox 自動產生的目錄 i.e., .tox/

    # convention 6
    測試套件根目錄 i.e., tests/
        python 套件目錄必要檔案 i.e., __init__.py
        py.test 第一個測試案例 e.g., test_.py

步驟 (第一開發人員)

mkdir pysperm && cd pysperm
    # convention 1
    git init
    wget https://raw.githubusercontent.com/github/gitignore/master/Python.gitignore -O .gitignore
    virtualenv --no-site-packages venv

    # convention 2
    mkdir pysperm && touch pysperm/__init__.py
    wget https://raw.githubusercontent.com/jinsenglin/snippet/master/python/__main__.py -O pysperm/__main__.py

    # convention 3
    wget https://raw.githubusercontent.com/jinsenglin/snippet/master/python/requirements.txt
    wget https://raw.githubusercontent.com/jinsenglin/snippet/master/python/requirements-dev.txt
    source venv/bin/activate && pip install -r requirements.txt -r requirements-dev.txt && deactivate

    # convention 4
    wget https://raw.githubusercontent.com/jinsenglin/snippet/master/python/setup.py
    wget https://raw.githubusercontent.com/jinsenglin/snippet/master/python/setup.cfg
    touch MANIFEST.in
    touch README.rst

    # convention 5
    # 簡易版 source venv/bin/activate && echo -e '1\npy.test\n\n' | tox-quickstart && deactivate
    source venv/bin/activate && echo -e '4\nn\ny\nn\nn\nn\ny\nn\nn\npy.test\n\n' | tox-quickstart && deactivate
    echo 'changedir = tests' >> tox.ini

    # convention 6
    mkdir tests && touch tests/__init__.py
    wget https://raw.githubusercontent.com/jinsenglin/snippet/master/python/test_.py -O tests/test_.py

    # run test
    # 如果 tests/ 目錄內沒有任何測試案例也會引發錯誤
    source venv/bin/activate && tox ; deactivate

步驟 (其他開發人員)

git clone <pysperm> && cd pysperm
    virtualenv --no-site-packages venv && source venv/bin/activate && pip install -r requirements.txt -r

results matching ""

    No results matching ""