Python:関数の引数あれこれ

| | | | | | |



PythonでLISPもどきを作る方法を思考実験。*1

そのために必要そうな「あらかじめ引数の数が判ってない関数の呼び出しかた」の答えは、ちゃんとリファレンスマニュアルにありました。

判らなくて、調べるのに結構時間がかかったのでメモ。

。。。って、大晦日になにやってんだか。


Pythonでも、関数名自体が関数へのポインタとして使えます。

def f1(a,b,c):
   print a,b,c

という関数があったとき、

>>> f=f1
>>> f1(1,2,3)
1 2 3 #結果

では、変数fに入っている関数が、どのような引数を取るのか判ってない場合はどうしたらいいのか?たとえば、

f=f1 ; arglist=[1,2,3]

関数のポインタと引数のリストがこのように変数で与えられた時には

>>> f(*arglist)   #arglistの前にアスタリスク
1 2 3 #結果

このように呼び出すことができます。*がキモです。

あらかじめ、関数定義側での引数名が判っていれば、引数リストを辞書で渡すこともできます

def f1(a,b,c) : print a,b,c

f=f1 ;  d={"a":1,"b":2,"c":3}

f(**d)  #  *が二つ

1 2 3 #結果

関数定義側で*や**を使うと、引数の数を自由にとることができます

def f2(*arglist):
   s=0
   for x in arglist :
       s+=x
   print s
>>> f2(1)

1

>>> f2(1,2,3)
6
>>> f2(1,2,3,4,5)
15

def f3(**d):
   for k in d :
       print k,"=", d[k],"  ",
>>> f3(a=1,b=2,c=3)
a = 1    c = 3    b = 2
>>> f3(hoge="fuga")
hoge = fuga

でもf3を変数名無しで呼び出すと

>>> f3(1,2,3)
Traceback (most recent call last):
 File "<pyshell #128>", line 1, in <module>
   f3(1,2,3)
TypeError: f3() takes exactly 0 arguments (3 given)

さらにさらにこんなことをすれば、自由自在に引数を取れます。

def f3(*arglist,**d):
   for x in arglist :
       print x
   for k in d :
       print k, d[k]

でも、普通に使う分にはIDLEなどで関数の引数の説明などが出なくなるなどのデメリットも。


関数の引数の数、デフォルト値を調べる

呼び出し可能型 (callable type) ユーザ定義関数 (user-defined function)より

def hoge(a,b,c): pass #こんな関数があったら
hoge.func_defaults

#デフォルト値を持つ引数に対するデフォルト値が収められたタプル、デフォルト値を持つ引数がない場合には None

内部型 (internal type) ->コードオブジェクトより

hoge.func_code.co_argcount が固定引数 (positional argument) の個数。


で、これをどういうときに使うのかといいますとー

Pythonのプログラムを、LISP風に構文木で書くときに使えるんじゃないかなーと。

PythonでLISPインタプリタを書くひとは居ない

max(1,2,3,4,5)

たとえば、最大値を返す関数maxはこんなふうにリストに格納

L=[max,1,2,3,4,5]

これを呼び出すときは

>>> L[0](L[1:])
5 #1~5の最大値は5

またはこんな書き方も?

L=[max,[1,2,3,4,5],{}]

これをPythonで実行するときは

>>> L[0](*L[1],**L[2])
5

sin(x)**2+cos(x)**2は、こんな構文木に。

from operator import add,mul
[add,[mul,[sin,x],2.0],[mul,[cos,x],2.0]]

三角関数の加法定理はこちらをカンニングしました。

三角関数(ウィキペディア)


>>> from operator import add,mul
>>> reduce(add,range(3+1))
6 #1~3の合計
L=[reduce,add,[range,10]]

構文木は、こうなりますが、実行しようと思うと、range -> reduceの順番に実行して、式を内側から展開する工夫が必要です。
。。。というか、これじゃ

  • rangeの呼び出しなのか
  • 引数としてrangeのポインタを渡しているのか

の区別がつきませんよね。どうしよう?

class exeObj : pass #マークアップ用オブジェクト
L=[exeObj,reduce,add,[exeObj,range,10]]

で、リストを内側からwalkしてexeObjが無くなったらパース終了。。。遅そうだな。

class exeObj:
   __init__(self,f): self.f=f  #クラスの書き方、まだ憶えてないです。すんません。
L=[exeObj(reduce),add,[exeObj(range),10]]

こっちのほうがいいかな?

parser — Python解析木にアクセスする

parserモジュールはPythonの内部パーサとバイトコード・コンパイラへのインターフェイスを提供します。このインターフェイスの第一の目的は、PythonコードからPythonの式の解析木を編集したり、これから実行可能なコードを作成したりできるようにすることです。

私の守備範囲を超えております。自分でPythonなLISPインタプリタ書くよりは、バイトコードにコンパイルしてPythonに渡したほうがよさげではあるのですけれども。

Programming/Python/LanguageServices

上記モジュールのサンプルコード

Pythonで書かれたLISP処理系

メモメモ。

λ門 様々な LISPより

pylisp
Lisp in Python
Lython: Python用common lispフロントエンド

最小のLISP(ウィキペディア LISP)

最小のLISPは、以下に示す、Cや機械語で記述された関数だけを必要とする。
他のすべての関数は、効率良くではないが、これらの関数に置き換えて定義できるだろう。

へー。こうなってくると、Pythonにdefuncやlambdaなどに相当する関数も欲しいですな。(インチキするとしたら、evalやcompile使うのかしらん?)

Lisp プログラマのための Python 入門

Python にはマクロがない。 ~中略~

Lisp なら、これと同等の構文木は (+ 2 2) である。この構文木なら誰にでも使えるが、 Python のこんな構文木をいじれるのは本当のエキスパートだけだろう。

Pythonプログラムを、構文木にしてから最適化しよう、という目論見は、私には難しそうです。いや、最初からできるとは思ってませんでしたけれども。(汗)


*1
Maximaで遊んでたら、なんとなく、こんなことのやりかたが気になったので。

Tags:

Related posts

Tags:

Comments are closed.