web-dev-qa-db-ja.com

Python関数に属性を追加する最良の方法

数学関数を評価するPython関数の単純なケースを考えます。

def func(x, a, b, c):
    """Return the value of the quadratic function, ax^2 + bx + c."""

    return a*x**2 + b*x + c

関数属性の形式でいくつかの詳細情報を「添付」したいとします。たとえば、LaTeX表現です。 PEP232 のおかげで、関数定義の外でこれを実行できることがわかります。

def func(x, a, b, c):
    return a*x**2 + b*x + c
func.latex = r'$ax^2 + bx + c$'

しかし、私は関数定義自体の中でそれをしたいと思います。私が書いたら

def func(x, a, b, c):
    func.latex = r'$ax^2 + bx + c$'
    return a*x**2 + b*x + c

これは確かに機能しますが、後で初めてfuncを呼び出しました(Pythonは "lazy "関数の実行中(?))

呼び出し可能なクラスを作成する唯一のオプションはありますか?

class MyFunction:
     def __init__(self, func, latex):
         self.func = func
         self.latex = latex

     def __call__(self, *args, **kwargs):
         return self.func(*args, **kwargs)

func = MyFunction(lambda x,a,b,c: a*x**2+b*x+c, r'$ax^2 + bx + c$')

それとも、これをきれいに行うために見落としている言語の特徴はありますか?

20
xnx

これを実現するためのより良いアプローチは、デコレータを使用することです。これには、2つのオプションがあります。

関数ベースのデコレーター:

ラテックス表現を引数として受け入れ、それを修飾する関数にアタッチする関数ベースのデコレーターを作成できます。

_def latex_repr(r):
    def wrapper(f):
        f.latex = r
        return f
    return wrapper
_

次に、関数を定義するときにそれを使用して、適切な表現を提供できます。

_@latex_repr(r'$ax^2 + bx + c$')
def func(x, a, b, c):
    return a*x**2 + b*x + c
_

これは次のように変換されます。

_func = latex_repr(r'$ax^2 + bx + c$')(func)
_

関数を定義した直後にlatex属性を使用できるようにします。

_print(func.latex)
'$ax^2 + bx + c$'
_

表現を必須の引数にしたので、表現を常に指定したくない場合は、適切なデフォルトを定義できます。

クラスベースのデコレーター:

クラスが好みのクラスである場合は、クラスベースのデコレーターを使用して、元の試みよりもPython的な方法で同様の効果を得ることができます。

_class LatexRepr:
    def __init__(self, r):
        self.latex = r

    def __call__(self, f):
        f.latex = self.latex
        return f
_

同じ方法で使用します。

_@LatexRepr(r'$ax^2 + bx + c$')
def func(x, a, b, c):
    return a*x**2 + b*x + c

print(func.latex)
'$ax^2 + bx + c$'
_

ここでLatexRepr(r'$ax^2 + bx + c$')はクラスを初期化し、呼び出し可能なインスタンスを返します(___call___定義済み)。これは何ですか:

_func = LatexRepr(r'$ax^2 + bx + c$')(func)
#                   __init__    
#                                  __call__
_

wrappedと同じことを行います。


どちらも関数に引数を追加するだけなので、そのまま返します。それらを別の呼び出し可能オブジェクトで置き換えることはありません。

クラスベースのアプローチでうまくいきますが、関数ベースのデコレーターはより高速で軽量でなければなりません。


あなたはさらに尋ねました:
"Pythonは関数の実行において「レイジー」であるため):Pythonは関数本体をコンパイルするだけで、内部のステートメントは実行しませんそれ;それだけdoes実行するのはデフォルトの引数値です(有名なQ こちら を参照)。そのため、最初に関数を呼び出して属性を「取得」する必要がありますlatex

このアプローチの追加の欠点は、その割り当てを実行することです毎回関数を呼び出す

関数を単純なPython関数よりも複雑なエンティティとして扱うため、提案したように、指定されたユーザー定義クラスの呼び出し可能なインスタンスとしてそれらを表すことは確かに多くの意味があります。

しかし、あなたが望むことを行うためのより簡単で一般的な方法は、デコレータを使用することです:

def with_func_attrs(**attrs):
    def with_attrs(f):
        for k,v in attrs.items():
            setattr(f, k, v)
        return f
    return with_attrs

@with_func_attrs(latex = r'$ax^2 + bx + c$', foo = 'bar')
def func(...):
    return ...
6
shx2