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

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

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

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

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

4回答

19398閲覧

関数の引数を減らす方法(グローバル変数の利用?クロージャ?)

退会済みユーザー

退会済みユーザー

総合スコア0

Python 3.x

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

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

4クリップ

投稿2017/05/04 13:50

編集2017/05/04 14:14

###悩んでいること
自作の関数について引数が多いと使い勝手が悪いため、引数を少なくしたいです。

change_point(dir, distance, width, height, x, y)の引数を省略して
change_point(dir, distance)と減らしたい場合、理想的な方法を教えて頂けませんでしょうか?

追記
クロージャを使えばグローバル変数を減らせるという記述を見つけたのですが、今回の目的と関係あるでしょうか?
クロージャってどんなときに使うの?

###試したこと
自作の関数(以下にコード記載)について、
change_point(dir, distance, width, height, x, y)の引数の内(width, height, x, y)をグローバル変数にして、
change_point(dir, distance)と二つの引数だけに変更しました。おかげでテストする際も、

assert cahnge_point("RIGHT", 4, 8, 6, 2 ,1)==2 となって読み難かったものが、
assert cahnge_point("RIGHT", 4)==2 と読みやすくなりました。(グローバル変数は別途指定)

しかし、その後下記コードを別のテストファイルにimportしてテストをしようとすると、名前の衝突?のような問題があり、上手くテストが動きませんでした。このようなトラブルがあるので、手元のPythonの入門書にはグローバル変数は安易に使うなと説明されていました。

グローバル変数の使い方に気を付けていれば問題ないのかもしれませんが、引数を減らすもっと良い方法はないでしょうか?
あるいは冗長になってしまっても、関数中で使う変数については、引数として明示的に受け取るべきなのでしょうか?

###実際のコード

1番目の入力として、width, height, x座標, y座標、
2番目の入力として、移動方向(UP,DOWN,RIGHT,LEFT)と移動量(-10~10)が与えられた際に、
移動後の座標を計算するコードを書きました。

change_point() 関数は2番目の入力を与えられた際に、移動後の座標を計算する関数です。

移動後の座標を計算するためには、移動方向(UP,DOWN,RIGHT,LEFT)と移動量(-10~10)に加え、
width, height, x座標, y座標が必要なので、それらを全て引数とすると、
change_point(dir, distance, width, height, x, y) と引数が非常に長くなってしまいます。

そこで(width, height, x, y)をグローバル変数にして、change_point(dir, distance) は引数を二つに省略しました。

###該当のソースコード
loop_point関数のコードは省略していますが、widthやheightを超えた移動について、0から数えなおす関数です。

def change_point(Dir,Distance): # ポイントを変更する関数 global height, width, x, y if Dir =="UP": y = y + Distance y = loop_point(y, height) # loop_point()はwidth,heightを超えたx,yを規定値に修正 return y if Dir =="DOWN": y = y - Distance y = loop_point(y, height) return y if Dir =="RIGHT": x = x + Distance x = loop_point(x, width) return x if Dir =="LEFT": x = x - Distance x = loop_point(x, width) return x def main(): global width, height, x, y width, height, x, y = [int(x) for x in input().split()] Dir, Distance = input().split() Distance = int(Distance) change_point(Dir, Distance) print(x, y) if __name__ == '__main__': main()

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

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

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

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

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

guest

回答4

0

ベストアンサー

いくつもの変数を複数の関数から扱う場合、クラス(オブジェクト)を使うことをおすすめします。
たとえばこんな感じです。

python

1class Point(object): 2 def __init__(self, width, height, x, y): 3 """初期化メソッド.""" 4 self.width = width 5 self.height = height 6 self.x = x 7 self.y = y 8 9 def limit_range(self): 10 """x,y を規定値に修正.""" 11 if self.x > self.width: 12 self.x = self.width 13 elif self.x < 0: 14 self.x = 0 15 if self.y > self.height: 16 self.y = self.height 17 elif self.y < 0: 18 self.y = 0 19 20 def move_up(self, distance): 21 """UP方向に移動させるメソッド.""" 22 self.y += distance 23 self.limit_range() 24 25 def move_down(self, distance): 26 """DOWN方向に移動させるメソッド.""" 27 self.y -= distance 28 self.limit_range() 29 30 def move_right(self, distance): 31 """RIGHT方向に移動させるメソッド.""" 32 self.x += distance 33 self.limit_range() 34 35 def move_left(self, distance): 36 """LEFT方向に移動させるメソッド.""" 37 self.x -= distance 38 self.limit_range() 39 40 41if __name__ == '__main__': 42 width, height, x, y = [int(x) for x in input().split()] 43 point = Point(width, height, x, y) 44 Dir, distance = input().split() 45 distance = int(distance) 46 if Dir == "UP": 47 point.move_up(distance) 48 if Dir == "DOWN": 49 point.move_down(distance) 50 if Dir == "RIGHT": 51 point.move_right(distance) 52 if Dir == "LEFT": 53 point.move_left(distance) 54 print(point.x, point.y)

これは "Point" というクラスの中に、width, height, x, y といった変数(インスタンス変数)と、ポイントを移動させる関数(インスタンスメソッド)をセットで納めています。

  • グローバル変数: どこでも使える。それゆえに衝突などのトラブルの原因に...
  • ローカル変数: その関数内だけで使える。なのでほかの関数に引数としていちいち渡さないといけない
  • インスタンス変数: そのクラス(インスタンス)内だけで使える。なので同クラス内の関数同士で共有できる。でもグローバルからは見えない

と、インスタンス変数は「必要な範囲で使い回せる」というグローバル変数やローカル変数の間を取った絶妙な特性を持っています。

またクラスを使うことで機能の拡張も簡単にできます。例えば、x,y を反転させるメソッドを追加するなら、こんなコードですみます。

python

1 def reverse_xy(self): 2 """x, yを反転させるメソッド.""" 3 self.x, self.y = self.y, self.x 4 self.limit_range()

また、もし複数のポイントを同時に扱わないといけなくなった場合、元のコードだと処理が非常に複雑になってしまいますが、クラスを使うことでデータを呼び出し元(main)で管理しなくてよいので簡単に実現できます。

python

1point1 = Point(width=640, height=480, x=10, y=20) 2point2 = Point(width=640, height=480, x=100, y=50) 3point3 = Point(width=640, height=480, x=0, y=0) 4 5point1.move_up(100) 6point2.move_left(-10) 7point3.move_down(50) 8 9print(point1.x, point1.y) 10print(point2.x, point2.y) 11print(point3.x, point3.y)

とても便利ですので、ぜひ学習してみてください。

投稿2017/05/04 14:56

編集2017/05/04 15:27
miyahan

総合スコア3095

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

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

退会済みユーザー

退会済みユーザー

2017/05/04 15:29

とても分かり易くご説明頂きありがとうございました。 グローバル変数やクロージャという言葉にとらわれていましたが、もっと相応しい方法があったのですね。目から鱗が落ちた思いです。 コード付きで丁寧にご説明頂いたおかげで、大変勉強になりました。 今後クラスを上手に扱えるように練習してみます。
guest

0

関数内でのグローバル変数の使用は最終手段です。安易にグローバル変数を使うと保守性が著しく低下し、バグの温床となります。グローバル変数を使う以外に手段がないという場合を除いて、極力避けるべきです。

widthとheightが固定であり、xとyが変化していきながら、方向と距離を次々と与えていくというのであれば、Pointというそれらを管理するクラスを作って、その中でwidthとheghitを保持、xとyを変化させた方が良いと思います。

Python

1# coding: utf-8 2class Point: 3 """ポインタを表すクラス""" 4 def __init__(self, width, height, x, y): 5 self.width = width 6 self.height = height 7 self.x = x 8 self.y = y 9 10 def change(self, direction, distance): 11 """ポイントを変更するメソッド""" 12 if direction == 'RIGHT': 13 self.x += distance 14 elif direction == 'LEFT': 15 self.x -= distance 16 elif direction == 'UP': 17 self.y += distance 18 elif direction == 'DOWN': 19 self.y -= distance 20 self.loop() 21 22 def loop(self): 23 """一周回って補正するメソッド、実際どうすべきかは知らない""" 24 self.x = ((self.x + self.width) % (2 * self.width)) - self.width 25 self.y = ((self.y + self.height) % (2 * self.height)) - self.height 26 27 28def main(): 29 width, height, x, y = [int(x) for x in input().split()] 30 point = Point(width, height, x, y) 31 direction, distance_str = input().split() 32 point.change(direction, int(distance_str)) 33 print(point.x, point.y) 34 35 36if __name__ == '__main__': 37 main()

このようにすることで次のような利点が得られます。

  • 同時に複数のPointを扱えます。A点とB点、それぞれ与えられて、それぞれ移動すると言った場合でも、独立したPointオブジェクトを作れば独立して処理できます。
  • 変更のメソッド呼び出しは方向と距離だけ渡すことになります。xやyは変更のメソッドによって自動的に変わっていきますので、複数の呼び出しにも対応しています。

参考されている記事の「グローバル変数の宣言をなるべく減らしたい場合」【利用場面1】についてですが、これはES5以前のJavaScriptにおける話だと思われます。

ES5以前のJavaScriptでは、トップレベルのローカル変数やグローバル変数(この二つは厳密には異なる)は読み込まれた全てのJavaScriptファイルに影響を及ぼすため、バグの温床になりやすい物です。それを防ぐために、とりあえず即時関数で囲むという手段がとられていました(ES2015以降は別方法があるので、必要性はなくなりました)。

Pythonのグローバル変数はファイルスコープであり、別のファイルに影響を及ぼすことはありません。そのため、単にスコープを狭くしたい場合にクロージャーを使うことは意味がありません。

むしろ、参考にすべきは【利用場面2】と【利用場面3】です。そして、この二つの利用方法に本質的に同じです。クラス版を高階関数版に書き換えると次のようになります(処理の内容は実質同じです)。

Python

1# coding: utf-8 2def create_change_point(width, height, x, y): 3 """ポインタを変更していく関数を作成する関数""" 4 5 def change(direction, distance): 6 """ 7 ポイントを変更する関数 8 x, yはクロージャーによって束縛されてる。 9 """ 10 nonlocal x, y 11 if direction == 'RIGHT': 12 x += distance 13 elif direction == 'LEFT': 14 x -= distance 15 elif direction == 'UP': 16 y += distance 17 elif direction == 'DOWN': 18 y -= distance 19 loop() 20 return (x, y) 21 22 def loop(): 23 """ 24 一周回って補正するメソッド 25 width, hight, x, yはクロージャーによって束縛されてる。 26 """ 27 nonlocal x, y 28 x = ((x + width) % (2 * width)) - width 29 y = ((y + height) % (2 * height)) - height 30 31 return change 32 33 34def main(): 35 width, height, x, y = [int(x) for x in input().split()] 36 change_point = create_change_point(width, height, x, y) 37 direction, distance_str = input().split() 38 (x, y) = change_point(direction, int(distance_str)) 39 print(x, y) 40 41 42if __name__ == '__main__': 43 main()

なお、クロージャーの利用方法の重要な物の一つとして、map等の関数を受け取る関数に対して、渡す関数をラムダ式等で記述して、その中でローカル変数を使用できるというものがあります(Javaのクロージャーは制限があるため、この方法ぐらいにしか使用できません)。その例が無い時点で、該当の記事は欠陥がある記事とは言えません。(記事の作者は他サイトの記事を適当に引用しているだけで、クロージャーを理解しているとは思えません。自分で考えたクロージャーのコードが1行も書いていない時点で、察することができるレベルです。蛇足としか思えない【参考】のコードはクロージャーとは関係無いばかりか、無限に呼び出せない時点で同等でも何でもありません。参考にはしない方がいいと思います。)

投稿2017/05/04 15:52

raccy

総合スコア21733

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

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

raccy

2017/05/04 15:54

蛇足を書いたら、投稿が出遅れた…。
退会済みユーザー

退会済みユーザー

2017/05/04 16:36

raccyさん、いつも詳しくご説明頂きありがとうございます。 手元の入門Python3ではクロージャーの利用例が少しだけしかなかったので、今回クロージャーを使った場合の実際のコードで説明して頂きとても勉強になりました。 nonlocalの使用方法等についても、覚えておきたいと思います。ありがとうございました。
guest

0

  1. 入力パラメータを配列もしくはオブジェクトにする

  2. 関数の機能が過剰と思われるので分割する

あたりでしょか。
このあたりのさじ加減はなかなか難しいので
よそのソースを参考にするなど…
--- 追記 ---
オブジェクトの利用に関しては他の方の回答をみていただくとして…

そのクラスが「何を扱うか」を常に意識しておかないと
ごちゃごちゃで分かりにくくて使いにくいものになります。
例えばこんな感じのはどうでしょう

Python

1class Gamen: 2 def __init__(self, w, h): 3 self.width = w 4 self.height = h 5 def loop_point(self, p): 6 # 位置を補正 7 # Pointオブジェクトを返す 8 9class Point: 10 def __init__(self, x, y): 11 self.x = x 12 self.y = y 13 def up(self, d): 14 self.y = self.y + d 15 def down(self, d): 16 self.y = self.y - d 17 def right(self, d): 18 self.x = self.x + d 19 def left(self, d): 20 self.x = self.x - d 21 22class Sousa: 23 def __init__(self, s, d): 24 self.dir = s 25 self.distance = d 26 27def change_point(g, p, s): 28 if s.dir == "UP": 29 p.up(s.distance) 30 if s.dir == "DOWN": 31 p.down(s.distance) 32 if s.dir == "RIGHT": 33 p.right(s.distance) 34 if s.dir == "LEFT": 35 p.left(s.distance) 36 r = g.loop_point(p) 37 return r 38 39if __name__ == '__main__': 40 width, height, x, y = [int(x) for x in input().split()] 41 Dir, Distance = input().split() 42 Distance = int(Distance) 43 g = Gamen(width, height) 44 p = Point(x, y) 45 s = Sousa(Dir, Distance) 46 p = change_point(g, p, s) 47 print(p.x, p.y)

投稿2017/05/04 14:12

編集2017/05/04 17:32
takasima20

総合スコア7458

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

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

退会済みユーザー

退会済みユーザー

2017/05/04 15:21

コメント頂きありがとうございます。 > 入力パラメータを配列もしくはオブジェクトにする 入力パラメータをオブジェクトにするという部分が難しくて理解できないのですが、例えばテキストファイルにパラメータを書いて、ファイルオブジェクトとして受け取るという事でしょうか?
退会済みユーザー

退会済みユーザー

2017/05/07 16:18

追加でのコメントありがとうございます。 今までオブジェクト指向的な考え方ができていなかったのでとても参考になりました。
guest

0

関数の引数を少なくしたいのは見た目的な問題なのか
位置引数を覚えなければいけないから少なくしたいのかどちらかわかりませんが
後者であればデフォルト値を最初に決めておけばいいかと思います

python

1x=5 2 3def python(x,y=10,z=20): 4 return x*y*z 5 6print(python(x)) 7

height, width, x, yも同じようにデフォルト値を決めておけばどうでしょうか

投稿2017/05/04 14:12

退会済みユーザー

退会済みユーザー

総合スコア0

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

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

退会済みユーザー

退会済みユーザー

2017/05/04 15:16

引数を少なくしたいと思ったきっかけはテストの際に引数が多く、扱い難く読み難かったことです。 ですから、見た目の問題と位置引数を覚えなければならない問題の両方が関係していると思います。 コメントありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問