Local変数とGlobal変数
まず言いたいのは、関数内で呼び出す変数は関数内でのみ有効だことが基本である。関数内で定義した変数はその関数を抜けたら無効になる。関数の中では関数内で定義した変数(=Local変数)しか扱えず(原則)、特に何も考慮することがなければ、こちらの使用が推奨される。→ Global変数を使用するとバグや気を使わねばならない状況が多発するため。しかし、関数の外でのglobal変数の使用は今後絶対に変えない変数を定義したい場合(円周率:PI=3.1415とする時など) には非常に有用なので状況に応じて使い分けることが大切。Pythonでは誤ってglobal変数を呼んだり編集したりすることのないよう、通例としてglobal変数は大文字とアンダースコアの組み合わせで定義する。
OSHI = "私の推しは" #このOSHIはglobal scopeにいる
def sample1():
OSHI += "リヴァイ兵長です" #このOSHIはlocal scopeにいる
return OSHI
print(sample1())
#出力内容
UnboundLocalError: local variable 'OSHI' referenced before assignment
上記のように関数外で定義したグローバル変数と同じ変数を関数内で用いようとしても、scopeが異なるので参照されず、それは既出の変数名だよーと跳ね返されてしまう。やりたかったこととしては「私の推しはリヴァイ兵長です」という文を出力すること。
1 i = 50 #このiはglobal scope
2 def foo():
3 i = 100 #このiはlocal scope
4 return i
6 foo() #global scopeは見ておらず、local scopeだけ見ている
7 print(i) #foo()関数内のlocal変数を出力
上記はUnboundLocalErrorになりそうだが、ならない。つまり、同じ変数名であろうと参照(アクセス)させなければ問題ないということである。同じ名前であろうとL1のiとL4のiは全く別物の変数である。
Global変数をLocal scope内で変更したい場合
しかし、それでも関数外で定義した変数(global変数)を使用したい場合もある。
解決策1: Global文 (非推奨)
その際は、関数内で関数外の変数を持ってくるよ!というglobal文を記述しなければならない。そうすることで、それ以降に出てくる該当グローバル変数がすべて編集後のグローバル変数を参照するようになる。つまり、同じ関数内で同じ名前を持つ変数が複数出てきた場合、1回目はローカル変数で、2回目以降はグローバル変数を用いることは可能だが、1回目にグローバル変数を用い、2回目はローカル変数にしたい!ということはできない。おそらく定義する際の名前を変えるしかないし、そもそもそういう設計にすること自体が間違っていると思う。こちらのサイトが分かりやすかった→ https://uxmilk.jp/12505
そんなこんなで、このGlobal文ははあまり使いたくない。そもそも、Global変数はあらゆる箇所で用いられるため、共通認識として設定しておこうね~という意図のもとGlobal変数として記述されているものなので、これをとあるLocal変数の中で使いたいからと言って変更してしまっては、それ以降のGlobal変数が全て値に変更が生じてバグを生産する要因となってしまう。
OSHI = "私の推しは" #ここのoshiはglobal scopeにいる
def sample1():
global OSHI #改変を加えたいglobal変数はここで宣言する
OSHI += "リヴァイ兵長" #ここのOSHIはglobal scopeからやってきたもの
return OSHI #新しくglobal scopeのOSHIが誕生!
def sample2():
return OSHI + "アルミンです" #ここのOSHIはglobal変数のoshi
print(sample1())
print(sample2())
#出力内容
私の推しはリヴァイ兵長です
私の推しはリヴァイ兵長ですアルミンです
上記のように新しいglobal変数として oshi="私の推しは リヴァイ兵長です" が定義されたが、そうすると、仮に他の関数sample2()などで推しの名前をアルミンにしたいとき、最初のglobal変数はもう使えなくなっているためバグの原因になってしまう。
解決策2:return文で調整する
OSHI = "私の推しは"
def sample1():
return OSHI + "リヴァイ兵長です"
def sample2():
return OSHI + "アルミンです"
print(sample1())
print(sample2())
#出力内容
私の推しはリヴァイ兵長です
私の推しはアルミンです
上記のようにreturn文に「+ "リヴァイ兵長"」を加えることで、Global変数「OSHI」に手を加えずともLocal scope内で値を変更することができ、それ以降の関数にも影響を及ぼさないためバグにならない。
Mission>> Guess Number Game
<To do list>
★静的要素
・数字の選択範囲
・answerの値
★動的要素
・ゲーム開始
・モード選択
easy modeなら試行回数は10回
hard modeなら試行回数は5回
どちらも選択しなかった場合はInvalid Inputとしてモード選択から再度行わせる
→難しいなら、デフォルトはeasyにしておくか?
・数字をguessさせる
・answer = guess → Correct
残りの試行回数を示し、ゲーム終了
answer < guess → Too high
answer > guess → Too low
残りの施行可能回数を表示し、再度guessさせる
・新規ゲーム自体を始めるかやめるか聞く
yes → ゲーム開始
no → ゲーム終了
ここで、アスキーアートを自動生成してくれるソフトウェアサイトがあるので紹介。今まではこれを使用していたんだなー→ http://patorjk.com/software/taag/#p=display&v=0&f=Doom&t=Type%20Something%20
以下guess1.pyとguess2.pyは失敗作。完成品は guess3.py
import random
a = 0
b = 100
scope = list(range(a,b,1))
def play():
answer = random.choice(scope)
print(f"\nGuess a number between {a} to {b}")
print(answer)
mode()
def mode():
mode = input("Select mode [easy/hard]? ")
if mode == "easy":
times = 7
elif mode == "hard":
times = 5
else:
print("Invalid Input...Try again!!")
play()
print(f"You have {times} attemps remaining to the guiess the number")
play()
これだと、数回Invalidしたあと、hardだの入力すると、timesがUnboundErrorになる。→scopeの問題を解決せよ!
なかなかアルゴリズムを設計するのがむずかしい...。ということでフローチャートを書いてみた。
import random
from replit import clear
a = 0
b = 100
scope = list(range(a,b,1))
answer = int(random.choice(scope))
is_game_over = False
def play():
clear()
print(f"Welcome to Guess The Number Game!!\nSelect a number from {a} to {b}.")
print(answer)
def mode(): #mode()は完璧
"""モード選択し、試行回数を返す"""
if which == "e":
times = 10
print(f"You have {times} attemps.")
return times #return1-1
else: #easy以外はhardとみなすことにした
times = 5
print(f"You have {times} attemps.")
return times #return1-2
#else:
# print("Invalid Input...Try again!!")
# mode()
# return num
def attempt():
"""残り試行回数を返す"""
times = int(mode()) - 1 #mode()のreturnのtimesから1減らして試行回数を定義
print(f"Remaining number of attemps: {times}")
times -= 1
return times
def judge():
"""答えと予想値を比較し、表示内容を返す"""
guess = int(input("Guess the number: "))
last_times = int(attempt())
last_times -= 1
print(last_times)
#attempt()のreturnで残り試行回数をint型でlast_timesに定義
if guess == answer:
print("\nCorrect!! You've got the number.")
elif guess < answer:
print("\nIt's too low.")
judge()
if last_times == 0:
print("\nGAME OVER")
else:
judge()
elif guess > answer:
print("\nIt's too high.")
last_times -= 1
judge()
if last_times == 0:
print("\nGAME OVER")
def restart():
"""もう一度ゲームを行うか問う"""
restart = input("Restart?? [y/n] ")
if not restart == "n":
play()
which = input("Select mode [easy/hard]? ")
judge()
else:
print("Bye...")
play()
which = input("Select mode [easy/hard]? ")
judge()
restart()
これだと、回数制限が無効になっている。あと一息。
import random
from replit import clear
a = 0
b = 100
scope = list(range(a,b,1))
answer = int(random.choice(scope))
def mode(): #mode()は完璧。
"""モード選択し、試行回数を返す"""
#ここにwhich入れたらダメだった
if which == "e":
times = 10
print(f"You have {times} attemps.")
return times #return1-1
else: #easy以外はhardとみなすことにした
times = 5
print(f"You have {times} attemps.")
return times
def play():
clear()
print(f"Welcome to Guess The Number Game!!\nSelect a number from {a} to {b}.")
print(answer)
def judge():
"""答えと予想値を比較し、表示内容を返す"""
guess = int(input("Guess the number: "))
if guess == answer:
print("\nCorrect!! You've got the number.")
elif guess < answer:
print("\nIt's too low.")
if times == 0:
print("\nGAME OVER")
elif guess > answer:
print("\nIt's too high.")
#times -= 1
if times == 0:
print("-----------\n|GAME OVER|\n-----------")
def restart():
"""もう一度ゲームを行うか問う"""
restart = input("Restart?? [y/n] ")
if not restart == "n":
play()
which = input("Select mode [easy/hard]? ")
global times
times = mode()
else:
print("Bye...")
play()
which = input("Select mode [easy/hard]? ")
times = mode()
for i in range(0,times):
if times != 0:
print(f"\nRemaining number of attempd: {times}")
times -= 1
judge()
else:
print("-----------\n|GAME OVER|\n-----------")
break
restart()
これだと、restart以降でjudgeが動かなくなる。
完成品
import random
from replit import clear
a = 0
b = 100
scope = list(range(a,b,1))
def play():
print(f"Welcome to Guess The Number Program!!\nGuess a number from {a} to {b}")
goal = random.choice(scope)
times = getTimes()
for i in range(times,0,-1):
print(f"\nremains {i} times")
answer = int(input("\nGuess the number: "))
if answer < goal:
print("too small...")
elif answer > goal:
print("too large...")
elif answer == goal:
print("HIT!!")
return
print(f"Game over\nAnswer is {goal}")
def getTimes():
"""モード選択し、試行回数を返す"""
mode = input("Select mode [e/h] ")
if mode == "e":
print(f"You have 10 attemps.")
return 10
else:
print(f"You have 5 attemps.")
return 5
while True:
play()
restart = input("\nRestart?? [y/n] ")
if restart == "n":
print("bye...")
break
#出力結果
Welcome to Guess The Number Program!!\nGuess a number from 0 to 100
Select mode [e/h] h
You have 5 attemps.
remains 5 times
Guess the number: 56
too large
remains 4 times
Guess the number: 43
too large
remains 3 times
Guess the number: 22
too large
remains 2 times
Guess the number: 10
too small
remains 1 times
Guess the number: 15
OK #ビンゴ!最後で当たったww
Restart?? [y/n] n
bye...
かなりすっきりした。
guess1.py & guess2.pyのどこがダメだったのか、今一度復習してみる。
・まず、大枠のフレームワークが書けていない。
→ これがコーディングする上で最も重要だが、初学者の自分には難しかった。フレームチャート型は詳細設計においてまとめるには有効だが、大枠を考えるのに適していないとの結論に至った。Function型という機能単位で大枠を掴む方法の方が全体を考える上で分かりやすい。これについてはまた今度追記する。
・Booleanの使い方
・for i in range(start,goal,増減)
・for/while から抜ける→ break
・def something(): から抜ける → return
・関数の命名規則:「動詞_目的語(何が返ってくるのか)」
・1つの関数の分量はPC画面に収まる程度(今後適用したい独自ルール)
・慣れないうちは1つの関数は1つの返り値を返す(単機能)にした方が良い
★ある関数内で定義した変数を他の関数で呼び出したい
共有する変数はそれぞれの関数の外側で定義するべき。その上で、同じ変数を変更する方法
1. 引数で渡す
2. クラスのメンバー変数にする
3. グローバル変数として扱う
のどれかになると思います。
[参照] https://teratail.com/questions/139976
まぁだから結論として、関数Aの返り値returnに再利用したい変数を書いて、関数Aの外で関数Aを呼び出してそのreturnを変数として再定義することで利用が可能となる。
もう一つサンプルを残しておく。
def test():
a = "omOTeNaShi"
b = "JApaN"
return a,b
def test2():
c,d = test() #呼び出したtest()はa,bをreturnするので、それぞれをc,dと定義する
e = c.title()
d = d.title()
return e,d
#test()関数の中の変数a,bを使いたいので、test2()関数の中でローカル変数として定義し、そこから
#持ってくると他の関数の変数の中身を使うことができる。
#変に中身をいじらなければ実体は同じだが、変数名だけ変えたということになる。これがからくり。
print(test2()) #test2でreturnしたe,dをprintしている
#出力内容
Omotenashi
Japan
以上を見た通り、
プロセス指向は、1個または複数の関数を作って、全てのオブジェクトに対して関数を適用すれば実現できます が、 このような処理が多くなるとプロセス指向は、たくさんの関数が定義され、関数の中に関数呼び出ししている可能性もあり、構造がどんどん不明瞭になります。そして、あるオブジェクトが来たら、関数で処理できるかどうかは中身を見ないと分からない場合もあります。
そこで、考え出されたのがオブジェクト指向!
早く全体の構成を考えないといけないわけだが、いろいろ苦労をし、どうやらそれにはオブジェクト指向とclassという概念とその書き方をマスターしなければいけないということになった。
オブジェクト指向は、classとそのclassのメソッドを定義し、全てのオブジェクトに対して、classのインスタンスを作り、そのインスタンスからメソッドを呼び出します。オブジェクト指向は、class単位でメソッドをまとめて管理し、オブジェクトの使えるメソッドは自明です。
[引用(整序改変)] https://qiita.com/kaitolucifer/items/926ed9bc08426ad8e835
オブジェクト指向とは?
例えば、役者50人が出る舞台『アニー』では、脚本にいつだれがどんな性格でどんな行動をして...といったことを、その役者が登場するタイミングでその都度書くと煩雑で脚本自体読みにくくなってしまう。だったら先に役ごとにそれぞれの性質(貴族/女/30代...etc)やスキル(行動/仕草)をまとめて記述しておいた方が脚本が頭に入りやすいでしょ?という話。まるでミュージカルのようなイメージ。処理が複雑になるにつれて従来の上から下まで時系列でコードするのに無理が生じるようになったため?、考え出された概念。
詳細はこちらに分かりやすく書いてくださっている方々がいるので参考にどうぞ
1. https://qiita.com/kaitolucifer/items/926ed9bc08426ad8e835
2. https://qiita.com/Usek/items/a206b8e49c02f756d636
佐々木さんもいつもありがとうございますm(__)m
3. https://wa3.i-3-i.info/word1898.html
★上記1のサイトで参考になる部分を抜粋
1 class Bird:
2 def __init__(self, name): #通例で書いてあるだけで、selfなしでも問題ない。
3 self.name = name
4
5 def move(self):
6 print("The bird named {} is flying".format(self.name))
7
8 bob = Bird("Bob")
9 bob.move()
#出力結果
The bird named Bob is flying
トレースすると以下のような感じ
① L8で変数bobはクラスBirdを呼び出し、引数に"Bob"を指定する
② L9でL8で指定した変数bobについてmove()メソッドを発動!bobはクラスBirdに引数"Bob"を引き渡す変数だったので、実質クラスBird()の中のmoveメソッド(=move関数)を発動し、その引数に"Bob"を渡している
③ L2で__init__はコントラクト(後述する)で、クラスのオブジェクトが扱うデータの初期化をするので、クラスBirdが参照された時点から②が発動される前の間でその引数nameをself.nameに置き換えている(新しい変数名として定義)
④ ②で渡された引数"Bob"は③により、self.nameとして、move()へ渡され、L6 print文の最後.format(self.name)のself.nameに渡される。
結論としては、「クラスBirdのmove()メソッドの引数にself.nameという変数名になった"Bob"が引き渡された」という話。
上記を見ると、結局やっていることは関数の中にある引数や変数に、関数呼び出し時に関数外で新たに定義した変数を渡すことと同じ。しかし、コードする際により分かりやすく、見やすく、把握しやすくするように発案されたのがclassによるオブジェクト指向なんだろうなーと納得した。以下引用にもこのレベルならプロセス志向でも同じことができるけど、オブジェクト指向を使った方が分かりやすくなる理由が書かれている。
実際のプログラムではmoveだけではなく、数十種類の処理関数を実装するのが普通です。関数が多くなると、変数との対応関係を明確にするのが極めて難しくなります。また、これらの関数は内部で他の関数を呼び出している可能性もあり、この関数を他のプログラムで再利用する時に、内部で使われている関数も全部見つけ出して、移行する必要があります。オブジェクト指向は変数を使って、classからインスタンスを作成し、どのメソッドが使えるかはclassを見れば分かります。そして、classとして抽象化することで、同じ文脈の関数が固まり、管理しやすくなります。=インターフェースの統一
★上記2のサイトで参考になる部分を抜粋
コンストラクタについて次のように述べられているのを発見!
オブジェクト生成時に呼び出される特殊な関数をコンストラクタと言います。オブジェクトが扱うデータの初期化などをここで行います。コンストラクタは__init__()という名前で定義します。"_"を前後に2つです。注意してください。
<今後拡張したい機能>
何セットのトランプを使用するか設定できる
何人の客と対戦するか設定できる
Mission>>Black Jack Game Retry!!
前回はいろいろ構成を考えるのに苦労したが、どうやらclassを用いたオブジェクト指向を学ばないとコードが煩雑になり、うまく書けないという結論に至った。(先に誰か言ってくれYO!!) 理由としては、プロセス志向だと、関数の呼び出しにあたり関数からの関数の呼び出し(ネスト関数)やlocal変数,global変数の扱いを考慮しないといけないためである。ということで前回作成したBlackjackプログラムをday12で習ったことをふまえ、オブジェクト指向でコードしてみよう!というチャレンジ。
プレイヤーとディーラーの1対1カードゲームで、自分の他に客が何人いようとプレイヤー同士はゲームの中では干渉しない。引いたカードの合計で勝敗が決まる。プレイヤーが山札からカードを引くたびにカードが抜けていくので、重複したカードを引くことはない。
<用語の解説>
ブラックジャック="A"と"J","Q","K"いずれかで作られた2枚のこと
→ 合計が21になる3枚以上の組み合わせより強い手となる
ヒット=カードを1枚追加すること
スタンド=カードを追加せず、その場でやめること
※他に、サレンダー,ダブルダウン,スプリット等あるが、今回は割愛する
<基本的なルール> ※コーディング難易度調整のため一部改変
Rule1:1枚ずつカードを引いて、その和が21より小さいかつより近い方が勝者
Rule2:カードの和が21を超えた時点で負け
Rule3:ディーラーは自分の手の合計が[16]以下であった場合はヒット、[17]以上になった段階でスタンドするという機械的な動きをする
(Rule4: "J","Q","K"は10として扱い、"1"は都合に応じて1or11どちらかとして扱う。)
→ まずは1だけ特別扱いするのは難しいので、"1"は11として扱うことにする。
<勝ち負けが決まるのか詳細設計> メモ
You win!!→ 両者とも21未満であるかつYOU=21のとき, YOUが21未満かつPCがover21のとき
Dealer wins!!→ 両者とも21未満であるかつDEALER=21のとき, DEALERが21未満かつYOUがover21のとき
DRAW!! → 両者ともカードを追加したタイミングでover21になったとき
両者とも21未満かつ同値かつHit?でnoと答えたとき
→ まずはPlayer<Delaerになったとき勝利とする
下のコードは改修前最初に書いたもの↓↓
オブジェクト指向はおろか、関数のreturnに複数返すことができるとさえ知らなかった。かろうじてday10あたりで学んだdictの使い方を実践できるじゃん!とワクワク意気込んでいたレベル。最終的にオブジェクト指向で書いたものを見たい方はBJ1.pyとBJ2.pyをすっとばしてこちらへ→ 完成品
from art import logo
import random
cards = {1:11,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,10:10,"J":10,"Q":10,"K":10}
you = 0
pc = 0
def blackjack():
print(logo)
key1 = random.choice(list(cards.keys()))
#key2 = random.choice(list(cards.keys())) #最初2枚いっぺんに引いたけど、1枚ずつに変更
value1 = cards[key1]
#value2 = cards[key2]
print(f"Your cards: {key1}")
total = you + value1
return total
def PC_blackjack():
key3 = random.choice(list(cards.keys()))
value3 = cards[key3]
print(f"Computer cards: {key3}")
PC_total = pc + value3
return PC_total
def add():
key_add = random.choice(list(cards.keys()))
value_add = cards[key_add]
return key_add,value_add
total = blackjack() #上記関数でreturnした値をその名前の変数に格納(you)
PC_total = PC_blackjack() #上記関数でreturnした値をその名前の変数に格納(PC)
k,v = add() #add()だけ値が2つreturnされるので、各々変数に格納
pk,pv = add() #PCのkey,PCのvalueってことでpk,pv
deal = True
while deal:
if total == 21: #you=21
print("You win!!")
deal = False
elif total > 21 or PC_total == 21: #you>21 or PC=21
print("You lose...")
deal = False
elif total < 21 and PC_total < 21: #you<21 and PC<21
while not input("Another card? [yes/no] ") == "no": #no以外でインクリメント
total += int(v) #add newcard to you
PC_total += int(pv) #add newcard to PC
print(f"You added {k}, now your total: {total}")
print(f"Computer added {pk}, now PC total: {PC_total}")
if (total == 21 and PC_total < 21) or (total < 21 and PC_total > 21): #追加後
print("You win!!")
deal = False
break
elif (total < 21 and PC_total == 21) or (total > 21 and PC_total < 21):
print("PC wins!!")
deal = False
break
elif (total > 21 and PC_total > 21) or (total < 21 and PC_total < 21) and total == PC_total:
print("draw!!")
deal = False
break
else: #no選択し、インクリメントしない場合
sa = int(21-total) - int(21-PC_total) #int(-2)=2。絶対値にしてくれる
if sa < 0 or PC_total > 21: #sa="差"
print("You win!")
deal = False
break
elif sa == 0: #絶対値が同値だった場合
print("draw")
break
else:
print("PC wins...!!")
break
なんか長いwwし、場合分け醜くすぎる。今思い返すと、一体blackjack()という関数は何事!?という感じだが、思い返すと、自分的にはゲーム全てを内包できるgameオブジェクトとして作りたかったのだろう、と推察する。関数でつくろうと思っていた(だってそれしか知らなかったんだもn...)下も手直し後だが、相変わらず煩雑で美しくない。
なぜだろう(オブジェクト指向を知らないk...)
from art import logo
import random
cards = {1:11,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,10:10,"J":10,"Q":10,"K":10}
dealer = 0
you = 0
def DEALER():
print(logo)
key1 = random.choice(list(cards.keys()))
key2 = random.choice(list(cards.keys()))
value1 = cards[key1]
value2 = cards[key2]
print(f"Dealer cards: {key1}")
total = dealer + value1
return total
def YOU():
key3 = random.choice(list(cards.keys()))
value3 = cards[key3]
print(f"Your cards: {key3}")
PC_total = you + value3
return PC_total
#totalとPC_totalの入れ替えまだやっていない!
def HIT():
key_add = random.choice(list(cards.keys()))
value_add = cards[key_add]
return key_add,value_add
total = DEALER() #上記関数でreturnした値をその名前の変数に格納(you)
PC_total = YOU() #上記関数でreturnした値をその名前の変数に格納(PC)
k,v = HIT() #add()だけ値が2つreturnされるので、各々変数に格納
pk,pv = HIT() #PCのkey,PCのvalueってことでpk,pv
#↑ここ省略できないか?
deal = True
while deal:
if total == 21: #you=21
print("Dealer win!!")
deal = False
elif total > 21 or PC_total == 21: #you>21 or PC=21
print("You win!!")
deal = False
elif total < 21 and PC_total < 21: #you<21 and PC<21
while not input("HIT? [yes/no] ") == "no": #no以外でインクリメント
total += int(v) #add newcard to you
PC_total += int(pv) #add newcard to PC
print(f"Dealer added {k}, now dealer total: {total}")
print(f"You added {pk}, now your total: {PC_total}")
if (total == 21 and PC_total < 21) or (total < 21 and PC_total > 21): #追加後
print("Dealer wins!!")
deal = False
break
elif (total < 21 and PC_total == 21) or (total > 21 and PC_total < 21):
print("You win!!")
deal = False
break
elif (total > 21 and PC_total > 21) or (total < 21 and PC_total < 21) and total == PC_total:
print("DRAW!!")
deal = False
break
else: #no選択し、インクリメントしない場合
sa = int(21-total) - int(21-PC_total) #int(-2)=2。絶対値にしてくれる
if sa < 0 or PC_total > 21: #sa="差"
print("Dealer win!")
deal = False
break
elif sa == 0: #絶対値が同値だった場合
print("DRAW")
break
else:
print("You win...!!")
break
オブジェクト指向で書いたプログラム