关于python:如何“打开”第三方库的基于__slots__的对象实例进行属性赋值?

How do I “open up” 3rd party libraries' __slots__ -based object instances for attribute assignment?

假设我想为第三方库返回的对象实例分配新的属性,第三方库使用槽返回实例。

(例如,为不打算写回数据库的数据在sqlachemy的rowproxy行上设置一些值。)

因为这个实例有__slots__,所以我将得到一个attributeError之类的。这是意料之中的,但什么是变通办法?

期望行为/约束

  • 考虑到第三方库是一个黑盒,不能更改其返回类型。
  • 尽可能保留原始实例的行为(方法、属性等…)。
  • 分配给槽中的属性会影响原始实例的属性。
  • 这就是我想到的,proxy2可以工作,但是有没有更优雅/更简单的东西?它断裂的风险是什么?它能在python 3上工作吗(我在2.7上,但最终计划升级到3.x)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    class Slotted(object):
        __slots__ = ("a","b")

        def hello(self):
            print("hello %s" % (self.a))

        def __init__(self, a="1", b="2"):
            self.a = a
            self.b = b

    def test_it(f):

        fname = f.__name__

        print("

    test_it(%s)"
    % (fname))

        instances = [Slotted(a=11), Slotted()]
        try:
            li_test = [f(obj) for obj in instances]
        except Exception, e:
            print("%s.failure at instance modification:%s" % (fname, str(e)[0:100]))
            return

        for cntr, tgt in enumerate(li_test):
            try:
                tgt.cntr = cntr
                print("tgt.cntr:%s" % (tgt.cntr))
                #do I still have my function?
                tgt.hello()
                tgt.a = 100
            except Exception, e:
                print("%s.failure:%s" % (fname, str(e)[0:100]))
                return

        #test that an attribute assignment to the slots actually went there...
        for ori in instances:
            try:
                assert ori.a == 100
            except AssertionError:
                print("%s.failure:original instance should have its slot-based attribute set to 100, but is still %s" % (fname, ori.a))
                break

        print"%s.success" % (fname)

    class Proxy2(object):
       """this works, can it be improved on?"""

        def __init__(self, obj):
            self.__dict__["_obj"] = obj

        def __setattr__(self, attrname, value):
            if attrname in self._obj.__slots__:
                setattr(self._obj, attrname, value)
            else:
                self.__dict__[attrname] = value

        def __getattr__(self, attrname):
            try:
                return getattr(self._obj, attrname)
            except AttributeError:
                raise

    #subclass Slotted
    class Opener(Slotted):
       """fails w Slotted' object layout differs from 'Opener'"""
        pass

    class Opener2(Slotted):
       """fails w Slotted' object layout differs from 'Opener2'"""
        __slots__ = Slotted.__slots__ + ("__dict__",)

    #functions to modify the original instances
    def proxy_instances(obj):
        #this works
        return Proxy2(obj)

    def do_nothing(obj):
        #fails, normal, this is the baseline slots behavior
        return obj

    def modify_class(obj):
        #change the instance's class to a subclass that has a __dict__
        obj.__class__ = Opener

    def modify_class2(obj):
        #change the instance's class to a subclass to add __dict__ to the __slots__
        obj.__class__ = Opener2

    for func in [do_nothing, modify_class, modify_class2, proxy_instances]:
        test_it(func)

    输出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    test_it(do_nothing)
    do_nothing.failure:'Slotted' object has no attribute 'cntr'


    test_it(modify_class)
    modify_class.failure at instance modification:__class__ assignment: 'Slotted' object layout differs from 'Opener'


    test_it(modify_class2)
    modify_class2.failure at instance modification:__class__ assignment: 'Slotted' object layout differs from 'Opener2'


    test_it(proxy_instances)
    tgt.cntr:0
    hello 11
    tgt.cntr:1
    hello 1
    proxy_instances.success


    我将获取从第三方库返回的结果,并将其与我要存储的其他数据一起存储在我自己的某种类型的容器对象(如类或字典)中。

    这种方法将更加明确,直接,易于理解,不那么棘手。因为它不是以一种巧妙的方式修补第三方数据,所以它更可能避免神秘的错误。