python関数オブジェクトの属性から関数スコープ内からアクセスできますか?
例えば持ってみましょう
def f():
return SOMETHING
f._x = "foo"
f() # -> "foo"
ここで、_x属性の内容「foo」を返したい場合、SOMETHINGは何である必要がありますか?それが可能であれば(単に)
ありがとう
更新:
次の作業もお願いします。
g = f
del f
g() # -> "foo"
更新2:
それが不可能である場合(そうである場合)、およびその理由は、それを偽造する方法を提供するよりも満足です。関数とは異なるオブジェクト
関数のデフォルト引数の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'
クラスを使用してこれを行うことができます
>>> class F(object):
... def __call__(self, *args, **kw):
... return self._x
...
>>> f=F()
>>> f._x = "foo"
>>> f()
'foo'
>>> g=f
>>> del f
>>> g()
'foo'
さて、関数とは何かを見てみましょう:
>>> 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}
もちろん、関数のように見えるようにできる他のシカナリーもあります-特にクラス定義のトリック...しかし、それ自体は関数ではありません。それはあなたが本当にそれで何をする必要があるかによります。
回避策として、ファクトリ関数を使用してスコープを修正できます。
def factory():
def inner():
print inner.x
return inner
>>> foo=factory()
>>> foo.x=11
>>> foo()
11
>>> bar = foo
>>> del foo
>>> bar()
11
以下は、関数を実行する前に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
これが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
答えはかなり簡単です。コンパイル時にではなく、実行時に検索されるファクト名を使用するだけです。
def f():
return f._x
f._x = "foo"
f() # -> "foo"
これは少しハックなアプローチを使用しますが、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
関数名から完全に独立させたい場合は、フレームマジックが必要です。例えば:
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()
私はこれがたくさん好きです。
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)
これはおそらく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)
クロージャー内で関数を定義するだけです:
def generate_f():
def f():
return f.x
return f
f = generate_f()
f.x = 314
g = f
del f
print g()
# => 314
関数の代わりにクラスを使用し、__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は署名を提供できません。
これを実現するもう1つの方法は、別の関数内で関数を定義し、外側の関数が内側の関数を返すようにすることです。その後、内部関数はクロージャーを介して自分自身にアクセスできます。以下に簡単な例を示します。
def makeFunc():
def f():
return f._x
return f
次に:
>>> f = makeFunc()
>>> f._x = "foo"
>>> f()
'foo'
>>> g = f
>>> del f
>>> g()
'foo'
必要なメソッドは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')