通过实现__get__描述符编写瘦对象代理

时间:2012-07-06 00:00:45

标签: python selenium

我正在尝试使用__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。我想要提供的是一种干净而简单的方法,供测试作者编写以下形式的PageObject:

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进行分组。

3 个答案:

答案 0 :(得分:2)

如果您想代理数组访问器,则需要添加其他代理。这是一个使用property()表示x的例子,因为它只是一个属性,而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,那么代理它就会更简单一些,并且当用户访问它时会更清楚地知道发生了什么。

答案 1 :(得分:1)

Python_noob的答案很好。

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

问题是描述符协议只对“类”属性“起作用”。在您放置的示例代码段中,class属性是列表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.

请注意,序列类无法自动知道它属于哪个实例 - 因此必须在实例化时明确设置。 (您的示例中的值会因其__init__方法遭遇相同的命运:它不知道其Container实例)

答案 2 :(得分:0)

我选择了jsbueno& amp; 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