モジュールはオブジェクトと同じようにプロパティを持つことができますか?


98

Pythonのプロパティを使用すると、次のようにすることができます

obj.y 

単に値を返すのではなく、関数を呼び出します。

モジュールでこれを行う方法はありますか?欲しい場合があります

module.y 

そこに格納されている値を返すだけでなく、関数を呼び出す。


2
より最新のソリューションについて__getattr__は、モジュールを参照してください。
wim

回答:


56

新しいスタイルのクラスのインスタンスのみがプロパティを持つことができます。Pythonに、そのようなインスタンスをモジュールであると信じ込ませることができます。sys.modules[thename] = theinstance。したがって、たとえば、m.pyモジュールファイルは次のようになります。

import sys

class _M(object):
    def __init__(self):
        self.c = 0
    def afunction(self):
        self.c += 1
        return self.c
    y = property(afunction)

sys.modules[__name__] = _M()

2
他の誰かがこれを試しましたか?私は1つのファイルx.pyにこのコードを配置し、その後はAttributeErrorでのxy結果を呼び出すと、別のからそれをインポートする場合:_Mは何とか値なしを持っていないので「NoneType」オブジェクトが...、何の属性「C」を持っていない
Stephan202

3
実際、コードはインタープリターで機能します。しかし、ファイル(たとえばbowwow.py)に入れて別のファイル(otherfile.py)からインポートすると、機能しなくなります...
Stephan202

4
Q:@Unknown types.ModuleTypeのその他の非常によく似た答えに示されているように、インスタンスのクラスを派生させることには特別な利点がありますか?
マルティノー

11
新しいスタイルのクラスのインスタンスのみがプロパティを持つことができます。これは理由ではありません。モジュール新しいスタイルのクラスのインスタンスでありbuiltins.module、それ自体がtype(新しいスタイルのクラスの定義である)のインスタンスであるのインスタンスです。問題は、プロパティは、クラス、インスタンスではなく上になければならないということです:あなたがしなければf = Foo()f.some_property = property(...)あなたは単純にモジュールに入れているかのように、それは同じように失敗します。解決策は、それをクラスに置くことですが、すべてのモジュールにプロパティを持たせたくないので、サブクラスにします(不明の回答を参照)。
タナトス2014

3
@Joe、名前の再バインド時のglobals()(キーはそのままにして値をにリセットするNone)の変更sys.modulesは、Python 2の問題です-Python 3.4は意図したとおりに動作します。Py2のクラスオブジェクトにアクセスする必要がある場合は、たとえばステートメントの_M._cls = _M直後に追加してclass(または他の名前空間に同等にスタッシュして)、それをself._cls必要とするメソッドのようにアクセスします(type(self)大丈夫かもしれませんが、のサブクラス化もしない場合_M) 。
Alex Martelli、2015

54

これは、モジュールのすべての属性を適切に継承し、isinstance()によって正しく識別されるようにするためです。

import types

class MyModule(types.ModuleType):
    @property
    def y(self):
        return 5


>>> a=MyModule("test")
>>> a
<module 'test' (built-in)>
>>> a.y
5

そして、これをsys.modulesに挿入できます:

sys.modules[__name__] = MyModule(__name__)  # remember to instantiate the class

これは、最も単純なケースでのみ機能するようです。考えられる問題は次のとおりです:(1)一部のインポートヘルパーは__file__、手動で定義する必要があるような他の属性も想定している可能性があります(2)クラスを含むモジュールで行われたインポートは、実行時に「見えない」など...
tutuDajuju

1
それからサブクラスを導出する必要はありませんtypes.ModuleType任意の(新しいスタイル)クラスが行います。継承したい特別なモジュール属性は何ですか?
martineau

元のモジュールがパッケージであり、元のモジュールの下のモジュールにアクセスしたい場合はどうなりますか?
kawing-chiu

2
@martineauモジュールのreprがあり、__init__インスタンスのときにモジュール名を指定でき、を使用すると正しい動作が得られますisinstance
wim

@wim:率直に言って、重要なIMOはどれも重要ではないようですが、ポイントが取られました。
マルティノー

33

PEP 562として Pythonで実装されています> = 3.7、今、私たちはこれを行うことができます

ファイル:module.py

def __getattr__(name):
    if name == 'y':
        return 3
    raise AttributeError(f"module '{__name__}' has no attribute '{name}'")

other = 4

使用法:

>>> import module
>>> module.y
3
>>> module.other
4
>>> module.nosuch
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "module.py", line 4, in __getattr__
    raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
AttributeError: module 'module' has no attribute 'nosuch'

あなたは省略することに注意raise AttributeErrorして__getattr__機能、それが持つ機能の終了を意味しreturn None、その後、module.nosuchの値を取得しますNone


2
これに基づいて、別の回答を追加しました:stackoverflow.com/a/58526852/2124834

3
これはプロパティの半分にすぎません。セッターなし。
WIM

残念ながら、そのような属性をツールに認識させることは難しいことではないでしょうか(?)(getattr何のレギュラーメンバーが見つからない場合にのみ呼び出されます)
olejorgenb

9

ジョン・リンの答えに基づいて:

def module_property(func):
    """Decorator to turn module functions into properties.
    Function names must be prefixed with an underscore."""
    module = sys.modules[func.__module__]

    def base_getattr(name):
        raise AttributeError(
            f"module '{module.__name__}' has no attribute '{name}'")

    old_getattr = getattr(module, '__getattr__', base_getattr)

    def new_getattr(name):
        if f'_{name}' == func.__name__:
            return func()
        else:
            return old_getattr(name)

    module.__getattr__ = new_getattr
    return func

使用方法(先頭のアンダースコアに注意)、中the_module.py

@module_property
def _thing():
    return 'hello'

次に:

import the_module

print(the_module.thing)  # prints 'hello'

プロパティ化された関数を元の関数と区別するには、先頭のアンダースコアが必要です。識別子を再割り当てする方法を考えることができませんでした。デコレータの実行時に識別子がまだ割り当てられていないためです。

IDEはプロパティが存在することを認識せず、赤い波線が表示されることに注意してください。


すごい!クラスプロパティと比較して、下線なしの方がより一般的@property def x(self): return self._xだと思いますdef thing()。そして、あなたはあなたの答えで「モジュールプロパティセッター」デコレーターも作成できますか?
John Lin、

2
@JohnLin、私はあなたのdef thing()提案を実装しようとしました。問題は、欠落している属性__getattr__に対してのみ呼び出されることです。ただし、@module_property def thing(): …実行後はthe_module.thingが定義されているため、getattrは呼び出されません。どういうわけかthing、デコレータに登録してから、それをモジュールの名前空間から削除する必要があります。Noneデコレータから戻ってみましたが、thingと定義されていNoneます。できるかもしれません@module_property def thing(): … del thingthing()、関数として使用するよりも悪いことに
気づき

「モジュールプロパティセッター」も「モジュール__getattribute__」もないようです。ありがとうございました。
ジョンリン

5

典型的な使用例は次のとおりです。すべてのモジュールをクラスレイアウトに変換せずに、(巨大な)既存のモジュールをいくつかの(少数の)動的属性で強化します。残念ながら、次のような最も単純なモジュールクラスパッチsys.modules[__name__].__class__ = MyPropertyModule失敗しTypeError: __class__ assignment: only for heap typesます。そのため、モジュールの作成をやり直す必要があります。

このアプローチは、モジュールコードの上にプロローグをいくつか置くだけで、Pythonインポートフックなしでそれを行います。

# propertymodule.py
""" Module property example """

if '__orgmod__' not in globals():

    # constant prolog for having module properties / supports reload()

    print "PropertyModule stub execution", __name__
    import sys, types
    class PropertyModule(types.ModuleType):
        def __str__(self):
            return "<PropertyModule %r from %r>" % (self.__name__, self.__file__)
    modnew = PropertyModule(__name__, __doc__)
    modnew.__modclass__ = PropertyModule        
    modnew.__file__ = __file__
    modnew.__orgmod__ = sys.modules[__name__]
    sys.modules[__name__] = modnew
    exec sys._getframe().f_code in modnew.__dict__

else:

    # normal module code (usually vast) ..

    print "regular module execution"
    a = 7

    def get_dynval(module):
        return "property function returns %s in module %r" % (a * 4, module.__name__)    
    __modclass__.dynval = property(get_dynval)

使用法:

>>> import propertymodule
PropertyModule stub execution propertymodule
regular module execution
>>> propertymodule.dynval
"property function returns 28 in module 'propertymodule'"
>>> reload(propertymodule)   # AFTER EDITS
regular module execution
<module 'propertymodule' from 'propertymodule.pyc'>
>>> propertymodule.dynval
"property function returns 36 in module 'propertymodule'"

注:のようなfrom propertymodule import dynvalものはもちろん、凍結されたコピーを生成します-に対応しますdynval = someobject.dynval


1

短い答え:使用 proxy_tools

proxy_tools提供するために、パッケージの試み@module_property機能を。

それはとインストールします

pip install proxy_tools

@Mareinの例を少し変更しthe_module.pyて、

from proxy_tools import module_property

@module_property
def thing():
    print(". ", end='')  # Prints ". " on each invocation
    return 'hello'

今、別のスクリプトから、私はすることができます

import the_module

print(the_module.thing)
# . hello

予期しない動作

この解決策には警告がないわけではありません。つまり、文字列the_module.thingはありません。これは、proxy_tools.Proxy文字列を模倣するように特別なメソッドがオーバーライドされたオブジェクトです。ポイントを説明するいくつかの基本的なテストはここにあります:

res = the_module.thing
# [No output!!! Evaluation doesn't occur yet.]

print(type(res))
# <class 'proxy_tools.Proxy'>

print(isinstance(res, str))
# False

print(res)
# . hello

print(res + " there")
# . hello there

print(isinstance(res + "", str))
# . True

print(res.split('e'))
# . ['h', 'llo']

内部的には、元の関数は次の場所に保存され the_module.thing._Proxy__localます。

print(res._Proxy__local)
# <function thing at 0x7f729c3bf680>

さらなる考え

正直なところ、モジュールにこの機能が組み込まれていない理由に困惑しています。問題の核心はそれthe_moduletypes.ModuleTypeクラスのインスタンスであると思います。「モジュールプロパティ」を設定すると、クラス自体ではなく、このクラスのインスタンスにプロパティを設定することになりますtypes.ModuleType。詳細については、この回答を参照してください

types.ModuleType結果は素晴らしいものではありませんが、実際には次のようにプロパティを実装できます。組み込み型を直接変更することはできませんが、それらを呪うことはできます。

# python -m pip install forbiddenfruit
from forbiddenfruit import curse
from types import ModuleType
# curse has the same signature as setattr.
curse(ModuleType, "thing2", property(lambda module: f'hi from {module.__name__}'))

これにより、すべてのモジュールに存在するプロパティが提供されます。すべてのモジュールで設定動作を壊すので、少し扱いに​​くいです。

import sys

print(sys.thing2)
# hi from sys

sys.thing2 = 5
# AttributeError: can't set attribute

1
これは、@ Alex Martelliの回答に示されているように、モジュールを実際のクラスのインスタンスにするだけよりも優れていますか?
マルティノー

1
私には意味のない何か他のことを言った。@module_propertyデコレータを持っていることについてこのビジネスを取る。一般的に言えば、組み込みの@propertyデコレータは、インスタンスが作成された後ではなく、クラスが定義されたときに使用されるため、モジュールプロパティにも同じことが当てはまり、Alexの回答にも当てはまります。 「モジュールはオブジェクトと同じ方法でプロパティを持つことができますか?」ただし、後でそれらを追加すること可能であり、実行可能な方法を示すために以前のスニペットを変更しました。
マルティノー

1
ベン:具体的な例のコードを調べたところ、あなたが今何をしているのか理解できたと思います。私は最近、モジュールのプロパティに似たものを実装する手法に偶然出会ったと思います。モジュールをAlexの回答のようにクラスインスタンスで置き換える必要はありません。それはデコレータを介して—私が何か進歩を遂げたらあなたに返信します。
マルティノー

1
わかりました。ここに、核となるアイデアを含む別の質問への回答へのリンクがあります
マルティノー

1
まあ、少なくともの場合、属性が定義されると呼び出されなくなるというcached_module_property事実__getattr__()は役に立ちます。(functools.cached_property達成することと同様)。
マルティノー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.