techno_memo

個人用の技術メモ。python・ROS・AI系のソフトウェア・ツールなどの情報を記載

C言語経験者がpythonで開発する際のポイントまとめ

この記事の目的

 C言語(特に組込みソフト)経験者がpythonで開発する際のポイントをまとめる (よく聞かれたこと、聞かれることの整理用)

 * C言語開発者がpythonを開発する際のポイント・・・階層構造をもつpythonスクリプトのインポート方法

 * 別ファイルのimport方法・・・階層構造をもつpythonスクリプトのインポート方法

 * 変数/定数の定義・・・const・#define・グローバル変数・ローカル変数の扱い

C言語開発者がpythonを開発する際のポイント

 最近のデータ処理・AI関連の技術・オープンソースの発展により、組込み制御ソフト・C/C++など他言語の開発者であってもpythonによるプロトタイピングやデータ分析に手を出すケースが多くなっている。 (私もその一人であるが)

 C言語で組み込み等のソフトの開発経験者がpythonを開発する際のポイントは下記参考記事にまとめられている

qiita.com

 特に留意しておく点は

 インデント・・・C言語と異なりインデント(字下げ)が明示的に意味を持つ。if文、for文などはインデントでどこまでが対象かを規定

 型定義・・・動的型付けがされる言語であり、変数宣言時に型は不要。作る際は楽だが意図せぬ代入がされることが多いためデバッグによる確認が必要

 参照渡し・・・pythonにおける代入は基本的に参照渡しとなる (C言語でいうポインタ的な扱い)

 その他上記の参考サイトにはpythonの動作の仕組み(インタプリタ)、型定義、if文など制御文の書式などに一通り触れられているが、C言語でよくある複数ファイル/フォルダからなる構造の構築方法やC言語でよく使うconst/defineなどについては記載されていないので追加でまとめておく。

別ファイルのimport方法

 C言語の制御ソフトでは、機能別に複数ファイル/フォルダに分けて実装する際、include文とmake環境へのインクルードパス設定を記述する。pythonで同様のことをしたい場合、import文で別ファイルの記述内容を参照することが可能である。

f:id:sd08419ttic:20190602183346p:plain

上記の図でルートフォルダにあるスクリプトroot_func.pyから各フォルダのサブファンクションを直接参照する場合は下記のように記述できる。

#1階層下のファイルを呼び出し
from SubA.sub_funcA import func_sub_funcA
from SubB.sub_funcB import func_sub_funcB

#2階層下のファイルを呼び出し
from SubA.Sub01.sub_funcA1a import func_sub_funcA1a
from SubA.Sub01.sub_funcA1b import func_sub_funcA1b
from SubA.Sub02.sub_funcA2 import func_sub_funcA2

#3階層下のファイルを呼び出し
from SubA.Sub01.Sub03.sub_funcA3 import func_sub_funcA3

if __name__ == '__main__':
    func_sub_funcA()
    func_sub_funcB()
    func_sub_funcA1a()
    func_sub_funcA1b()
    func_sub_funcA2()
    func_sub_funcA3()

一方で、サブフォルダSub01にあるsub_funcA1a.pyから上位階層にある関数をインポートしたい場合、相対パスを通すだけだとエラーが出力されてしまう。

(実行フォルダよりも上位パスにあるファイルのインポートを禁止するpythonの仕様のため)

そこで、サブディレクトリからのインポート時にpythonの探索パスにルートディレクトリのパスを加える方法を用いる。

上図でサブフォルダ01から上位階層や他フォルダを参照するためにシステムパスを変更する。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys

#prj_config.pyまでのパスを追加する

#os.path.abspath(__file__) : pythonファイルが存在する絶対パスを取得
#os.path.dirname(): 入力したパスの一つ上の階層のパスを取得

ROOTPATH = os.path.dirname(os.path.abspath(__file__))
CFGPATH = os.path.dirname(os.path.dirname(ROOTPATH))

#sys.path.append() :入力したパスをpythonファイルの探索パスに追加する

sys.path.append(CFGPATH)

#from にルートフォルダからのフォルダ階層/ファイル名を記載する
from SubA.Sub01.sub_funcA1b import func_sub_funcA1b
from SubA.Sub02.sub_funcA2 import func_sub_funcA2
from SubA.Sub01.Sub03.sub_funcA3 import func_sub_funcA3

def func_sub_funcA1a():
    print("sub_funcA1a is called!!")

if __name__ == '__main__':
    func_sub_funcA1b()
    func_sub_funcA2()
    func_sub_funcA3()
    pass

システムパスにルートフォルダを追加することで、開発時に自分のファイルからの相対的なフォルダ関係ではなくルートフォルダからの階層を考慮した記述で実装することが可能である。

変数/定数の定義

const (定数)について

C言語では変数宣言時にconst修飾子を追加することで書き込み不可な定数を利用することができる。pythonには同様の機能はない (完全に書き込みを防ぐ変数を定義できない)

慣習として、全文大文字の変数を定数として扱うことが多いらしい。

 参考サイト www.tohoho-web.com

defineについて

C言語では#defineを用いてよく使うパラメータやコンパイル時に切り替えたい機能のフラグなどを記述することが一般的である。 (特に組込みソフトでは込み入った実装がされることが多い)

pythonでは#defineにあたる機能はないため、上記のような実装は通常の関数/変数を用いて実現する必要がある。

複数ファイルにまたがる機能の開発を行う場合においては、プロジェクト共通で使う定義(機能の切り替え、主要な値の定義など)設定用のコンフィグを記載したpythonファイルをルートフォルダに作成し、他ファイルからimportするようにつくるとC言語に近いスタイルで開発することができる。

global変数・static変数について

pythonではモジュール内のglobal変数を定義し、他関数から参照することができる。しかし、下記の方法では複数ファイルからの更新はできずC言語のような他ファイルの変数をexternで参照して更新という使い方ではない。

ama-ch.hatenablog.com

他機能から利用する変数はクラス内にまとめて記載したほうが開発後の再利用性やデバッグなどの効率が良い。下記のようなクラスを用いるとC言語のイメージに近い実装ができると考えている。

class sub_function_X():

    #初期化用クラス (変数の宣言/初期化)
    def __init__(self):

        #グローバル変数 (他機能からの参照がある変数)
        self.glb_var1 = 0
        self.glb_flg = True

        #スタティック変数 (公開範囲をクラス内だけにしたい変数)
        self.__sts_var1 = 0

    #クラス内ローカル関数
    def __static_func(self):
        self.__sts_var1 = self.__sts_var1 + 1
        print ("This is Private function")

    #グローバル関数
    def global_func(self):
        self.__static_func()
        print ("This is Global function")
        print(self.__sts_var1)

if __name__ == '__main__':
    test_instance = sub_function_X()
    test_instance.global_func()