web-dev-qa-db-ja.com

関数はどのようにして独自の属性にアクセスできますか?

python関数オブジェクトの属性から関数スコープ内からアクセスできますか?

例えば持ってみましょう

def f():
    return SOMETHING

f._x = "foo"
f()           # -> "foo"

ここで、_x属性の内容「foo」を返したい場合、SOMETHINGは何である必要がありますか?それが可能であれば(単に)

ありがとう

更新:

次の作業もお願いします。

g = f
del f
g()          # -> "foo"

更新2:

それが不可能である場合(そうである場合)、およびその理由は、それを偽造する方法を提供するよりも満足です。関数とは異なるオブジェクト

40
mykhal

解決

関数のデフォルト引数の1つを関数自体への参照にします。

def f(self):
    return self.x
f.func_defaults = (f,)

使用例:

>>> f.x = 17
>>> b = f
>>> del f
>>> b()
17

説明

元の投稿者は、グローバル名の検索を必要としないソリューションを望んでいました。シンプルなソリューション

def f():
    return f.x

各呼び出しでグローバル変数fの検索を実行しますが、要件を満たしていません。 fが削除された場合、関数は失敗します。より複雑なinspectプロポーザルも同じように失敗します。

私たちが望むのはearly bindingを実行し、バインドされた参照をオブジェクト自体に格納することです。以下は概念的に私たちがやっていることです:

def f(self=f):
    return self.x

上記では、selfはローカル変数なので、グローバルルックアップは実行されません。ただし、fのデフォルト値をバインドしようとしたときにselfがまだ定義されていないため、コードをそのまま書き込むことはできません。代わりに、fが定義された後でデフォルト値を設定します。

デコレータ

これはあなたのためにこれを行う簡単なデコレータです。 selfが最初に来るメソッドとは異なり、self引数は最後に来る必要があることに注意してください。これは、他の引数のいずれかがデフォルト値を取る場合は、デフォルト値を指定する必要があることも意味します。

def self_reference(f):
    f.func_defaults = f.func_defaults[:-1] + (f,)
    return f

@self_reference
def foo(verb, adverb='swiftly', self=None):
    return '%s %s %s' % (self.subject, verb, adverb)

例:

>>> foo.subject = 'Fred'
>>> bar = foo
>>> del foo
>>> bar('runs')
'Fred runs swiftly'
49
Mark Lodato

クラスを使用してこれを行うことができます

>>> class F(object):
...     def __call__(self, *args, **kw):
...         return self._x
... 
>>> f=F()
>>> f._x = "foo"
>>> f()
'foo'
>>> g=f
>>> del f
>>> g()
'foo'
24
John La Rooy

さて、関数とは何かを見てみましょう:

>>> def foo():
...     return x
... 
>>> foo.x = 777
>>> foo.x
777
>>> foo()
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
  File "<interactive input>", line 2, in foo
NameError: global name 'x' is not defined
>>> dir(foo)
['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__get__', 
'__getattribute__', '__hash__', '__init__', '__module__', '__name__', '__new__', 
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', 
'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 
'func_globals', 'func_name', 'x']
>>> getattr(foo, 'x')
777

ああ!したがって、属性は関数オブジェクトに追加されましたが、代わりにグローバルxを探しているため、属性は表示されません。

関数実行のフレームを取得して、そこに何があるかを確認することを試みることができます(基本的に、Anthony Kongが提案したものですが、inspectモジュールなし):

>>> def foo():
...     import sys
...     return sys._getframe()
... 
>>> fr = foo()
>>> dir(fr)
['__class__', '__delattr__', '__doc__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', 'f_back', 'f_builtins', 'f_code', 'f_exc_traceback', 'f_exc_type', 'f_exc_value', 'f_globals', 'f_lasti', 'f_lineno', 'f_locals', 'f_restricted', 'f_trace']
>>> fr.f_locals
{'sys': <module 'sys' (built-in)>}
>>> fr.f_code
<code object foo at 01753020, file "<interactive input>", line 1>
>>> fr.f_code.co_code
'd\x01\x00d\x00\x00k\x00\x00}\x00\x00|\x00\x00i\x01\x00\x83\x00\x00S'
>>> fr.f_code.co_name
'foo'

ああ!それで、コードブロックの名前から関数の名前を取得し、属性の回り道を調べることができるでしょうか。案の定:

>>> getattr(fr.f_globals[fr.f_code.co_name], 'x')
777
>>> fr.f_globals[fr.f_code.co_name].x
777
>>> def foo():
...     import sys
...     frm = sys._getframe()
...     return frm.f_globals[frm.f_code.co_name].x
... 
>>> foo.x=777
>>> foo()
777

それは素晴らしいことです!しかし、それは元の機能の名前変更と削除に耐えられるでしょうか?

>>> g = foo
>>> g.func_name
'foo'
>>> g.func_code.co_name
'foo'

ああ、とても疑わしい。関数オブジェクトとそのコードオブジェクトは、それらがfooと呼ばれていると主張しています。案の定、ここで問題が発生します。

>>> g.x
777
>>> g.x=888
>>> foo.x
888
>>> g()
888
>>> del foo
>>> g()
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
  File "<interactive input>", line 4, in foo
KeyError: 'foo'

ダン!したがって、一般的には、実行フレームを介した内省では実行できません。問題は関数オブジェクトコードオブジェクトの間に違いがあるようです-コードオブジェクトは実行されるものであり、関数のfunc_codeの1つの属性にすぎません-objectなどはfunc_dict属性にアクセスできません。ここで、属性xは次のとおりです。

>>> g
<function foo at 0x0173AE30>
>>> type(g)
<type 'function'>
>>> g.func_code
<code object foo at 017532F0, file "<interactive input>", line 1>
>>> type(g.func_code)
<type 'code'>
>>> g.func_dict
{'x': 888}

もちろん、関数のように見えるようにできる他のシカナリーもあります-特にクラス定義のトリック...しかし、それ自体は関数ではありません。それはあなたが本当にそれで何をする必要があるかによります。

15
Nas Banov

回避策として、ファクトリ関数を使用してスコープを修正できます。

def factory():
    def inner():
        print inner.x
    return inner


>>> foo=factory()
>>> foo.x=11
>>> foo()
11
>>> bar = foo
>>> del foo
>>> bar()
11
8
AntiEgo

以下は、関数を実行する前にcurrent_funを関数のグローバルに挿入するデコレータです。これはかなりハックですが、非常に効果的です。

from functools import wraps


def introspective(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        exists = 'current_fun' in f.func_globals
        old = f.func_globals.get('current_fun',None)
        f.func_globals['current_fun'] = wrapper
        try:
            return f(*args, **kwargs)
        finally:
            if exists:
                f.func_globals['current_fun'] = old
            else:
                del f.func_globals['current_fun']
    return wrapper

@introspective
def f():
    print 'func_dict is ',current_fun.func_dict
    print '__dict__ is ',current_fun.__dict__
    print 'x is ',current_fun.x

これが使用例です

In [41]: f.x = 'x'

In [42]: f()
func_dict is  {'x': 'x'}
__dict__ is  {'x': 'x'}
x is  x

In [43]: g = f

In [44]: del f

In [45]: g()
func_dict is  {'x': 'x'}
__dict__ is  {'x': 'x'}
x is  x
3
Geoff Reedy

これがbestの方法であるとは思えませんが、メソッド内でメソッドの名前を使用して属性にアクセスできます。

>>> def foo():
...   print foo.x
... 
>>> foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in foo
AttributeError: 'function' object has no attribute 'x'
>>> foo.x = 5
>>> foo()
5
3
Mark Rushakoff

答えはかなり簡単です。コンパイル時にではなく、実行時に検索されるファクト名を使用するだけです。

def f():
    return f._x

f._x = "foo"
f()           # -> "foo"
2
PierreBdR

これは少しハックなアプローチを使用しますが、g()呼び出しでも機能することを考えると、これはおそらくこれまでで最も正しい方法です。ショートカットとして dis モジュールによって実行されるバイトコード検査に依存しているため、これは機能します。

dis.disassemble()呼び出しがstdoutに出力されるため、実際よりもハックに見えます。そのため、これをStringIOにリダイレクトします。最後の命令を強調表示する機能にdisassemble()を使用します(print text行を追加して、外観を確認します)。これにより、以前のLOAD_NAMEとそれが使用した変数。

disモジュールをまったく使用せずに、よりクリーンなバイトコード検査ライブラリを使用してこれを行うことは可能ですが、これはそれが可能であることを証明しています。これは最も堅牢な方法ではないかもしれませんが、ほとんどの場合はうまくいくでしょう。 Python内部またはバイトコードを調べて、ほとんどのCALL_FUNCTIONバイトコードの前には、正規表現のトリックが選択する命令がすぐに続きます。

import inspect
import dis
import re
import sys
import StringIO

def f():
    caller = inspect.stack()[1][0]
    sys.stdout = StringIO.StringIO()
    dis.disassemble(caller.f_code, caller.f_lasti)
    text = sys.stdout.getvalue()
    sys.stdout = sys.__stdout__
    match = re.search(r'LOAD_NAME.*\((.*?)\)\s+-->', text)
    name = match.group(1)
    try:
        func = caller.f_locals[name]
    except KeyError:
        func = caller.f_globals[name]
    return func._x

f._x = 'foo'
print 'call f():', f()
g = f
del f
print 'call g():', g()

これにより、次の出力が生成されます。

call f(): foo
call g(): foo
1
Peter Hansen

関数名から完全に独立させたい場合は、フレームマジックが必要です。例えば:

def f2():
    import inspect
    frame = inspect.currentframe()
    fname = frame.f_code.co_name
    fobj = frame.f_globals[fname]
    print fobj._x


f2._x = 2
f2() 
1
Anthony Kong

私はこれがたくさん好きです。

from functools import update_wrapper

def dictAsGlobals(f):
    nf = type(f)(f.__code__, f.__dict__, f.__name__, f.__defaults__, f.__closure__)
    try: nf.__kwdefaults__ = f.__kwdefaults__
    except AttributeError: pass
    nf.__dict__ = f.__dict__
    nf.__builtins__ = f.__globals__["__builtins__"]
    return update_wrapper(nf, f)

@dictAsGlobals
def f():
    global timesCalled
    timesCalled += 1
    print(len.__doc__.split("\n")[0])
    return factor0 * factor1

vars(f).update(timesCalled = 0, factor0 = 3, factor1 = 2)

print(f())
print(f())
print(f.timesCalled)
0
flobbie

これはおそらくfunc_defaultsのアイデアよりも悪い戦略ですが、それでも興味深いものです。それはハッキーですが、私はそれに関して実際に間違っていることを考えることができません。

単一の__new__メソッド(通常はそのクラスの新しいオブジェクトを作成するメソッド)を使用して、それ自体をクラスとして参照できる関数を実装できます。

class new:
    """Returns True the first time an argument is passed, else False."""
    seen = set()
    def __new__(cls, x):
        old = x in cls.seen
        cls.seen.add(x)
        return not old

def main():
    print(new(1))  # True
    print(new(2))  # True
    print(new(2))  # false
    is_new = new
    print(is_new(1))  # False

おそらく、このパターンはロギング機能に役立ちます...

class log_once:
    """Log a message if it has not already been logged.

    Args:
        msg: message to be logged
        printer: function to log the message
        id_: the identifier of the msg determines whether the msg
          has already been logged. Defaults to the msg itself.

    This is useful to log a condition that occurs many times in a single
    execution. It may be relevant that the condition was true once, but
    you did not need to know that it was true 10000 times, nor do you
    desire evidence to that effect to fill your terminal screen.
    """
    seen = set()
    def __new__(cls, msg, printer=print, id_=None):
        id_ = id_ or msg
        if id_ not in cls.seen:
            cls.seen.add(id_)
            printer(id_)


if __name__ == '__main__':
    log_once(1)
    log_once(1)
    log_once(2)
0
fredcallaway

クロージャー内で関数を定義するだけです:

def generate_f():
    def f():
        return f.x
    return f

f = generate_f()

f.x = 314
g = f

del f
print g()
# => 314
0

関数の代わりにクラスを使用し、__new__メソッドを悪用して、クラスを関数として呼び出し可能にしてみませんか? __new__メソッドは最初のパラメーターとしてクラス名を取得するため、すべてのクラス属性にアクセスできます

のように

class f(object):
        def __new__(cls, x):
            print cls.myattribute
            return x

これは次のように機能します

f.myattribute = "foo"
f(3)
foo
3

その後、あなたはできる

g=f
f=None
g(3)
foo
3

問題は、オブジェクトが関数のように動作しても、そうではないということです。したがって、IDEは署名を提供できません。

0
user2340231

これを実現するもう1つの方法は、別の関数内で関数を定義し、外側の関数が内側の関数を返すようにすることです。その後、内部関数はクロージャーを介して自分自身にアクセスできます。以下に簡単な例を示します。

def makeFunc():
    def f():
        return f._x
    return f

次に:

>>> f = makeFunc()
>>> f._x = "foo"
>>> f()
'foo'
>>> g = f
>>> del f
>>> g()
'foo'
0
BrenBarn

必要なメソッドは1つだけですが、共有クラスの状態と個別のインスタンスの状態を持つ軽量クラスが必要な場合は、次のようなクロージャパターンを試すことができます。

# closure example of light weight object having class state,
#    local state, and single method
# This is a singleton in the sense that there is a single class
#    state (see Borg singleton pattern notebook)
#    BUT combined with local state
# As long as only one method is needed, this one way to do it
# If a full class singleton object is needed with multiple 
#    methods, best look at one of the singleton patterns

def LW_Object_Factory(localState):

    # class state - doesn't change
    lwof_args = (1, 2, 3)
    lwof_kwargs =  {'a': 4, 'b': 5}

    # local instance - function object - unique per
    # instantiation sharing class state
    def theObj(doc, x):
        print doc, 'instance:'
        print '\tinstance class state:\n\t\targs -', \
              lwof_args, ' kwargs -', lwof_kwargs
        print '\tinstance locals().items():'
        for i in locals().items():
            print '\t\t', i
        print '\tinstance argument x:\n\t\t', '"{}"'.format(x)
        print '\tinstance local state theObj.foo:\n\t\t',\
              '"{}"'.format(theObj.foo)
        print ''

    # setting local state from argument
    theObj.foo = localState

    return(theObj)

lwo1 = LW_Object_Factory('foo in local state for first')
lwo2 = LW_Object_Factory('foo in local state for second')

# prove each instance is unique while sharing class state
print 'lwo1 {} distinct instance from lwo2\n'\
      .format(id(lwo1) <> id(lwo2) and "IS" or "IS NOT")

# run them
lwo1('lwo1', 'argument lwo1') 
lwo2('lwo2', 'argument lwo2')
0
upandacross