3

我正在尝试使用__get__描述符在某种类型的数据上创建一个思考代理,以便客户端代码可以方便地使用它(最好通过示例显示):

class Value(object):
    def __init__(self, val):
        self.val = val
    def __get__(self, instance, owner):
        return self.val

class Container(object):
    x = Value(1)
    y = [ Value(2), Value(3) ]

c = Container()
assert c.x == 1
assert c.y[0] == 2 # fails.

现在,我主要知道它为什么会失败,所以我想知道是否有更好的方法来实现我的目标,即包装数据,这样当客户端访问它时,他们只需直接访问 Value 对象(实际上,方法会在返回之前__get__做一些事情)self.val

细节

我试图解决的实际案例是帮助编写用于硒测试用例的PageObjects和 PageElements 的实用程序代码。我想为测试作者提供一种简洁的方式来编写表单的 PageObjects:

class MyLogin(PageObject):
    username_text_box = PageElement(By.ID, "username")
    password_text_box = PageElement(By.ID, "password")
    submit_button = PageElement(By.CLASS_NAME, "login-button")

driver = webdriver.Firefox()
driver.get("http://mypage.com")
login_page = MyLogin(driver)
login_page.username_text_box.send_keys("username01")
login_page.password_text_box.send_keys("XXXX")
login_page.submit_button.click()

我正在尝试将在 PageElement 类中获取实际WebElement的业务封装起来,并且我希望允许客户端在访问 PageElement 对象后能够访问 WebElement。能够拥有列表,字典有助于使 PageObject 实例更清晰,并对相关的 PageElements 进行分组。

4

3 回答 3

2

如果您还想代理数组访问器,则需要添加一个额外的代理。这是一个使用 x 的 property() 的示例,因为它只是一个属性,以及 y 的数组代理访问器。

self._ x 和 self. _y 是真实值的内部私有访问器

import functools

class array_proxy(object):
    def __init__(self, accessor):
        self.accessor = accessor

    def __getitem__(self, key):
        return self.accessor(key)

class Value(object):
    def __init__(self, val):
        self.val = val

class Container(object):
    def __init__(self):
        self.__x = Value(1)
        self.__y = [Value(2), Value(3)]
        self.y = array_proxy(functools.partial(Container.get_y_element, self))

    @property
    def x(self):
        val = self.__x.val
        # do stuff with val
        return val

    def get_y_element(self, key):
        val = self.__y[key].val
        # do stuff with val
        return val


c = Container()
assert c.x == 1
assert c.y[0] == 2

如果您可以强制其他人直接使用 get_y_element ,那么代理它会更简单一些,并且对于用户访问它时发生的事情更加明确。

于 2012-07-06T00:47:51.437 回答
1

Python_noob 的回答很好。

它可以变得更简单,更不通用 - 以便于理解。

问题是描述符协议只是与类属性“工作”。在您放置的示例代码段中,类属性是 list y,而不是其中的Value对象。

如果你只是实现可以创建一个序列类来替换__getitem__对项目的调用,__get__你应该得到你想要的行为:

class Value(object):
    def __init__(self, val):
        self.val = val
    def __get__(self, instance, owner):
        return self.val

class ValueList(list):
    def __getitem__(self, item):
        return list.__getitem__(self,item).__get__(self.instance, self.instance.__class__)

class Container(object):
    def __init__(self, *args, **kw):
        self.y = ValueList(self.__class__.y)
        self.y.instance = self
        # ...

    x = Value(1)
    y = [ Value(2), Value(3) ]

c = Container()
assert c.x == 1
assert c.y[0] == 2 # works.

请注意,序列类无法自动知道它属于哪个实例 - 因此必须在实例化时显式设置。(示例中的您的 Value 将在其方法中遭受同样的命运__init__:它不知道其 Container 实例)

于 2012-07-06T03:47:14.567 回答
0

我解决了 jsbueno 和 python_noob 都在接近的实现。然而,他们的两个实现都不符合我允许测试作者编写简单的声明性页面对象的标准。

import inspect
import weakref
class Value(object):
    def __init__(self, val):
        self.val = val
    def __get__(self, instance, owner):
        return self.val

def get_value_from_proxy(obj, inst):
    if isinstance(obj, Value):
        return obj.__get__(inst, inst.__class__)
    else:
        return obj 

class ValueList(list):
    def __init__(self, instance, *args, **kwargs):
        list.__init__(self, *args, **kwargs)
        self.instance = weakref.ref(instance)
    def __getitem__(self, idx):
        return get_value_from_proxy(list.__getitem__(self, idx), self.instance() )

class ValueDict(dict):
    def __init__(self, instance, *args, **kwargs):
        dict.__init__(self, *args, **kwargs)
        self.instance = weakref.ref(instance)
    def __getitem__(self, key):
        return get_value_from_proxy(dict.__getitem__(self, key), self.instance() )

class BaseContainer(object):
    def __init__(self):
        for el in inspect.getmembers(self):
            if isinstance(el[1],list):
                setattr(self, el[0], ValueList(self, el[1]))
            elif isinstance(el[1],dict):
                setattr(self, el[0], ValueDict(self, el[1]))


class Container(BaseContainer):
    x = Value(1)
    y = [ Value(2), Value(3) ]
    z = {"test":Value(4)}

c = Container()
assert c.x == 1
assert c.y[0] == 2
assert c.z["test"] == 4 
于 2012-07-06T13:45:37.620 回答