• 締切済み

python:関数が複数globalを読むロジック

pythonの次のコードがあります #------------------------------------ #code default_name = "Ichiro" def kyodai1(name=default_name): return f"Hello {name}," def kyodai2(name=None): if name is None: name = default_name return f"Hello {name}." default_name = "Jiro" print(kyodai1(), kyodai2()) # 出力 # Hello Ichiro, Hello Jiro. #----------------------------------- kyodai2()関数では、 name = default_name とあり、default_nameは最初の行の"Ichiro"を呼ぶかと思ったら、関数の下にある"Jiro"を読みに行っています default_nameはglobal変数として default_name = "Ichiro" があるのに、def kyodai2()関数だけ default_name = "Jiro" を読みに行った理由がわからずにいます このロジックを3つのAIにコードを読ませて説明させたところ、うち1つが 「(kyodai2スコープ内で)default_nameが定義されていません。この場合、default_name = "Ichiro"ではなく、default_name = "Jiro"を参照します」 と答えました 質問: この説明が正しいかどうかもわからないのですが、なぜkyodai2()関数は、最初のIchiroではなくJiroを読みにいくのでしょうか? Tutolialで該当箇所がわからずにいます 【回答上のご注意】 回答は、解答(答え)を求めています わたしはプログラマーではないので、昭和的な「自分で考えろ」的なものは求めていません わからなければ答えない自由もあなたにはあります 不明点があれば説明いたします

  • ketae
  • お礼率85% (293/342)
  • Python
  • 回答数5
  • ありがとう数4

みんなの回答

回答No.5

う〜む、これはだな・・・・・・。 最初に言っておく。実は直感的な話を言うと # 出力 # Hello Jiro, Hello Jiro. になるのが「正しい」。 繰り返すが、「マトモなレキシカルスコープを持つ」プログラミング言語だと全部同様の結果になる。 // JavaScriptの例 default_name = "Ichiro"; function kyodai1(name = default_name) { return `Hello ${name},`; } function kyodai2(name = null) { if (name === null) { name = default_name; } return `Hello ${name}.`; } default_name = "Jiro"; console.log(`${kyodai1()} ${kyodai2()}`); これも出力はJiro Jiroだ。 いくらファイルの冒頭でdefault_name = "Ichiro";と定義されていても実行直前でdefault_nameが"Jiro"と書き換えられている以上、実行時に参照するdefault_nameは"Jiro"になる。 他のレキシカルスコープ採用の(しかもキーワード引数アリの)言語で試しても同じ結果になるだろう。 レキシカルスコープは別名「字句的スコープ」と言う。「書いた通りにスコープが決定する」と言う意味だ。 つまり、他のスコーピングのシステムに比べると「悩む必要がない」設計になっている。直感的に判断が可能だ。 一方、Pythonは「一応」レキシカルスコープ採用、って言って良い言語だが、実はその実装は極めて不完全なんだ。 んで、だ。 どこからこの問題を持ってきた? この問題は、普遍的なレキシカルスコープの挙動を問うてる問題じゃなくって、実の事を言うと「Pythonのバグ地味た挙動」を問うてる問題なんだ。 問題は、Pythonのスコープの問題ではなくって、「Pythonの関数定義に於いてのキーワード引数の挙動の不審さ」の問題なんだ。 従って、 > Tutolialで該当箇所がわからずにいます はこれ、だ。 4.8.1. デフォルトの引数値: https://docs.python.org/ja/3/tutorial/controlflow.html#default-argument-values つまり、kyodai1の定義時にdefault_nameが"Ichiro"だった為、一旦それを評価した以上「そこから変わらない」と言うバグ地味た挙動が他の「マトモなレキシカルスコープを持った言語」と違う挙動をする原因になってる(これは全然何も得がない、んだ)。 そして、「こういうスタイルでPythonでプログラムしたらダメ」なんだよ。 ダメなプログラムを出して、つまり、重箱の隅をつつくような出題をする、ってのがPythonの「資格試験」の特徴なんだけど、まさしくそのテの問題だと思う。 Pythonにキーワード引数に大域変数で定義したデフォルト値を与えちゃいけない、んだ。この例のように「思わぬ挙動を引き起こす」場合がある、ってこった。 (もっとも「Pythonの危険性を知る」にはいい問題かもしれんが・・・・・・苦笑) プログラミング言語に於いては、この、Pythonのように「実装者のポカ」が影響を与える場合がある。 従って、 > このロジックを3つのAIにコードを読ませて説明させた 極論、「ロジック」じゃねぇんだよ。「実装者がそういう実装をしちまった」以上に理由がない場合がしばしばある。 従って、「資格試験対策」を考えるなら、「ロジックを考える」んじゃなくって「暗記対策」しかあり得ないわけだ。 「重箱の隅をつつく」問題が出る以上、そこには「普遍的な(レキシカルスコープ採用言語に於いての)プログラミングに於いてのお約束」を問うてるわけじゃないんで、当然「暗記での対策」になっちまうわけだ。 Pythonは比較的、「どこを取っても論理的で整然としてる」言語じゃないんだ(もっとも言っちゃうと、かなりの確率でどの言語も「理論的にはどっか破綻してはいるが・・・「人が設計してる以上しょうがない」が)。

ketae
質問者

お礼

実際AIにコードを読ませたあと、実行させると Hello Jiro, Hello Jiro. と出したのがあったので、pythonではない言語の振る舞いではそうなるかなと昨日思っていました 同時にAIはglobal変数の多用は勧めないともいうので、これは問題集上の問題だなと思います ありがとうございます

  • chie65535
  • ベストアンサー率43% (8520/19368)
回答No.4

>のような流れをイメージしました。間違っていたらご指摘ください。 間違っています。 >print(kyodai2()) #最終行で > ↓ 関数kyodai2に飛ぶ >def kyodai2(name=None): > if name is None: > name = default_name > #default_nameがはどこだ?上にglobalの"Ichiro"あるけど、他も探してみよう default_nameは探しません。 default_nameはもう既に「定義済み」で「値が代入済み」なので探す必要はありません。 メモリ内の「グローバル変数一覧」に存在していて「このグローバル変数の現在の値はJiro」と、一覧に記載されています。 default_nameには「一番最後に代入されていた値」が読みだされます。 代入文が「どこにあるか?」は関係ありません。上にあろうが、下にあろうが、関係ありません。重要なのは「一番最後に行われた代入が、どれなのか?」です。 そして、一番最後に行われた代入で何が代入されたかは「グローバル変数一覧」の「このグローバル変数の現在の値」に書かれています。 > return f"Hello {name}." > ↓ >default_name = "Jiro" #あっ、下に"Jiro"があった 繰り返しますが、上にあろうが、下にあろうが、関係ありません。「どれが一番最近に行われた代入なのか」が重要です。 で、今回は「一番最近に行われたのはJiroの代入」だった訳です。

ketae
質問者

お礼

はい。 「一番最近に行われた代入」を理解するため、 default_name = "Jiro" の行を撤去すると、"Ichiro, Ichiro" なのでそこは理解できていると思います。 最初のコードにもどると、この質問の主旨はなぜ def kyodai2(): のスコープの中で、default_name への代入がない しかしglobalで default_name = "Jiro" があった では、なぜdef kyodai1()で「一番最近に行われた代入」であるはずの、Jiroが用いられないのかというのが疑問でした def kyodai1()では、関数内で def kyodai1(name=default_name): と代入が起こってしまっている(既済) def kyodai2(): のスコープ内ではdefault_nameはglobalで「一番最近に行われた代入」であるはずのJiroを出力する global変数に大して「一番最近に行われた代入」=Jiroであるが、def kyodai1()関数の中で、先行してname=default_nameの代入が起こっており、name = "Ichiro"となった (先ほどまでdefault_nameを出力していると思い込んでいた) nameを出力している そう理解しました ありがとうございます

回答No.3

> それぞれ異なるグローバル変数の値を利用するロジックがいまいち? 利用しているのはもちろん同じグローバル変数です。 kyodai1()は関数定義時にdefault_nameという名前の箱に入った値である"Ichiro"を利用し、kyodai2()は関数実行時、つまりprint()される直前の行で箱の中身が"Jiro"に入れ替えられたdefault_nameの値を利用するという違いだけです。

ketae
質問者

補足

default_nameを出力していると思いこんでいましたが、 name を出力していると気づきました ありがとうございます

  • chie65535
  • ベストアンサー率43% (8520/19368)
回答No.2

関数を def kyodai1(name=default_name): と定義した場合、nameのデフォルト値は「関数定義時点に決定」します。 default_nameは、この関数が定義された時点では「ichiroと定義されている」(「ichiroが代入されている、のではない」ことに注意)ので、この関数定義は def kyodai1(name="ichiro"): と同義です。 変数と関数の定義が終わると、実行に移ります。 実行段階に入ると、 default_name = "Ichiro" が実行された後に、関数定義部分は読み飛ばされ、何も実行されずに、 default_name = "Jiro" が実行され、default_nameにJiroが「代入」されます(定義ではなく「代入」である事に注意) そして、 def kyodai1(name=default_name): def kyodai2(name=None): と定義された関数が連続で呼ばれ kyodai2関数の中で if name is None: name = default_name が実行され、結果として Hello Ichiro, Hello Jiro. が出力されます。 最近の高級言語では、定義段階でソースコードを上から下まで走査(スキャン)して、実行段階で再度ソースコードを上から下まで走査(実行)して、のように、1つのソースコードを、何度も繰り返して読み込みます。一回で上から順にいきなり実行している訳ではないので「書いてある順に実行されるとは限らない」ので注意しましょう。

ketae
質問者

お礼

いつも詳しい説明をありがとうございます def がたぶん定義のdefinitionかなとおもっています 今回謎なのは、最終行で print(kyodai1(), kyodai2()) と出力する際、kyodai1()と kyodai2()の2つの関数が実行されているのはわかるのですが、 kyodai1()が"Ichiro"を呼んでいるのはわかります kyodai2()の関数が実行された場合、関数が参照する流れは結局 name = default_name のdefault_nameを探すとき print(kyodai2()) #最終行で  ↓ 関数kyodai2に飛ぶ def kyodai2(name=None): if name is None: name = default_name #default_nameがはどこだ?上にglobalの"Ichiro"あるけど、他も探してみよう return f"Hello {name}."  ↓ default_name = "Jiro" #あっ、下に"Jiro"があった  ↓ #出力 Hello Jiro. のような流れをイメージしました。間違っていたらご指摘ください。

回答No.1

> なぜkyodai2()関数は、最初のIchiroではなくJiroを読みにいくのでしょうか? kyodai2()関数が呼ばれる直前に default_name = "Jiro" が実行され、kyodai2()関数内の name = default_name のdefault_nameは上記で書き換えられたグローバル変数を参照するからです。

ketae
質問者

お礼

ありがとうございます kyodai2()関数が呼ばれるとき、kyodai1()も呼ばれているのに、それぞれ異なるグローバル変数の値を利用するロジックがいまいち?です笑

関連するQ&A

  • pythonの関数内での関数の定義について

    Python Cookbook 3rd の2章6節の内容でわからないことがあります。 コード import re #サンプルテキスト text = 'UPPER PYTHON, lower python, Mixed Python' #正規表現で文字のケースに合わせて置換する def matchcase(word) .....def replace(m): ..........text = m.group() ..........if text.isupper(): ...............return word.upper() ..........elif text.islower() ...............return word.lower() ..........elif text[0].isupper(): ...............return word.capitalize() ..........else: ..............return word .....return replace #実行分 re.sub('python', matches('snake'), text, flags=re.IGNORECASE) #実行結果 >>>'UPPER SNAKE, lower snake, Mixed Snake' このサンプルコードのmatchcase内のreplace関数に引数を渡していないと思うのですが、どのような仕組みでこのコードは動いているのでしょうか? m.group()とあるので実行分のre.sub(...)でのマッチしたという情報がreplace(m)のmに渡されているだろうとは思うのですが、なぜ引数を指定せずにそのようなことが起こるのかが理解できません。 何卒よろしくお願いします。

  • python: nonlocalとglobal文

    pythonで次のような関数の書き方がチュートリアル本に書かれていました ------------------------------------------- スコープと名前空間の例 def scope(): loc = "abc" def do_local(): loc = "local" def do_nonlocal(): nonlocal loc loc = "nonlocal" def do_global(): global loc loc = "global" ------------------------------------------- VS Codeでブレークポイントを置き走らせ、 nonlocal loc → loc = "nonlocal" global loc →loc = "global" の代入(バインディング?)が起こることはわかりました チュートリアル本では、nonlocalとglobalは次のように書かれています 説明文 nonlocal文 その変数が自分を取り囲むスコープにある global文 その変数がグローバルスコープにある 質問; 上の2行(説明文)の 1)nonlocal文 その変数が自分を取り囲むスコープにある 2)global文 その変数がグローバルスコープにある これらのスコープが何、どこ、どのように、指しているか教えてください また 3)これらと名前空間の関係をわかりやすく教えてください (本ではわからず) 【回答上のご注意】 回答は、解答(答え)を求めています わたしはプログラマーではないので、昭和的な「自分で考えろ」的なものは求めていません わからなければ答えない自由もあなたにはあります 不明点があれば説明いたします

  • VBAからPythonを動かしたいのですが…

    いつもお世話になっております。 icevainと申します。 python超初心者です。 【質問】 『VBAでPythonを動かす』という興味のあるサイトを見つけました。 https://qiita.com/O_LUPAN/items/1ceb5c950ff40f3558ab サイトのpythonコード #ここから import sys def sum(suji1, suji2): return suji1 + suji2 if __name__ == "__main__": argv = sys.argv suji1 = str(argv[1]) suji2 = str(argv[2]) total = sum(suji1, suji2) print(total) #ここまで サイトのpythonコードを無理やり変更して、 #ここから import sys def sum(suji1, suji2): rst="OK" return rst if __name__ == "__main__": argv = sys.argv suji1 = str(argv[1]) suji2 = str(argv[2]) total = sum(suji1, suji2) print(total) #ここまで OKがかえってくると思ったのですが、 0がかえってきてしまいます。 OKをかえすにはどうすればよいのかわかりません。 お分かりの方おりましたらご協力お願い致します。

  • python

    pythonについての質問です。 def S_sa(k=1) for step in xrange(k) : if step == 0 : print 'a' else print 'b' というコードがあって、なぜかprint aが実行されるのはなぜでしょうか? xrangeのreturnは1ではないのでしょうか?

  • Pythonにて不明な部分

    あるPythonコードからの抽出です。 Synset = namedtuple('Synset', 'synset pos name src') def getSynset(synset): print synset x = "select * from synset where synset='" + synset + "';" print x cur = conn.execute(x) return Synset(*cur.fetchone()) このgetSynsetを呼んだ時に、どのような内容が返されるか分かりません。 特に *cur の"*"は機能は何でしょうか。Cならポインタが指す内容ですが、Pythonにポインタは無いと思います。

  • pythonの問題を解くのを助けていただきたいです

    スペースが2つ以上ある場合スペースを一つだけにする、そして文の最初と最後にスペースがあった場合そのスペースを消すというコードをこのコードをベースに書き換えていただくことは可能でしょうか? def removeExtraSpaces(theString): outSt = "" foundExtraSpaces = False for ch in theString: if ch = " ": foundExtraSpaces = True elif foundExtraSpaces == True: outSt += ch return outSt print(removeExtraSpaces(" Hello Joe ")) #この場合”Hello Joe”と出力されるはずです。

  • Pythonに詳しい方助けてください!

    --------------------------------------------------- class Node: def __init__(self, x, bros = None, child = None): self.data = x self.bros = bros self.child = child def traverse(self, leaf): if self.data == leaf: yield [] else: child = self.child while child: for x in child.traverse(leaf): yield [self.data] + x child = child.bros --------------------------------------------------- Nodeクラスの一部を抜粋いたしました。 関数traverseが何をやっているのか知りたいのです。 Pythonの知識はほぼないですが、C++が多少分かるので、 C++に書き換えていただけると一番ありがたいです。 本当にすみません。 お分かりになる方いらっしゃいましたら教えて下さい。 ※インデントが重要と思われるPythonで投稿したらインデントが消えちゃいました。  画像を添付いたします。  本当にどうか助けてください. (;_;).

  • python print文のエラーがでます

    #!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import print_function import datetime class TZ(datetime.tzinfo): def __init__(self, name, offset): self.name = name self.offset = offset def utcoffset(self, dt): return datetime.timedelta(hours=self.offset) def tzname(self, dt): return self.name def dst(self, dt): return datetime.timedelta(0) def main(): path = '/sys/bus/w1/devices/28-000*********/w1_slave' with open(path) as f: data = f.read() temp = float(data[data.index('t=')+2:])/1000 FMT = '{},{:.1f}' JST = TZ('JST', 9) now = datetime.datetime.now(JST) print(FMT.format(now.strftime("%Y/%m/%d %H:%M:%S"), temp)) if __name__ == '__main__': main()
 とうってじっこうすると print(FMT.format(now.strftime("%Y/%m/%d %H:%M:%S"), temp)) の部分に SyntaxError: invalid syntax とエラーがでます。 どういうことなのか教えていただけるとうれしいです!

  • Pythonでのプログラミングについて。

    Pythonでのプログラミングについて。 大学の授業でPythonが出たのですが、なんせ先生が適当すぎてさっぱり意味がわかりません・・; 以下の問題の答えか、もしくは解説してくれる方はいらっしゃいませんでしょうか。 Q1:オレンジの数(count)を受け取って、"Number of oranges:<count>"を返すorange関数(a)を書け。 ただし、countが10以上の場合は実際の値のかわりに"many"を使え。 def orange(count): (a) return str print orange(4) print orange(99) Q2:文字列を受け取って、最初と最後の各2文字から作成した文字列を返すboth_ends関数(b)を書け。 ただし、文字列の長さが1のときは空文字列を返せ。 def both_ends(coun): (b) return str print both_end("hello") print both_end("a") Q3:文字列を反転するプログラム(c)を書け。 orig="hello" (c) print result Q4:文字列を反転するreverse関数(d)を書け。 (d) orig="good" result=reverse(orig) print result Q5:キーボードから受け取った文字列が回文(palindrome)かどうか判定するプログラム(e)を書け。 (d) orig=raw_input("Type a phrease:") result=reverse(orig) (e) Type a phrease:alice reverse=ecila Type a phrease:anna **palindrome** 自分で本を読んだり色々調べてみたのですが、判りませんでした。。 今日の12時までなのですが、誰かお願いします(;_;)

  • pythonで実体参照文字を直す方法

    pythonで以下のコードを使って実体参照、文字参照を直しているのですが「~」 など特定の文字がうまく直されませんどういうことでしょうか? python2.7です。 下のコードで4&#xFF5E;6&#x4EBA;を直すと46人になる。 文字参照とか実体参照とかhtmlentitydefsの使い方も詳しく分かってません。 その辺も総合して教えていただけるとありがたいです。 def sansyounaosi(text): # 正規表現のコンパイル reference_regex = re.compile(u'&(#x?[0-9a-f]+|[a-z]+);', re.IGNORECASE) num16_regex = re.compile(u'#x\d+', re.IGNORECASE) num10_regex = re.compile(u'#\d+', re.IGNORECASE) result = u'' g = 0 while True: # 実体参照 or 文字参照を見つける match = reference_regex.search(text, g) if match is None: result += text[g:] break result += text[g:match.start()] g = match.end() name = match.group(1) # 実体参照 if name in htmlentitydefs.name2codepoint.keys(): result += unichr(htmlentitydefs.name2codepoint[name]) # 文字参照 elif num16_regex.match(name): # 16進数 result += unichr(int(u'0'+name[1:], 16)) elif num10_regex.match(name): # 10進数 result += unichr(int(name[1:])) return result

専門家に質問してみよう