【问题标题】:How to fake/proxy a class in Python如何在 Python 中伪造/代理一个类
【发布时间】:2012-03-30 12:01:40
【问题描述】:

我写了一些包装器,它有另一个对象作为属性。此包装器将所有带有__getattr____setattr__ 的属性请求代理(转发)到作为属性存储的对象。我还需要为我的代理提供什么,以使包装器在通常情况下看起来像被包装的类?

我想我需要修复继承之类的问题,也许是__repr__,... 我还需要注意什么以及如何修复继承以使instanceof() 正常工作?

编辑:我尝试做一个函数代理,但是由于我不完全理解配方,它失败了:(

setattr_=object.__setattr__
getattr_=object.__getattribute__

class Proxy(object):
    __slots__=["_func", "_params", "_kwargs", "_obj", "_loaded", "__weakref__"]
    def __init__(self, func, *params, **kwargs):
        setattr_(self, "_func", func)
        setattr_(self, "_params", params)
        setattr_(self, "_kwargs", kwargs)

        setattr_(self, "_obj", None)
        setattr_(self, "_loaded", False)

    def _get_obj(self):
        if getattr_(self, "_loaded")==False:
            print("Loading")
            setattr_(self, "_obj", getattr_(self, "_func")(*getattr_(self, "_params"), **getattr_(self, "_kwargs")))
            setattr_(self, "_loaded", True)

        return getattr_(self, "_obj")
    #
    # proxying (special cases)
    #
    def __getattribute__(self, name):
        return getattr(getattr_(self, "_get_obj")(), name)
    def __delattr__(self, name):
        delattr(getattr_(self, "_get_obj")(), name)
    def __setattr__(self, name, value):
        setattr(getattr_(self, "_get_obj")(), name, value)

    def __nonzero__(self):
        return bool(getattr_(self, "_get_obj")())
    def __str__(self):
        return str(getattr_(self, "_get_obj")())
    def __repr__(self):
        return repr(getattr_(self, "_get_obj")())

    #
    # factories
    #
    _special_names=[
        '__abs__', '__add__', '__and__', '__call__', '__cmp__', '__coerce__',
        '__contains__', '__delitem__', '__delslice__', '__div__', '__divmod__',
        '__eq__', '__float__', '__floordiv__', '__ge__', '__getitem__',
        '__getslice__', '__gt__', '__hash__', '__hex__', '__iadd__', '__iand__',
        '__idiv__', '__idivmod__', '__ifloordiv__', '__ilshift__', '__imod__',
        '__imul__', '__int__', '__invert__', '__ior__', '__ipow__', '__irshift__',
        '__isub__', '__iter__', '__itruediv__', '__ixor__', '__le__', '__len__',
        '__long__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__',
        '__neg__', '__oct__', '__or__', '__pos__', '__pow__', '__radd__',
        '__rand__', '__rdiv__', '__rdivmod__', '__reduce__', '__reduce_ex__',
        '__repr__', '__reversed__', '__rfloorfiv__', '__rlshift__', '__rmod__',
        '__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__',
        '__rtruediv__', '__rxor__', '__setitem__', '__setslice__', '__sub__',
        '__truediv__', '__xor__', 'next',
    ]

    @classmethod
    def _create_class_proxy(cls, theclass):
        """creates a proxy for the given class"""

        def make_method(name):
            def method(self, *args, **kw):
                return getattr(getattr_(self, "_get_obj")(), name)(*args, **kw)
            return method

        namespace={}
        for name in cls._special_names:
            if hasattr(theclass, name):
                namespace[name]=make_method(name)
        return type("%s(%s)"%(cls.__name__, theclass.__name__), (cls,), namespace)

    def __new__(cls, obj, *args, **kwargs):
        """
        creates an proxy instance referencing `obj`. (obj, *args, **kwargs) are
        passed to this class' __init__, so deriving classes can define an 
        __init__ method of their own.
        note: _class_proxy_cache is unique per deriving class (each deriving
        class must hold its own cache)
        """
        try:
            cache=cls.__dict__["_class_proxy_cache"]
        except KeyError:
            cls._class_proxy_cache=cache={}
        try:
            theclass=cache[obj.__class__]
        except KeyError:
            cache[obj.__class__]=theclass=cls._create_class_proxy(obj.__class__)
        ins=object.__new__(theclass)
        theclass.__init__(ins, obj, *args, **kwargs)
        return ins

if __name__=='__main__':
    def t(x, y):
        print("Running t")
        return x+y

    a=Proxy(t, "a", "b")
    print("Go")
    print(a+"c")

【问题讨论】:

  • 出于兴趣,你为什么不直接继承?
  • 因为这个包装器应该包装各种对象。它是一个通用代理。
  • 你稍微改变了配方。看起来你真正想要的是functools.partial,但也许不是;你真正想要解决的问题是什么?
  • 我想装饰一个函数,让它懒惰地执行。它适用于某种配置文件,我不想从 dict 执行所有值元素(函数调用)。它可以在各种嵌套级别中,因此我不需要专门的机制。同时,我认为如果我删除 if hasattr(cls, name) 行,该配方会起作用。但是我不确定我在做什么:)

标签: python


【解决方案1】:

这个问题可以通过这个方法很好地解决:

Object Proxying (Python recipe)

您必须遵循的一般想法是,类上的大多数方法都是通过类本身或其元类上的__getattr____getattribute__ 的某种组合访问的,但这不适用于 python 特殊方法,以双下划线开头和结尾的,要找到的,必须是实际类的实际方法,不能进行属性代理。

您必须提供哪些方法显然取决于代理类本身提供的哪些方法。您需要为isinstance() 提供的特殊方法是__instancecheck__ and __subclasscheck__ 方法。要使repr() 工作,您还必须在代理类本身上定义一个适当的__repr__()

【讨论】:

  • 这似乎很有趣,但是我发现很难理解所有细节:(我尝试将其作为函数调用的代理 - 请参阅编辑。但是,我想我需要生成特殊名称在对象初始化后不知何故。可以帮助我了解我需要更改什么吗?
【解决方案2】:

通常,您可以使用 wrapt 库 (pypi),它会为您完成繁重的工作:

wrapt 模块非常注重正确性。因此,它超越了诸如 functools.wraps() 之类的现有机制,以确保装饰器保留自省性、签名、类型检查能力等。 [...]

为确保开销尽可能小,C 扩展模块用于性能关键组件

supports creating custom wrapper classes。为了添加您自己的属性,您需要以wrapt 不会尝试将它们传递给包装实例的方式声明它们。你可以:

  • 为属性添加前缀_self_ 并添加属性以供访问
  • 在类级别声明属性,除了在__init__
  • 如果适合您的课程,请使用插槽(文档中未提及),如下所示:

    class ExtendedMesh(ObjectProxy):
        __slots__ = ('foo')
    
        def __init__(self, subject):
            super().__init__(subject)
            self.foo = "bar"
    

它也是supports function wrappers,这可能适合你的目的。

【讨论】:

    【解决方案3】:

    以防其他人正在寻找更完整的代理实现

    虽然有几个类似于 OP 正在寻找的 python 代理解决方案,但我找不到同时代理 classes 和任意 class 对象 的解决方案作为自动代理函数返回值参数。这是我需要的。

    我已经为此目的编写了一些代码,作为完整的代理/日志记录 python 执行的一部分,将来我可能会将它变成一个单独的库。如果有人感兴趣,您可以在pull request 中找到代码的核心。如果您希望将此作为独立库,请给我留言。

    我的代码将自动为任何代理对象的属性、函数和类返回包装器/代理对象。目的是记录和重放一些代码,所以我有等效的“重放”代码和一些将所有代理对象存储到 json 文件的逻辑。

    【讨论】:

      猜你喜欢
      • 2011-08-17
      • 1970-01-01
      • 1970-01-01
      • 2014-05-26
      • 2011-06-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-26
      相关资源
      最近更新 更多