Pythonの関数内での関数の定義について

このQ&Aのポイント
  • Python Cookbook 3rdの2章6節で説明されているサンプルコードでは、matchcase関数内のreplace関数に引数を渡していないことが疑問です。
  • サンプルコードの実行分で、re.sub(...)によってマッチした情報がreplace(m)の引数mに渡される仕組みになっているのでしょうか?
  • なぜreplace関数に引数を指定せずに、re.sub(...)でのマッチ情報が渡されるのかが理解できません。
回答を見る
  • ベストアンサー

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に渡されているだろうとは思うのですが、なぜ引数を指定せずにそのようなことが起こるのかが理解できません。 何卒よろしくお願いします。

質問者が選んだベストアンサー

  • ベストアンサー
回答No.1

あー、うん、どう言えばいいんだろ(笑)。 Lisp経験者とかなら「あー、これは関数を返す関数だから」とかすぐ分かるんですが(笑)。 最近よく出る(例えばJava8で出て来た新機能)、「クロージャ」ってのがこれなんです。 つまり、関数matchcaseってのは中で定義された「ローカル関数」replace、ってのを最後に「丸ごと」返してますよね(return replace)。これはmatchcaseが「関数を返す関数だ」って事なんです。 関数が計算した値を返してるわけじゃない、関数「そのものを」返してるんです。大事な事なんで二回言いました。 ちょっと簡単な例を挙げてみましょう。次のような関数を定義してみます。 def foo(x):   def bar(y):     return x * y   return bar 関数fooは引数xを一つ取ります。内部にローカル関数barを定義してて、barは「xとは全く関係ない」別の引数yを取ります。つまり、ローカル関数barはfooが求める引数xを「引数としては使わない」わけですよね。全く別の引数yを求めていて、しかもbarはxとyの積を計算します。 実際、fooに2つの引数、例えば2×3を計算させたいとして、2と3を与えるとエラーを返すわけです。 >>> foo(2, 3) Traceback (most recent call last):  File "<pyshell#19>", line 1, in <module>   foo(2, 3) TypeError: foo() takes 1 positional argument but 2 were given 当然一つの引数しかfooは取れないので、ここでは2を与えてみます。 >>> foo(2) <function foo.<locals>.bar at 0x027E2B28> fooは「関数を返す関数」なんで、なんかの計算した値は返してくれません。その代わり変わったリテラルを返してきます。日本語で言うと要するに 「関数fooはローカル定義された関数barを返しました」 的な一種のメッセージを返してくるわけです。これが実はクロージャなんですね。こう言う表現で返してくる。 さて、じゃあ適当に、変数varにfoo(2)を代入してみましょう。 >>> var = foo(2) >>> 何も返ってきませんね。まあ、これは単純にvarに代入しただけなんで当然です。 varの中身を見てみましょうか。 >>> var <function foo.<locals>.bar at 0x027E2B70> はい、さっきと同じような「ローカル関数barですよ」と言う一種のメッセージが返ってきました。 さて、これがクロージャの面白いトコなんですが、varは変数です。ただし、中に入ってる「値」ってのはどうやら「barと言うfoo内部のローカル関数らしい」って事が分かっています。つまり、原理的にはvarは「関数と言う機能を持ってるんじゃないか」って事なんですよ。あれ? 実はこの推論は正しく、「クロージャを代入された変数varは関数として振る舞う」んです。var自体は変数なのに?実はそうなんですね。 ここで、変数varに引数3を与えてみます。「動く筈ねぇじゃねぇか」って思ってもさにあらず。 >>> var(3) 6 なんと、変数varに「引数3を与えたら」6が返ってきました。ビックリですね(笑)。 つまり、どういう事か、と言うと、関数fooは引数を一つ取って、ローカル関数barを返す関数として定義しました。変数varは「引数2を取った関数foo」を保持したままの(言い換えると引数2を取った状態でローカル関数bar自体を剥き出しにした)状態です。今、変数varはつまり、「引数待ちのbar」、言い換えると、一種計算途中のまま「待機状態」だったわけで、そこに新しく「barに与える為の」引数をvarを介して与えると「止まってた計算が動き出す」ようなカンジになるんですね。 これがクロージャ、で、今、JavaScript等の比較的新しい言語には搭載されてる機能です。 ついでに言うと、最近Java8にも入ってきて、多くの古典的なJavaユーザーを混乱に叩き込んでる機能です(笑)。

hayato333
質問者

お礼

返事が遅くなり申し訳ございません。 どうも、ありがとうございます。 大変ご丁寧な解説で、すぐに理解することができました。 回答者様がいなければ、この謎を解くために数時間浪費して、最後には爆発していたと思います。本当にありがとうございます!!!

関連するQ&A

  • python return 関数

    pythonで以下のようなコードがありました。 returnでメソッド内のメソッドを返すのは、どういう意味があるのでしょうか? 仕組みが分かっていません。 よろしくおねがいします。 class OkWave(object): @classmethod def funcA(cls ,abc,def): def funcB(self, xyz): 何かの処理P return funcB

  • pythonについて

    pythonについて質問があります。 下記の組み合わせプログラムで、comb'(n,m)のn.mをループさせて、m=1から5、n=1から5のようにループさせたいと思っています。 どなたかご教授ください。 def comb(n, m, a = []): if m == 0: print a elif n == m: print range(1, m + 1) + a else: comb(n - 1, m, a) comb(n - 1, m - 1, [n] + a) よろしくお願いします。

  • 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

  • pythonでフィボナッチ数カウントを行いたいのですが...

    pythonで、入力された二つの数字によるrange(a, b)内からフィボナッチ数の数をカウントするプログラムを書きたいのですが、どのように書けば良いでしょうか?因みに、今のところ a=int(raw_input("Enter the minimum valuer:")) b=int(raw_input("Enter the maximum value:")) def fib(n): if n <= 0: n = 0 elif n == 1: n = 1 else: n = fib(n-1) + fib(n-2) return n for i in range(a, b+1): まで書いてみたのですが、この後どうしたら良いかわからなくなってしまいました。 初歩的な質問なのですが、どうかご回答よろしくお願いします。

  • 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(&quot;%Y/%m/%d %H:%M:%S&quot;), temp)) if __name__ == '__main__': main()
 とうってじっこうすると print(FMT.format(now.strftime(&quot;%Y/%m/%d %H:%M:%S&quot;), temp)) の部分に SyntaxError: invalid syntax とエラーがでます。 どういうことなのか教えていただけるとうれしいです!

  • Python csvについて

    Python 初心者です。 Python/Djangoです。 下記のエラーを解決できなくて!! Exception Type: UnicodeEncodeError Exception Value: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128) コードは: def export_excel(request): response = HttpResponse(mimetype='text/csv') response['down.csv'] = 'attachment; filename=down.csv' writer = csv.writer(response) obj_all=User.objects.all() for obj in obj_all: row=[] for field in User._meta.fields: row.append(getattr(obj,field.name)) writer.writerow(row) return response

  • Python: TypeError

    Pythonでテキストファイルから数字を読み込む時に、スペースで区切られたデータをパラメータセットとして配列の中に格納したいのですが、その際にチェック事項があってFor LoopにTryを挟んでいます。この状態でどうしても下記のエラーが出るのですが、どのようにすれば良いのでしょうか? 宜しくお願い致します。 TypeError: argument 2 to map() must support iteration --- def ReadOutput1(): f = open('C:/Users/.../output1.txt','rb') output = [] for line in f: ls = line.split(' ') for i in range(4, 8)): try: modelParams[i-4] = map(add, float(ls[i])) except ValueError: (...) return output

  • Pythonのコードself多すぎ読み書きしにくい

    Pythonのクラスには通常class:宣言行直下で作成する「クラス変数」と、__init__()メソッド内で作成するかインスタンス作成後にインスタンス名.変数名で追加作成する「インスタンス変数」があります。 Pythonのクラス変数はJavaのクラスでstatic修飾子を付けて宣言したクラス変数と同じで、そのクラスから作成されたインスタンス全てに同じメモリーを割り当てます。 Pythonのインスタンス変数はインスタンスごとにメモリーが割り当てられるので、Javaの普通の変数(フィールド)のように同一クラス内のすべてのメソッドから参照できますが、参照するたびにいちいち「self.」を付けないといけません。 (例 かなり恣意的ですが。。。インデントはOKWAVEが取ってしまうかもしれせん) class Sample: def __init__(self): self.L = [] # Lは数値のリスト def setList(self, L): self.L = L def exe(self, i1, i2): # L[i1]>0かつL[i2]>0のとき、大きい方から小さい方を引いた # 値に、大きい方を小さい方で累乗した値を加える if self.L[i1] > 0 and self.L[i2]: if self.L[i1] > self.L[i2]: return (self.L[i1] - self.L[i2]) + self.L[i1] ** self.L[i2] else: return (self.L[i2] - self.L[i1]) + self.L[i2] ** self.L[i1] イテレーターを使て同じ処理をループする場合はいいのですが、ループで処理できない複雑な処理が続く場合はこのように「self」だらけになり、コーディングミスが多発し、コードが読みにくいものになります。 「self」を省略してインスタンス変数をメソッドから参照し、コードを読みやすいものにする手段はないのでしょうか? どうぞよろしくお願いします。

  • Pythonプログラミングでエラー

    Pythonのプログラムを教科書をみながら勉強しはじめました。 バージョンは3.0.1です。 下記の初歩的なプログラムで#の行でエラーがでて対応方法が わかりません。 アドバイスいただけませんでしょうか。 #! /python30/python # coding:shift_jis ''' 簡単な電卓です。 Usage: mycalc.py n (+-*/) m ''' import sys, operator OP={'+':operator.__add__,\ '-':operator.__sub__,\ '*':operator.__mul__,\ '/':operator.__truediv__,\ } def calc(ls): assert len(ls)==3 #ここでエラー try: op=OP(ls.pop(1)) # 真ん中の文字列をpop,数字のみ残る。 x,y=[float(u) for u in ls] # 残った文字列をfloatに変換 except: print('Err:数字 演算子(+-*/) 数字 の順に引数をセットしてください。') sys.exit() return op(x,y) if __name__=='__main__': print(calc(sys.argv[1:])) #ここでエラー (ここで質問するとTABのインデントがなくなってしまうようです。)

  • pythonのスクリプトが動きません

    Webで"大きなPDFファイルを自動分割するPythonスクリプト" #!/usr/bin/env python # -*- coding: utf-8 -*- import os import glob import math import subprocess import re #==-User Parameter==== MAX_PDF_SIZE_MB=80 class PdfSplit: def __init__(self, fileName, resultpath): print("Split file name is "+fileName) #FileSizeCheck fsizeMB=float(os.path.getsize(fileName))/1000000 print("file size is "+str(fsizeMB)+" MB") if fsizeMB<=MAX_PDF_SIZE_MB: print("Not need split") return; nSplit=int(math.ceil(fsizeMB/MAX_PDF_SIZE_MB)) print("nSplit:"+str(nSplit)) nPage=self.GetPdfPageNumber(fileName) # nPage=888 print("nPage:"+str(nPage)) if nPage==0: print("Error: cannot read page:"+fileName) return onePage=int(math.ceil(nPage/nSplit)) print("onePage:"+str(onePage)) #result path finalpath=resultpath+"/"+fileName[2:-4] print("finalpath:"+finalpath) #Page split startpage=1 for i in range(nSplit): endpage=(i+1)*onePage if i==nSplit-1: endpage=nPage print "start page:"+str(startpage)+",endpage:"+str(endpage) cmd="pdftk "+fileName+" cat "+str(startpage)+"-"+str(endpage)+" output "+finalpath+"_"+str(i+1)+".pdf" # pdftk 元ファイル名.pdf cat 開始ページ-終了ページ output 出力ファイル名.pdf p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) p.wait() stdout_data, stderr_data = p.communicate() print stderr_data startpage=endpage+1 def GetPdfPageNumber(self,fileName): cmd="pdftk "+fileName+" data_dump" print cmd p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # p.wait() stdout_data, stderr_data = p.communicate() extracted=0 count=0 for strline in stdout_data.split("\n"): # print strline #sample NumberOfPages: 344 if "NumberOfPages" in strline: number = re.search("\d+",strline) extracted = number.group() break count+=1 if count>=100: break return int(extracted) class SplitManager(): def __init__(self): print "init" def Main(self): #Create Result dir RESULT_DIR='results' if not os.path.isdir(RESULT_DIR): os.mkdir(RESULT_DIR) files = glob.glob('./*.pdf') for file in files: PdfSplit(file,RESULT_DIR) if __name__ == '__main__': print __file__+" start!!" manager=SplitManager() manager.Main() が公開されていたので使用しようと思ったのですがうまく動作しません。 このスクリプトを使用するためにPython3.7.0b3とPDFtkをインストールしました。 そして、公開されていたシクリプトをtextにコピーしてpdf.pyという名前で保存しました。 最後にpdfファイルを収納したフォルダにpdf.pyを入れて、pdf.pyを実行しました。 しかしコマンドプロンプトが一瞬表示されるだけで動作しませんでした。 何か間違っているから動かないのだと思うのですが分からないので教えてください。 Pythonスクリプトの実行手順はこれで合ってますか。 それともPythonのスクリプトとバージョンが合ってないのでしょうか。 PCはWindows7です。 回答よろしくお願いします。