質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.31%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Raspberry Pi

Raspberry Piは、ラズベリーパイ財団が開発した、名刺サイズのLinuxコンピュータです。 学校で基本的なコンピュータ科学の教育を促進することを意図しています。

Q&A

解決済

1回答

668閲覧

プログラムの簡素化:クラスの使用について(Pythonを使用)

shinchoroX

総合スコア18

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Raspberry Pi

Raspberry Piは、ラズベリーパイ財団が開発した、名刺サイズのLinuxコンピュータです。 学校で基本的なコンピュータ科学の教育を促進することを意図しています。

0グッド

0クリップ

投稿2023/07/31 02:46

編集2023/08/23 08:33

実現したいこと

Pythonにてプログラムを作成しました。
が、古いC言語の知識しか無いので、プログラムがあまり簡素に出来ません。
以下のプログラムを簡素化する場合は、クラスを使えば簡素に出来るのか?
または、別の手法があるのか?
アドバイスを頂けると幸いです。
オブジェクト指向を勉強していますが、実際に適用するとなると難しくなやんでいます。

前提

ラズベリーパイにて、Pythonにてプログラムを製作しました。
7つのデバイスの測定値を受け取ります。
関数を作成し、引数にてデバイスを指定し
・デバイスの初期化
・測定値の入手
・測定値の表示
・測定値のファイル書き込み
をそれぞれ各関数にて行っています。

したがって、7台を制御するプログラムは、4つの関数を7台分記述しています。
これを簡素に出来ないか?と考えています。

該当のソースコード

Python

1#抜粋 例えば2台のデバイスを制御する場合 2#見やすくするために、関数定義は後半に記載 3 4#初期化 5init_ina226(INA226_ADDR_1,CONFIG_VALUE_1,SHANT_OHM_VALUE_1) 6init_ina226(INA226_ADDR_3,CONFIG_VALUE_1,SHANT_OHM_VALUE_1) 7 8#測定メインループ 9#1台目 10meas_results_1= get_result(INA226_ADDR_1,SHUNT_OHM_1) 11print_result(1,meas_results_1[0],meas_results_1[1],meas_results_1[2],meas_results_1[3],meas_results_1[4],meas_results_1[5]) 12savedata(1,meas_results_1[5],meas_results_1[3]) 13 14#2台目 15meas_results_2= get_result(INA226_ADDR_1,SHUNT_OHM_1) 16print_result(2,meas_results_2[0],meas_results_2[1],meas_results_2[2],meas_results_2[3],meas_results_2[4],meas_results_2[5]) 17savedata(2,meas_results_2[5],meas_results_2[3]) 18 19#関数定義 20def get_result(dev_addr,shant_ohm): 21 busvolt = conv_busbolt(read_ina226(dev_addr,BUS_V_REG_ADDR)) 22 shuntvolt = conv_shuntbolt(read_ina226(dev_addr,SHUNT_V_RED_ADDR)) 23 current = conv_curr(read_ina226(dev_addr,CURR_REG_ADDR)) 24 #power = conv_pow(read_ina226(INA226_ADDR_1,POW_REG_ADDR)) 25 power = f'{conv_pow(read_ina226(dev_addr,POW_REG_ADDR)):.3f}' 26 27 calc_i=calc_curr(shuntvolt,shant_ohm) 28 calc_power=f'{calc_pow(busvolt,calc_i):.3f}' 29 30 return busvolt,shuntvolt,current,power,calc_i,calc_power 31

考えていること

Ptyhonの勉強をしていると(+JaxaScriptも)
・関数の引数を「関数で渡せる」
・配列の最後に値を入れるpushメソッドがある
こと自体に驚いています。

現在の私の考えですと、接続台数でifを書いて、meas_result_xを、二次元配列にして・・・
とどうしても手続き型で考えてしまいます。

オブジェクト指向でのスッキリしたプログラムの考え方をアドバイス頂けると幸いです。

2023/8/23追記

現状のクラス化のプログラム

# -*- coding: utf-8 -*- ''' 2023/7/1 TI製のIC「INA226」を使ったストロベリーリナックス製の 「INA226iso」を使った基本プログラム 2023/07/06 複数台に対応できるように汎用化している。 ファイルライトも各ファイルにて書き出し可能 I2C用のライブラリはsmbus2が主流らしいが、デフォルトではまだ入っていないので smbusを使う。 後学の為に、冗長表現がある。 1.数値の桁数の操作 a.PrintResult()内で、print時にfloatの数字の小数点を3桁にしている b.calcPower=f'{calc_pow(busvolt,calc_i):.4f}' で、printを使用しない場合での桁数を指定している これは後からファイルライトしている。 ''' import smbus import time import datetime import File_w_time as FW i2c = smbus.SMBus(1) #I2Cチャンネルの指定(基本は「1」) ''' ユーザーで変更するのは以下の4つだが、基本的には2つしか変更しない 1. INA226_ADDR_1 :デバイスの物理的なアドレス。ハンダでA1とA0をショートさせて16パターンから選ぶ 2. CONFIG_VALUE :AD変換の平均回数や返還時間のパラメータ変更 ***複数台接続時のほとんどの場合は、デバイスアドレスのみを変更すればOK ∵AD変換の回数等は同じ場合が多い 以下の2つはストロベリーリナックスのモジュールについているシャント抵抗なので基本的には変更しない 3. SHANT_OHM_VALUE -> : IC内部演算で電流値や電力を計算するときに使う 4. SHUNT_OHM -> 手計算用 : get_result()内のcalc_curr()にて使用 ''' DIR_PATH = './log' INTERVAL_MEAS = 1 #電力測定の間隔[s]。台数分の積が1サイクルとなる。例)2[s] x 7台 = 14[s] / 1サイクル '''以下は変更しない定数''' CONFIG_REG_ADDR = 0x00 SHUNT_V_RED_ADDR = 0x01 BUS_V_REG_ADDR = 0x02 POW_REG_ADDR = 0x03 CURR_REG_ADDR = 0x04 CAL_SHANTOHM_REG_ADDR = 0x05 class Ina226(): def __init__(self,dev_addr,conf,shant_ohm_hex,shant_ohm): self.dev_addr = dev_addr self.conf = conf self.shant_ohm_hex = shant_ohm_hex self.shant_ohm = shant_ohm def read_ina226(self,dev_addr,reg_addr): word = i2c.read_word_data(dev_addr, reg_addr) & 0xFFFF digitresult = ( (word << 8) & 0xFF00 ) + (word >> 8) return digitresult time.sleep(0.05) def write_ina226(self,dev_addr,reg_addr,code ): i2c.write_i2c_block_data(dev_addr,reg_addr,code) time.sleep(0.1) def init_ina226(self): self.write_ina226(self.dev_addr,CONFIG_REG_ADDR,self.conf) self.write_ina226(self.dev_addr,CAL_SHANTOHM_REG_ADDR,self.shant_ohm_hex) def get_result(self): busvolt = self.conv_busbolt(self.read_ina226(self.dev_addr,BUS_V_REG_ADDR)) shuntvolt = self.conv_shuntbolt(self.read_ina226(self.dev_addr,SHUNT_V_RED_ADDR)) current = self.conv_curr(self.read_ina226(self.dev_addr,CURR_REG_ADDR)) power = f'{self.conv_pow(self.read_ina226(self.dev_addr,POW_REG_ADDR)):.3f}' calc_i=self.calc_curr(shuntvolt,self.shant_ohm) calc_power=f'{self.calc_pow(busvolt,calc_i):.3f}' return [busvolt,shuntvolt,current,power,calc_i,calc_power] def print_result(self,num,busvolt,shuntvolt,current,power,calc_i,calc_power): print('----------%d台目の測定結果--------------'%num) print(f'busvolt = {busvolt:.4f}[V] , shuntvolt ={shuntvolt:.4f}') print(f'calc I = {calc_i:.3f}[A] , Meas I = {current:.3f}[A]') print(f'calc W = {float(calc_power):.3f}[W] , Meas W = {float(power):.3f}[W]') print('---------------------------------------') def savedata(self,dir_path,prefix,calc_power,power): texttime = datetime.datetime.now().strftime('%H:%M:%S') addpath = FW.make_tspath(dir_path,prefix) adddata = texttime +'\t'+ str(calc_power)+ '\t'+ str(power)+ '\n' FW.faddtxt(addpath,adddata) ### get_result用の関数 ### def conv_busbolt(self,digitval): busvolt = digitval * 1.25 /1000 # digit unit is [mV] so, divided by 1000 return busvolt def conv_shuntbolt(self,digitval): shuntvolt = digitval * 2.5 /(1000 *1000) # digit unit is [uV] so, divided by 1000*1000 return shuntvolt def conv_curr(self,digitval): current = digitval *1.0 / 1000 # digit unit is [mA] return current def conv_pow(self,digitval): power = digitval *25.0 / 1000 # digit unit is [mW] return power def calc_curr(self,shuntbolt,shuntorm): #input unit is [v][Ohm] not [mV] and [mOhm] calc_curr = shuntbolt / shuntorm return calc_curr def calc_pow(self,busvolt,current): power = busvolt * current return power if __name__ == '__main__': conf_list = [{'dev_addr':0x40, 'conf':[0x41,0x27], 'shant_ohm_hex':[0x0A,0x00], 'shant_ohm':0.002}, {'dev_addr':0x41, 'conf':[0x41,0x27], 'shant_ohm_hex':[0x0A,0x00], 'shant_ohm':0.002}, {'dev_addr':0x42, 'conf':[0x41,0x27], 'shant_ohm_hex':[0x0A,0x00], 'shant_ohm':0.002}, {'dev_addr':0x43, 'conf':[0x41,0x27], 'shant_ohm_hex':[0x0A,0x00], 'shant_ohm':0.002}, {'dev_addr':0x44, 'conf':[0x41,0x27], 'shant_ohm_hex':[0x0A,0x00], 'shant_ohm':0.002}, {'dev_addr':0x45, 'conf':[0x41,0x27], 'shant_ohm_hex':[0x0A,0x00], 'shant_ohm':0.002}, {'dev_addr':0x46, 'conf':[0x41,0x27], 'shant_ohm_hex':[0x0A,0x00], 'shant_ohm':0.002} ] ina226_list =[] for dev in conf_list: new_ina = Ina226(dev['dev_addr'],dev['conf'],dev['shant_ohm_hex'],dev['shant_ohm']) ina226_list.append(new_ina) for dev in range(len(ina226_list)): ina226_list[dev].init_ina226() #print( ina226_list[1].dev_addr) print(len(ina226_list)) while(True): for dev in range(len(ina226_list)): res = ina226_list[dev].get_result() ina226_list[dev].print_result(dev,*res) ina226_list[dev].savedata(DIR_PATH,dev,res[5],res[3]) time.sleep(INTERVAL_MEAS)

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

otn

2023/07/31 03:16

一般論としては、変数名に連番を付けるのを止めて配列にして、配列をループすることにすれば繰り返し部分の記述は1回で済みます。 ただ、このコードの場合、 ・5行目、6行目の連番が不規則 ・10行目、15行目の関数呼び出しで、引数が同じなのに結果が異なるように読み取れ、仕組みが不明 など、そもそものプログラム構造が不明なので、一般論が適用できるのか不明。
shinchoroX

2023/07/31 03:48

アドバイスありがとうございます。 ストンと腑に落ちました。 >・5行目、6行目の連番が不規則 ここまで見ていただいてありがとうございます。 ここはハードウェアに依存するパラメータですので、7台が同じハードウェアですので「_1」が不要でした。 >一般論としては、変数名に連番を付けるのを止めて配列にして ありがとうございます。 これが決めてでした。 うまく言葉では言いえませんが、変数名の連番の代わりにクラスに引数を渡して実装が出来そうな気がしてきましたので、それで考えてみます!
shinchoroX

2023/07/31 03:51

なのでお手数ですが、回答を上記のままでして頂いてもよろしいでしょうか。 それを「解決済み」としたいと考えております。
guest

回答1

0

ベストアンサー

「簡素なプログラム」というのがどういうものなのかわかりませんが、オブジェクト指向を取り入れると簡素になるということはないでしょうし、簡素にするためにオブジェクト指向で設計する必要があるということもありません。別の概念です。

クラスを勉強中ということで、この処理をオブジェクト指向で作ったらどうなるか、ということで取り組むのはいいと思います。

INA226 が対象デバイスであれば、 Ina226 というクラスを作って、それに対する操作をメソッドとして記述するというのが定番でしょうか。
最近はクラスの継承を利用しない方向ですが、同様のデバイスがあれば、レギュレータを親クラスとして用意するというようなこともできるでしょう。

以下適当な雰囲気だけのものですが、参考になれば。

python

1class Ina226(): 2 def __init__(self, addr, conf_1, shant_ohm): 3 # 初期化処理 4 5 def get_result(self): 6 # 値の取得処理 7 8 # その他の処理 9 10 11# 設定のリストを用意しておく。 12conf_list = [{'addr': ???, 13 'conf_1': ???, 14 'shant_ohm': ???}, 15 {'addr': ???, 16 'conf_1': ???, 17 'shant_ohm': ???} 18 ] 19 20# たとえば、ina226をリストで管理するなら 21ina226_list = [] 22for conf in conf_list: 23 new_ina = Ina226(conf['addr'], conf['conf_1'], conf['shant_ohm']) 24 ina226_list.append(new_ina) 25 26# すべての装置の結果を取得 27result_list = [] 28for ina in ina226_list: 29 res = ina.get_result() 30 result_list.append(res)

別質問のコメントからの追記

載っていたコードに気になるところがあったので、合せて僕なりの修正をしてみました。
これが正しいということではありません。 いちおうなぜそうしたのかをコメントに入れました。

python

1for conf in conf_list: 2 new_ina = Ina226(**conf) 3 # Ina226.init()の仮引数名とconf_listの辞書のkey名が一致していれば上のように書けます。 4 # ループ引数はconfの方がわかりやすいかと。 5 ina226_list.append(new_ina) 6 7for ina226 in ina226_list: 8 ina226.init_ina226() 9 # インデックス番号を使わないのであれば、こちらがわかりやすいでしょう。 10 # もし使うということだとしても、enumarateを使ったほうがわかりやすいと思います。 11 # こちらもループ引数を変えました。 12 # また、init_ina226()関数も、Ina226クラスの中にあるので、単にinit、`__init__`と被りたくないのであれば、dev_initでいいと思います。 13 # その場合 こうなります。 ina225.dev_init() 14 15#print( ina226_list[1].dev_addr) 16print(len(ina226_list)) 17 18while True: 19 for ina226 in ina226_list: 20 res = ina226.get_result() 21 ina226.print_result(ina226,*res)

ひっかかったのは最後の関数です。
まず、メソッドに自分自身を渡す必要はないでしょう。 必要であれば、第一引数(self)に入っています。
また、オブジェクトにそのオブジェクトのメソッド(get_result)で取得したデータを渡して出力(?)してもらうというのは、あまり見ない処理です。

  • print_result()の引数を無くして、呼ばれたら、内部でget_result()と同様の処理をして取得したデータを表示する。必要であれば、取得した値を返す。
  • get_result()のタイミングとは別のタイビングで取得結果を表示したいのであれば、スタティックメソッドにしたらいいのではないかと思います。

get_result()の後にprint_result()がある理由が明確であれば、それに対応した処理が書けるかなと思います。

投稿2023/07/31 04:41

編集2023/08/23 09:13
TakaiY

総合スコア14291

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

shinchoroX

2023/07/31 04:51

ありがとうございます。 少し前に本屋さんで表紙のみですが WEB+DB PRESS Vol.132 「オブジェクト指向神話からの脱却」を見ました。 一度買って読んでみます。 >「簡素なプログラム」というのがどういうものなのかわかりませんが、オブジェクト指向を取り入れると簡素になるということはないでしょうし、簡素にするためにオブジェクト指向で設計する必要があるということもありません。別の概念です。 ありがとうございます。 まずは仰っていると通り「ある処理をオブジェクト指向で作るとどうなるか?」をいくつか経験し、判断できるようになります。 > Ina226 というクラスを作って、それに対する操作をメソッドとして記述するというのが定番でしょうか。 本当にありがとうございます。 3週間ほど前に考えていたオブジェクト指向のプログラム残骸では、「測定結果をいれるオブジェクト」 を作ろうとしていました。 全く「構造化プログラム」が抜けていませんでした。 一度頂いたアドバイスを基に作ってみます!! 本当にありがとうございます。
shinchoroX

2023/08/04 08:50 編集

ありがとうございました。 まずは、クラスの実装前に混乱した頭の中を整理するために、パラメータを二次元配列にしてループするとどうなるかを試しました。 そうなると、配列で定義したパラメータを意味毎に纏めたい -> 構造体を使おう ->Pythonに構造体は無いぞ ->クラスがある との結論になりました。 とても勉強になりました。 アドバイスを頂いたお二方に心から感謝いたします。 ありがとうございました。 以下、クラス実装まえのループで7台分を測定するプログラム抜粋 #パラメータを配列で定義 dev_addr_list =[0x40,0x41,0x42,0x43,0x44,0x45,0x46] conf_list= [[0x41,0x27],[0x41,0x27],[0x41,0x27],[0x41,0x27],[0x41,0x27],[0x41,0x27],[0x41,0x27]] shant_orm_hex_list =[[0x0A,0x00],[0x0A,0x00],[0x0A,0x00],[0x0A,0x00],[0x0A,0x00],[0x0A,0x00],[0x0A,0x00] ] shant_orm_list =[0.002,0.002,0.002,0.002,0.002,0.002,0.002] dir_path = './log_multi' result_list=[[],[]] #init loop for dev in range(len(dev_addr_list)): init_ina226(dev_addr_list[dev],conf_list[dev],shant_orm_hex_list[dev]) while(True): for dev in range(len(dev_addr_list)): res = get_result(dev_addr_list[dev],shant_orm_list[dev]) result_list.append(res) print_result(dev,*res) savedata(dir_path,dev,res[5],res[3]) time.sleep(INTERVAL_MEAS)
TakaiY

2023/08/04 09:00

> 構造体を使おう ->Pythonに構造体は無いぞ ->クラスがある ここはそうではなく、 > 構造体を使おう ->Pythonに構造体相当は辞書(dict)だ です。
shinchoroX

2023/08/04 09:07

ご指摘ありがとうございます。 仰っる通りでした。 それに加えて関数がメソッドとして加わったものが、クラスと言うイメージとの認識を改めてました。
shinchoroX

2023/08/23 08:39 編集

本当に本当にありがとうございます! クラス云々は、入門書には少ししか記載のない部分でした。 しかも私の拙いプログラムに対して、TakaiY様の思考の過程まで示して下さりありがとうございます。 >また、オブジェクトにそのオブジェクトのメソッド(get_result)で取得したデータを渡して出力(?)してもらうというのは、あまり見ない処理です。 等、先に実装した「ループで7台を制御するプログラム」の考えから脱却できていませんでした。 少しずつになるとは思うのですが、頂いたアドバイスをしっかりと理解するように勉強を重ねます。 追記: whileのループの最後に"ina226_list[dev].savedata(DIR_PATH,dev,res[5],res[3])"があるのを記載忘れていました。 念のため、全文を記載しました。(厚かましい行為とは思うのですが、「一応は動いているプログラムを自分間なりに考えました」と誰かの後学になればと考えました)
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.31%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問