Pythonプログラミングで

リバーシを作る

Stage 2 プログラムの土台を作る

2-1 ロガーの設定をする

まずはコードの config.py というファイルをご覧ください。

                        
#! /usr/bin/env python3
# config.py
# programmed by Saito-Saito-Saito
# explained in https://Saito-Saito-Saito.github.io/reversi
# last update: 2/7/2020


import sys
from logging import getLogger, StreamHandler, FileHandler, Formatter, DEBUG, INFO, WARNING, ERROR, CRITICAL


### LOG SETTINGS
DEFAULT_LOG_ADDRESS = 'log.txt'
DEFAULT_LOG_FORMAT = Formatter('%(asctime)s - %(levelname)s - logger:%(name)s - %(filename)s - L%(lineno)d - %(funcName)s - %(message)s')

# set up function
def setLogger(name='default', level=DEBUG, *, fhandler=None, fhandler_level=DEBUG, filename=DEFAULT_LOG_ADDRESS, filemode='w', fileformat=DEFAULT_LOG_FORMAT, shandler=None, shandler_level=CRITICAL, streamformat=DEFAULT_LOG_FORMAT):
    logger = getLogger(name)
    logger.setLevel(level)
    
    # file handler
    fhandler = fhandler or FileHandler(filename, mode=filemode)
    fhandler.setLevel(fhandler_level)
    fhandler.setFormatter(fileformat)
    logger.addHandler(fhandler)
    
    # stream handler
    shandler = shandler or StreamHandler()
    shandler.setLevel(shandler_level)
    shandler.setFormatter(streamformat)
    logger.addHandler(shandler)
    
    return logger


logger = setLogger(__name__)


# board is 8 * 8
SIZE = 8
if int(SIZE / 2) != SIZE / 2:
    logger.error('SIZE VALUE HAS TO BE EVEN')
    print('SYSTEM ERROR')
    sys.exit()


# rows & columns index
ROW = 0
COL = 1


# piece values
EMPTY = 0
B = BLACK = 1
W = WHITE = -1


# game proceeding/set
GAME_PRC = 0
GAME_SET = 1


# for return
SUCCEEDED = True
FAILED = False


# direction is represented as follows: [toROW - frROW, toCOL - frCOL]
WHOLE_DIRECTION = [
    [-1, -1], [-1, 0], [-1, 1],
    [0, -1], [0, 1],
    [1, -1], [1, 0], [1, 1]
]


# whether the index is in the board
def InBoard(subject):
    if 0 <= subject < SIZE:
        return True
    else:
        return False
                        
                    
UNAVAILABLE

クソなげーなおい

冒頭でいろいろインポートしていますね。

                        
import sys
from logging import getLogger, StreamHandler, FileHandler, Formatter, DEBUG, INFO, WARNING, ERROR, CRITICAL
                        
                    

下段の logging からのインポートについては、loggerlogging の違いを明確にするために、logging という文字を書かなくていいようにしています。その意義についてはこちらの記事で大いに力説されていますので、是非ご覧ください。

ログ出力のための print と import logging はやめてほしい

~~~~~~~~~~~~~~~

まずはログの設定をします。

                        
### LOG SETTINGS
DEFAULT_LOG_ADDRESS = 'log.txt'
DEFAULT_LOG_FORMAT = Formatter('%(asctime)s - %(levelname)s - logger:%(name)s - %(filename)s - L%(lineno)d - %(funcName)s - %(message)s')

# set up function
def setLogger(name='default', level=DEBUG, *, fhandler=None, fhandler_level=DEBUG, filename=DEFAULT_LOG_ADDRESS, filemode='w', fileformat=DEFAULT_LOG_FORMAT, shandler=None, shandler_level=CRITICAL, streamformat=DEFAULT_LOG_FORMAT):
    logger = getLogger(name)
    logger.setLevel(level)
    
    # file handler
    fhandler = fhandler or FileHandler(filename, mode=filemode)
    fhandler.setLevel(fhandler_level)
    fhandler.setFormatter(fileformat)
    logger.addHandler(fhandler)
    
    # stream handler
    shandler = shandler or StreamHandler()
    shandler.setLevel(shandler_level)
    shandler.setFormatter(streamformat)
    logger.addHandler(shandler)
    
    return logger
                        
                    
UNAVAILABLE

こんなに長いの何やるんだよ

大したことないですって。見てけばわかりますから。

~~~~~~~~~~~~~~~

はじめにログを出力するデフォルトのファイルの名前、フォーマットを設定しておきます。

                        
### LOG SETTINGS
DEFAULT_LOG_ADDRESS = 'log.txt'
DEFAULT_LOG_FORMAT = Formatter('%(asctime)s - %(levelname)s - logger:%(name)s - %(filename)s - L%(lineno)d - %(funcName)s - %(message)s')
                        
                    

今後ロガーは基本的にこのデフォルトに従って出力します。

下に行くと関数がありますよね。

                        
# set up function
def setLogger(name='default', level=DEBUG, *, fhandler=None, fhandler_level=DEBUG, filename=DEFAULT_LOG_ADDRESS, filemode='w', fileformat=DEFAULT_LOG_FORMAT, shandler=None, shandler_level=CRITICAL, streamformat=DEFAULT_LOG_FORMAT):
    logger = getLogger(name)
    logger.setLevel(level)
    
    # file handler
    fhandler = fhandler or FileHandler(filename, mode=filemode)
    fhandler.setLevel(fhandler_level)
    fhandler.setFormatter(fileformat)
    logger.addHandler(fhandler)
    
    # stream handler
    shandler = shandler or StreamHandler()
    shandler.setLevel(shandler_level)
    shandler.setFormatter(streamformat)
    logger.addHandler(shandler)
    
    return logger
                        
                    
UNAVAILABLE

なんでわざわざ関数なんか作んなあかんの?

今後各ファイルでロガーを作ることになるわけですが、いちいち「ロガーを宣言して、ハンドラーを宣言して、ロガーに加えて、...」っていうのはあまりにも面倒。そこで「いっそのことロガーを作る機能を全部まとめちゃおうじゃないか」という魂胆です。楽になりますよ。

~~~~~~~~~~~~~~~

デフォルトの引数を順に見ていきましょう。

内容デフォルト
name新設するロガーの名前'default'
levelロガーのレベルDEBUG
fhandlerロガーファイルハンドラーNone
fhandler_levelファイルハンドラーに記載する最低レベルDEBUG
filenameログを出力するファイルの名前DEFAULT_LOG_FILE_NAME
filemodeファイルの書き込みモード(新規書き込み 'w' / 上書き 'a')'w'
fileformatファイルに記録するフォーマットDEFAULT_LOG_FORMAT
shandlerロガーストリームハンドラーNone
shandler_levelストリームハンドラーで記録する最低レベルWARNING
streamformatストリームハンドラーで記録するフォーマットDEFAULT_LOG_FORMAT
UNAVAILABLE

さっぱり意味わかんねぇ...

所々にリンクを隠していますので、わからない言葉に当たったら都度解説をご覧ください。なんならここを飛ばして 2-2 に行ってしまうのもアリです。

引数の途中にアスタリスク * が一人でいるのは、後続の引数をキーワード引数にするためです。

                        
def setLogger(name='default', level=DEBUG, *, fhandler=None, fhandler_level=DEBUG, filename=DEFAULT_LOG_ADDRESS, filemode='w', fileformat=DEFAULT_LOG_FORMAT, shandler=None, shandler_level=CRITICAL, streamformat=DEFAULT_LOG_FORMAT):
                        
                    

実際に引数を入れるときは setLogger(filemode='a') のように、キーワードと一緒に書く必要があります。

~~~~~~~~~~~~~~~

関数の中身に入ります。まずは getLogger でロガーを取得。getLogger の引数はロガーにつける名前ですから、setLogger の引数にした name を入れるんですね。

                        
def setLogger(name='default', level=DEBUG, *, fhandler=None, fhandler_level=DEBUG, filename=DEFAULT_LOG_ADDRESS, filemode='w', fileformat=DEFAULT_LOG_FORMAT, shandler=None, shandler_level=CRITICAL, streamformat=DEFAULT_LOG_FORMAT):
    logger = getLogger(name)
    logger.setLevel(level)
                        
                    

直下で logger に setLevel を使ってレベルを設定しています。

今度はファイルハンドラーの設定です。

                        
    # file handler
    fhandler = fhandler or FileHandler(filename, mode=filemode)
    fhandler.setLevel(fhandler_level)
    fhandler.setFormatter(fileformat)
    logger.addHandler(fhandler)
                        
                    

もし引数の時点で使うファイルハンドラーが指定されていれば、それをそのまま fhandler に代入します。一方引数に何も入れられていなければ fhandler は None の状態ですから、FileHandler メソッドファイルハンドラーを新設するという仕組みになっています。

その下でフォーマットを設定します。

                        
    fhandler.setLevel(fhandler_level)
    fhandler.setFormatter(fileformat)
    logger.addHandler(fhandler)
                        
                    

設定が終われば addHandler で logger に適用しましょう。

~~~~~~~~~~~~~~~

ラストはストリームハンドラーを設定しますよ。

UNAVAILABLE

ターミナルとかに出力されるやつやろ

よく勉強してらっしゃるじゃないですか。その通りです。

UNAVAILABLE

イヤミかクソ

                        
    # stream handler
    shandler = shandler or StreamHandler()
    shandler.setLevel(shandler_level)
    shandler.setFormatter(streamformat)
    logger.addHandler(shandler)
                        
                    

やることはファイルハンドラーでやったのと同じです。引数でストリームハンドラーが指定されていればそれを使いますし、指定されていなければ shandler は None ですから、StreamHandler メソッドで新設します。

レベルとフォーマットを設定したら、やはり logger に適用しましょう。

                        
    shandler.setFormatter(streamformat)
    logger.addHandler(shandler)
                        
                    

~~~~~~~~~~~~~~~

これで logger は出来上がりました。この setLogger メソッドを使ってロガーを作りたいわけですから、リターンするのはロガーがいいですね。

UNAVAILABLE

logger = setLogger(...) みたいにしたいってことやろ?

その通りです。ということで、メソッドの最後には

                        
    return logger
                        
                    

を入れておきます。

NEXT 2-2 定数を設定する