この機能を知っている人は多くありませんが、Pythonの関数(およびメソッド)には attributes を含めることができます。見よ:
>>> def foo(x):
... pass
...
>>> foo.score = 10
>>> 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', 'score']
>>> foo.score
10
>>> foo.score += 1
>>> foo.score
11
Pythonでこの機能を使用および悪用する可能性はありますか?私が知っている1つの良い使用法は、 PLY のdocstringの使用法であり、構文規則をメソッドに関連付けます。しかし、カスタム属性はどうでしょうか?それらを使用する正当な理由はありますか?
通常、注釈のストレージとして関数属性を使用します。 C#のスタイル(特定のメソッドがWebサービスインターフェイスの一部であることを示す)で記述したいとします。
class Foo(WebService):
@webmethod
def bar(self, arg1, arg2):
...
その後、私は定義することができます
def webmethod(func):
func.is_webmethod = True
return func
次に、Webサービス呼び出しが到着すると、メソッドを検索し、基になる関数にis_webmethod属性があるかどうかを確認し(実際の値は無関係です)、メソッドが存在しないか、Web経由で呼び出されることを意図していない場合、サービスを拒否します。
関数の静的変数として使用しました。たとえば、次のCコードがあるとします。
int fn(int i)
{
static f = 1;
f += i;
return f;
}
Pythonでも同様に関数を実装できます。
def fn(i):
fn.f += i
return fn.f
fn.f = 1
これは間違いなくスペクトルの「乱用」の端に該当します。
JavaScriptの方法でオブジェクトを実行できます...それは意味がありませんが、動作します;)
>>> def FakeObject():
... def test():
... print "foo"
... FakeObject.test = test
... return FakeObject
>>> x = FakeObject()
>>> x.test()
foo
私はそれらを控えめに使用しますが、かなり便利です:
def log(msg):
log.logfile.write(msg)
これで、モジュール全体でlog
を使用でき、log.logfile
を設定するだけで出力をリダイレクトできます。それを実現する方法は他にもたくさんありますが、これは軽量で汚れが簡単です。そして、初めてやったときはおかしいにおいがしていましたが、グローバルなlogfile
変数を持つよりも匂いが良いと信じるようになりました。
関数属性を使用して、コードと関連データをラップする軽量クロージャーを作成できます。
#!/usr/bin/env python
SW_DELTA = 0
SW_MARK = 1
SW_BASE = 2
def stopwatch():
import time
def _sw( action = SW_DELTA ):
if action == SW_DELTA:
return time.time() - _sw._time
Elif action == SW_MARK:
_sw._time = time.time()
return _sw._time
Elif action == SW_BASE:
return _sw._time
else:
raise NotImplementedError
_sw._time = time.time() # time of creation
return _sw
# test code
sw=stopwatch()
sw2=stopwatch()
import os
os.system("sleep 1")
print sw() # defaults to "SW_DELTA"
sw( SW_MARK )
os.system("sleep 2")
print sw()
print sw2()
1.00934004784
2.00644397736
3.01593494415
時々、すでに計算された値をキャッシュするために関数の属性を使用します。このアプローチを一般化する汎用デコレータを使用することもできます。このような関数の並行性の問題と副作用に注意してください!
関数の属性を簡単に設定するために、このヘルパーデコレータを作成しました。
def with_attrs(**func_attrs):
"""Set attributes in the decorated function, at definition time.
Only accepts keyword arguments.
E.g.:
@with_attrs(counter=0, something='boing')
def count_it():
count_it.counter += 1
print count_it.counter
print count_it.something
# Out:
# >>> 0
# >>> 'boing'
"""
def attr_decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
return fn(*args, **kwargs)
for attr, value in func_attrs.iteritems():
setattr(wrapper, attr, value)
return wrapper
return attr_decorator
ユースケースは、ファクトリのコレクションを作成し、関数メタレベルで作成できるデータ型を照会することです。
たとえば(非常に愚かな):
@with_attrs(datatype=list)
def factory1():
return [1, 2, 3]
@with_attrs(datatype=SomeClass)
def factory2():
return SomeClass()
factories = [factory1, factory2]
def create(datatype):
for f in factories:
if f.datatype == datatype:
return f()
return None
これが可能な唯一の理由は、doc-stringやその他のようなものを置く論理的な場所があるからだといつも思っていました。私はそれを本番コードに使用した場合、それを読む人のほとんどが混乱することを知っています。