web-dev-qa-db-ja.com

インスタンスメンバーの既定の引数値をメソッドに渡す方法

インスタンスの属性の値を使用して、インスタンスメソッドにデフォルトの引数を渡します。

class C:
    def __init__(self, format):
        self.format = format

    def process(self, formatting=self.format):
        print(formatting)

それをしようとすると、次のエラーメッセージが表示されます。

NameError: name 'self' is not defined

メソッドを次のように動作させたい:

C("abc").process()       # prints "abc"
C("abc").process("xyz")  # prints "xyz"

ここでの問題は何ですか、なぜこれが機能しないのですか?そして、どうすればこの作品を作ることができますか?

51
Yugal Jindle

インスタンスが存在する前のメソッドが定義されるとデフォルト値が評価されるため、これをデフォルト値として実際に定義することはできません。通常のパターンは、代わりに次のようなことをすることです。

class C:
    def __init__(self, format):
        self.format = format

    def process(self, formatting=None):
        if formatting is None:
            formatting = self.format
        print(formatting)

self.formatは、formattingNoneの場合にのみ使用されます。


デフォルト値がどのように機能するかを示すために、この例を参照してください。

def mk_default():
    print("mk_default has been called!")

def myfun(foo=mk_default()):
    print("myfun has been called.")

print("about to test functions")
myfun("testing")
myfun("testing again")

そして、ここの出力:

mk_default has been called!
about to test functions
myfun has been called.
myfun has been called.

mk_defaultは一度だけ呼び出され、関数が呼び出される前に発生しました!

63
Adam Wagner

Pythonでは、名前selfnot特別です。これは単にパラメーター名の規則であるため、___init___にselfパラメーターがあります。 (実際、___init___もそれほど特別ではありません。特にnotは実際にオブジェクトを作成します...これは長い話です)

C("abc").process()Cインスタンスを作成し、processクラスのCメソッドを検索し、Cインスタンスでそのメソッドを呼び出します最初のパラメーターとして。そのため、指定した場合、selfパラメーターになります。

ただし、そのパラメーターがあったとしても、selfはまだデフォルト値を設定した時点ではスコープ内にないため、def process(self, formatting = self.formatting)のようなものを書くことはできません。 Pythonでは、関数のコンパイル時にパラメーターのデフォルト値が計算され、関数に「スタック」します。 (これは、_[]_のようなデフォルトを使用する場合、そのリストが関数呼び出し間の変更を記憶する理由と同じ理由です。)

どうすればこの作品を作ることができますか?

従来の方法は、Noneをデフォルトとして使用し、その値を確認して関数内で置き換えることです。目的の特別な値を作成する方が少し安全であることがわかります(呼び出しコードが同じインスタンスを使用しないように非表示にする限り、objectインスタンスで十分です)。 None。いずれにしても、この値は_==_ではなくisで確認する必要があります。

8
Karl Knechtel

self.formatをデフォルト引数として使用したいので、これはメソッドがインスタンス固有である必要があることを意味します(つまり、クラスレベルでこれを定義する方法はありません)。代わりに、たとえばクラスの__init__の間に特定のメソッドを定義できます。これは、インスタンス固有の属性にアクセスできる場所です。

1つのアプローチは、メソッドの更新された(特定の)バージョンを取得するために functools.partial を使用することです。

from functools import partial


class C:
    def __init__(self, format):
        self.format = format
        self.process = partial(self.process, formatting=self.format)

    def process(self, formatting):
        print(formatting)


c = C('default')
c.process()
# c.process('custom')  # Doesn't work!
c.process(formatting='custom')

このアプローチでは、対応する引数をキーワードでしか渡すことができないことに注意してください。位置で指定した場合、partialで競合が発生するためです。

別のアプローチは、__init__でメソッドを定義および設定することです。

from types import MethodType


class C:
    def __init__(self, format):
        self.format = format

        def process(self, formatting=self.format):
            print(formatting)

        self.process = MethodType(process, self)


c = C('test')
c.process()
c.process('custom')
c.process(formatting='custom')

これにより、引数を位置で渡すこともできますが、メソッドの解決順序はあまり明確になりません(たとえば、IDE検査に影響する可能性がありますが、IDE =そのための特定の回避策)。

別のアプローチは、これらの種類の「インスタンス属性のデフォルト」のカスタム型を、対応するgetattr引数の充填を実行する特別なデコレータとともに作成することです。

import inspect


class Attribute:
    def __init__(self, name):
        self.name = name


def decorator(method):
    signature = inspect.signature(method)

    def wrapper(self, *args, **kwargs):
        bound = signature.bind(*((self,) + args), **kwargs)
        bound.apply_defaults()
        bound.arguments.update({k: getattr(self, v.name) for k, v in bound.arguments.items()
                                if isinstance(v, Attribute)})
        return method(*bound.args, **bound.kwargs)

    return wrapper


class C:
    def __init__(self, format):
        self.format = format

    @decorator
    def process(self, formatting=Attribute('format')):
        print(formatting)


c = C('test')
c.process()
c.process('custom')
c.process(formatting='custom')
3
a_guest

クラス関数を非静的メソッドとして動作させる場合、「自己」をクラス関数の最初の引数として渡す必要があります。

オブジェクト自体を指します。最初の引数として位置が固定されているため、デフォルト引数として「自己」を渡すことができませんでした。

あなたの場合、「formatting = self.format」の代わりに「formatting = None」を使用して、コードから次のように値を割り当てます。

[編集]

class c:
        def __init__(self, cformat):
            self.cformat = cformat

        def process(self, formatting=None):
            print "Formating---",formatting
            if formatting == None:
                formatting = self.cformat
                print formatting
                return formatting
            else:
                print formatting
                return formatting

c("abc").process()          # prints "abc"
c("abc").process("xyz")     # prints "xyz"

注:「format」を変数名として使用しないでください。「Pythonの組み込み関数であるため」

1
Yajushi

デフォルトの引数にまたがるif-thenのリストを作成する代わりに、「デフォルト」辞書を使用して、eval()を使用してクラスの新しいインスタンスを作成できます。

class foo():
    def __init__(self,arg):
        self.arg = arg

class bar():
    def __init__(self,*args,**kwargs):
        #default values are given in a dictionary
        defaults = {'foo1':'foo()','foo2':'foo()'}
        for key in defaults.keys():

            #if key is passed through kwargs, use that value of that key
            if key in kwargs: setattr(self,key,kwargs[key]) 

            #if no key is not passed through kwargs
            #create a new instance of the default value
            else: setattr(self,key, eval(defaults[key]))

デフォルト引数として別のクラスをインスタンス化するすべてのクラスの先頭にこれをスローします。 pythonコンパイル時のデフォルトの評価を回避します...よりクリーンなPythonのアプローチが欲しいのですが、lo 'です。

0
noPounch

メソッド定義で自分にアクセスすることはできません。私の回避策はこれです-

class Test:
  def __init__(self):
    self.default_v = 20

  def test(self, v=None):
    v = v or self.default_v
    print(v)

Test().test()
> 20

Test().test(10)
> 10
0
amolk