銀月の符号

Python 使い見習いの日記・雑記

ディスクの空き容量を調べる、 Linux, Windows 両対応

seko さん、 bonlife さんに続き、ディスクの空き容量を調べる Python コードの作成に挑戦しました。動作確認は WindowsDebian にて行っています。

disk_free(path) 関数は Unix の df コマンドのように path を含むファイルシステムの総容量、使用済み量、(現ユーザーの)使用可能量を返します。

2009/5/28 修正。 disk_free 関数の docstring 誤記。空き容量を使用量に。

# coding: utf-8

import os

__all__ = [
        'disk_free', 'get_avail_size', 'get_total_size', 'get_used_size']

if os.name ==  'nt':
    from ctypes import windll, WINFUNCTYPE, POINTER
    from ctypes import GetLastError, FormatError
    from ctypes import c_ulonglong
    from ctypes.wintypes import BOOL, LPCSTR, LPCWSTR

    ULARGE_INTEGER = c_ulonglong
    PULARGE_INTEGER = POINTER(ULARGE_INTEGER)
    if True:
        # UNICODE ビルド環境
        LPCTSTR = LPCWSTR
        _GetDiskFreeSpaceEx = 'GetDiskFreeSpaceExW'
    else:
        # ASCII ビルド環境、 os.name=='nt' では存在しない?
        LPCTSTR = LPCSTR
        _GetDiskFreeSpaceEx = 'GetDiskFreeSpaceExA'

    def _GetDiskFreeSpaceExErrcheck(result, func, args):
        if result == 0:
            error_code = GetLastError()
            error_message = FormatError(error_code)
            raise WindowsError(
                    error_code, error_message)
        return args

    GetDiskFreeSpaceEx = WINFUNCTYPE(BOOL,
            LPCTSTR, PULARGE_INTEGER, PULARGE_INTEGER, PULARGE_INTEGER)(
                (_GetDiskFreeSpaceEx, windll.kernel32),
                (
                    (1, 'lpDirectoryName', None),
                    (2, 'lpFreeBytesAvailable'),
                    (2, 'lpTotalNumberOfBytes'),
                    (2, 'lpTotalNumberOfFreeBytes')))
    GetDiskFreeSpaceEx.errcheck = _GetDiskFreeSpaceExErrcheck

    def disk_free(path):
        u"""
        path の総容量、使用量、(現ユーザーの)使用可能容量を返す
        """
        avail, total, free = GetDiskFreeSpaceEx(path)
        used = total - free
        return total, used, avail

else:
    def disk_free(path):
        u"""
        path の総容量、使用量、(現ユーザーの)使用可能容量を返す
        """
        s = os.statvfs(path)
        avail = s.f_bsize * s.f_bavail
        total = s.f_bsize * s.f_blocks
        free = s.f_bsize * s.f_bfree
        used = total - free
        return total, used, avail

def get_total_size(path):
    return disk_free(path)[0]

def get_used_size(path):
    return disk_free(path)[1]

def get_avail_size(path):
    return disk_free(path)[2]

def _test():
    print disk_free(r'.')

if __name__ == '__main__':
    _test()

Windows 版解説

Windows では GetDiskFreeSpaceEx を ctypes 経由で使用しています。

この API はポインタを渡して、値を書いてもらうことで結果を得るタイプのものです。この処理を ctypes で行うための素直な方法は、受け取り元となる c_longlong 型を作り、これを参照する簡易ポインタを byref 関数を使用して作り、 この簡易ポインタを GetDiskFreeSpaceEx に渡す、です。

しかし、この一連の処理は ctypes に任せることが可能です。使用する側は楽になる反面、 GetDiskFreeSpaceEx 外部関数を定義する側の手間は増えてしまいますが。

まずは WINFUNCTYPE を使用してプロトタイプを作ります。 WINFUNCTYPE に与えるべき値は戻り値の型と引数の型を並べたものです。

そうしてできたプロトタイプを実行し、外部関数を作ります。第 1 引数は関数名と共有ライブラリのタプルです。第 2 引数は外部関数の引数の扱いを表すタプルのタプルです。

通常、 ((1, 'argName1', 'default_value1'), (1, 'argName2', 'default_value2)) のように「1 と引数名とデフォルト値」にします。引数名とデフォルト値は省略可能です。

ポインタを渡して値を受け取るだけの引数には 1 ではなく 2 を指定します。つまり「2 と引数名とデフォルト値」です。たとえば (2, 'lpFreeBytesAvailable') とします。 2 の引数をもつ外部関数の戻り値は、本来の戻り値ではなく、 2 の引数に書かれるべき値のタプルになります。本来の戻り値は得られなくなるので、 errcheck 関数を駆使して例外処理をおこなうべきです。

詳しくは http://www.python.jp/doc/release/lib/ctypes-function-prototypes.html を参照してください。

なお、pywin32 が使える環境ならば 9-39 行目を次の 1 行に取り替えることができるのは bonlife さんの記事どおりです。 ctypes も不要です。

    from win32api import GetDiskFreeSpaceEx

Linux 版解説

Linux では os.statvfs を使用しています。