0

同じインターフェイスをエクスポートしている複数のスクリプトがあり、それらは隔離されたスコープで execfile() を使用して実行されます。

問題は、新しいスクリプトごとに最初からそれらを再度ロードする必要がないように、いくつかのリソースを共有することです。これにより、開始速度が低下し、不要な量の RAM が使用されます。

スクリプトは実際には、以下の例に示すよりもはるかにうまくカプセル化され、悪意のあるプラグインから保護されています。ここから問題が始まります。

問題は、リソースを作成するスクリプトで、データを入力したり、データを削除したり、リソースを削除したり、もちろんそのデータにアクセスしたりできるようにすることです。

ただし、他のスクリプトは、別のスクリプト リソースを変更することはできません。それを読み取るだけです。新しくインストールされたプラグインが、共有リソースの乱用によって、既にロードされ実行されているプラ​​グインに干渉できないようにしたいと考えています。

例:

class SharedResources:
    # Here should be a shared resource manager that I tried to write
    # but got stuck. That's why I ask this long and convoluted question!
    # Some beginning:
    def __init__ (self, owner):
        self.owner = owner

    def __call__ (self):
        # Here we should return some object that will do
        # required stuff. Read more for details.
        pass

class plugin (dict):
    def __init__ (self, filename):
        dict.__init__(self)
        # Here some checks and filling with secure versions of __builtins__ etc.
        # ...
        self["__name__"] = "__main__"
        self["__file__"] = filename
        # Add a shared resources manager to this plugin
        self["SharedResources"] = SharedResources(filename)
        # And then:
        execfile(filename, self, self)

    # Expose the plug-in interface to outside world:
    def __getattr__ (self, a):
        return self[a]
    def __setattr__ (self, a, v):
        self[a] = v
    def __delattr__ (self, a):
        del self[a]
    # Note: I didn't use self.__dict__ because this makes encapsulation easier.
    # In future I won't use object itself at all but separate dict to do it. For now let it be

----------------------------------------
# An example of two scripts that would use shared resource and be run with plugins["name"] = plugin("<filename>"):
# Presented code is same in both scripts, what comes after will be different.

def loadSomeResource ():
    # Do it here...
    return loadedresource

# Then Load this resource if it's not already loaded in shared resources, if it isn't then add loaded resource to shared resources:
shr = SharedResources() # This would be an instance allowing access to shared resources
if not shr.has_key("Default Resources"):
    shr.create("Default Resources")
if not shr["Default Resources"].has_key("SomeResource"):
    shr["Default Resources"].add("SomeResource", loadSomeResource())
resource = shr["Default Resources"]["SomeResource"]
# And then we use normally resource variable that can be any object.
# Here I Used category "Default Resources" to add and/or retrieve a resource named "SomeResource".
# I want more categories so that plugins that deal with audio aren't mixed with plug-ins that deal with video for instance. But this is not strictly needed.
# Here comes code specific for each plug-in that will use shared resource named "SomeResource" from category "Default Resources".
...
# And end of plugin script!
----------------------------------------

# And then, in main program we load plug-ins:
import os
plugins = {} # Here we store all loaded plugins
for x in os.listdir("plugins"):
    plugins[x] = plugin(x)

2 つのスクリプトがプラグイン ディレクトリに格納されており、どちらもメモリにロードされた WAVE ファイルを使用しているとします。最初に読み込まれるプラグインは、WAVE を読み込み、RAM に配置します。他のプラグインは、既にロードされている WAVE にアクセスできますが、それを置き換えたり削除したりすることはできず、他のプラグインを台無しにします。

ここで、各リソースに所有者、プラグイン スクリプトの ID またはファイル名を設定し、このリソースはその所有者だけが書き込みできるようにします。

他のプラグインが最初のプラグインにアクセスできるようにする調整や回避策はありません。

私はほとんどそれをやりましたが、行き詰まりました。私の頭は、実装されたときに部分的にしか機能しないという概念でぐるぐる回っています。これは私を食べてしまうので、これ以上集中できません。どんな提案でも大歓迎です!

追加:

これは、安全性を含めずに現在使用しているものです。

# Dict that will hold a category of resources (should implement some security):
class ResourceCategory (dict):
    def __getattr__ (self, i): return self[i]
    def __setattr__ (self, i, v): self[i] = v
    def __delattr__ (self, i): del self[i]

SharedResources = {} # Resource pool

class ResourceManager:
    def __init__ (self, owner):
        self.owner = owner

    def add (self, category, name, value):
        if not SharedResources.has_key(category):
            SharedResources[category] = ResourceCategory()
        SharedResources[category][name] = value

    def get (self, category, name):
        return SharedResources[category][name]

    def rem (self, category, name=None):
        if name==None: del SharedResources[category]
        else: del SharedResources[category][name]

    def __call__ (self, category):
        if not SharedResources.has_key(category):
            SharedResources[category] = ResourceCategory()
        return SharedResources[category]

    __getattr__ = __getitem__ = __call__

    # When securing, this must not be left as this, it is unsecure, can provide a way back to SharedResources pool:
    has_category = has_key = SharedResources.has_key

プラグイン カプセル:

class plugin(dict):
    def __init__ (self, path, owner):
        dict.__init__()
        self["__name__"] = "__main__"
        # etc. etc.
        # And when adding resource manager to the plugin, register it with this plugin as an owner
        self["SharedResources"] = ResourceManager(owner)
        # ...
        execfile(path, self, self)
        # ...

プラグイン スクリプトの例:

#-----------------------------------
# Get a category we want. (Using __call__() ) Note: If a category doesn't exist, it is created automatically.
AudioResource = SharedResources("Audio")
# Use an MP3 resource (let say a bytestring):
if not AudioResource.has_key("Beep"):
    f = open("./sounds/beep.mp3", "rb")
    Audio.Beep = f.read()
    f.close()
# Take a reference out for fast access and nicer look:
beep = Audio.Beep # BTW, immutables doesn't propagate as references by themselves, doesn't they? A copy will be returned, so the RAM space usage will increase instead. Immutables shall be wrapped in a composed data type.

これは完全に機能しますが、前述したように、リソースを台無しにするのは簡単すぎます。

ResourceManager() のインスタンスが、保存されたデータのどのバージョンを返すかを担当したいと思います。

4

1 に答える 1

1

したがって、私の一般的なアプローチはこれになります。

  1. 中央の共有リソース プールを用意します。このプールを介したアクセスは、すべてのユーザーに対して読み取り専用になります。共有プール内のすべてのデータをラップして、「ルールに従っている」人がその中の何かを編集できないようにします。

  2. 各エージェント (プラグイン) は、ロード時に「所有」するものに関する知識を維持します。それ自体の読み取り/書き込み参照を保持し、リソースへの参照を集中型の読み取り専用プールに登録します。

  3. プラグインが読み込まれると、新しいリソースを登録できる中央の読み取り専用プールへの参照が取得されます。

したがって、Python ネイティブ データ構造 (カスタム クラスのインスタンスではない) の問題のみに対処すると、読み取り専用実装のかなりロックダウンされたシステムは次のようになります。それらをロックダウンするために使用されるトリックは、誰かがロックを回避するために使用できるトリックと同じであることに注意してください。

import collections as _col
import sys

if sys.version_info >= (3, 0):
    immutable_scalar_types = (bytes, complex, float, int, str)
else:
    immutable_scalar_types = (basestring, complex, float, int, long)

# calling this will circumvent any control an object has on its own attribute lookup
getattribute = object.__getattribute__

# types that will be safe to return without wrapping them in a proxy
immutable_safe = immutable_scalar_types

def add_immutable_safe(cls):
    # decorator for adding a new class to the immutable_safe collection
    # Note: only ImmutableProxyContainer uses it in this initial
    # implementation
    global immutable_safe
    immutable_safe += (cls,)
    return cls

def get_proxied(proxy):
    # circumvent normal object attribute lookup
    return getattribute(proxy, "_proxied")

def set_proxied(proxy, proxied):
    # circumvent normal object attribute setting
    object.__setattr__(proxy, "_proxied", proxied)

def immutable_proxy_for(value):
    # Proxy for known container types, reject all others
    if isinstance(value, _col.Sequence):
        return ImmutableProxySequence(value)
    elif isinstance(value, _col.Mapping):
        return ImmutableProxyMapping(value)
    elif isinstance(value, _col.Set):
        return ImmutableProxySet(value)
    else:
        raise NotImplementedError(
            "Return type {} from an ImmutableProxyContainer not supported".format(
                type(value)))

@add_immutable_safe
class ImmutableProxyContainer(object):

    # the only names that are allowed to be looked up on an instance through
    # normal attribute lookup
    _allowed_getattr_fields = ()

    def __init__(self, proxied):
        set_proxied(self, proxied)

    def __setattr__(self, name, value):
        # never allow attribute setting through normal mechanism
        raise AttributeError(
            "Cannot set attributes on an ImmutableProxyContainer")

    def __getattribute__(self, name):
        # enforce attribute lookup policy
        allowed_fields = getattribute(self, "_allowed_getattr_fields")
        if name in allowed_fields:
            return getattribute(self, name)
        raise AttributeError(
            "Cannot get attribute {} on an ImmutableProxyContainer".format(name))

    def __repr__(self):
        proxied = get_proxied(self)
        return "{}({})".format(type(self).__name__, repr(proxied))

    def __len__(self):
        # works for all currently supported subclasses
        return len(get_proxied(self))

    def __hash__(self):
        # will error out if proxied object is unhashable
        proxied = getattribute(self, "_proxied")
        return hash(proxied)

    def __eq__(self, other):
        proxied = get_proxied(self)
        if isinstance(other, ImmutableProxyContainer):
            other = get_proxied(other)
        return proxied == other


class ImmutableProxySequence(ImmutableProxyContainer, _col.Sequence):

    _allowed_getattr_fields = ("count", "index")

    def __getitem__(self, index):
        proxied = get_proxied(self)
        value = proxied[index]
        if isinstance(value, immutable_safe):
            return value
        return immutable_proxy_for(value)


class ImmutableProxyMapping(ImmutableProxyContainer, _col.Mapping):

    _allowed_getattr_fields = ("get", "keys", "values", "items")

    def __getitem__(self, key):
        proxied = get_proxied(self)
        value = proxied[key]
        if isinstance(value, immutable_safe):
            return value
        return immutable_proxy_for(value)

    def __iter__(self):
        proxied = get_proxied(self)
        for key in proxied:
            if not isinstance(key, immutable_scalar_types):
                # If mutable keys are used, returning them could be dangerous.
                # If owner never puts a mutable key in, then integrity should
                # be okay. tuples and frozensets should be okay as keys, but
                # are not supported in this implementation for simplicity.
                raise NotImplementedError(
                    "keys of type {} not supported in "
                    "ImmutableProxyMapping".format(type(key)))
            yield key


class ImmutableProxySet(ImmutableProxyContainer, _col.Set):

    _allowed_getattr_fields = ("isdisjoint", "_from_iterable")

    def __contains__(self, value):
        return value in get_proxied(self)

    def __iter__(self):
        proxied = get_proxied(self)
        for value in proxied:
            if isinstance(value, immutable_safe):
                yield value
            yield immutable_proxy_for(value)

    @classmethod
    def _from_iterable(cls, it):
        return set(it)

注: これは Python 3.4 でのみテストされていますが、Python 2 と 3 の両方と互換性があるように記述しようとしました。

共有リソースのルートをディクショナリにします。ImmutableProxyMappingその辞書をプラグインに渡します。

private_shared_root = {}
public_shared_root = ImmutableProxyMapping(private_shared_root)

public_shared_rootおそらく先着順で、プラグインが新しいリソースを に登録できる API を作成します(既に存在する場合は登録できません)。private_shared_root必要になることがわかっているコンテナ、またはすべてのプラグインと共有したいが読み取り専用にしたいことがわかっているデータを事前に入力します。

/home/dalen/local/python共有ルート マッピングのキーの規則が、ファイル システム パス ( ) や Python ライブラリ オブジェクトのようなドット パス( ) などのすべての文字列であると便利な場合がありますos.path.expanduser。そうすれば、プラグインが同じリソースをプールに追加しようとした場合、衝突の検出は即時かつ自明です。

于 2015-09-06T04:38:42.013 に答える