【问题标题】:Writing a thin object proxy by implementing the __get__ descriptor通过实现 __get__ 描述符来编写瘦对象代理
【发布时间】:2012-07-06 10:31:20
【问题描述】:

我正在尝试使用__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 用于 selenium 测试用例。我想为测试作者提供一种简洁的方式来编写表单的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()

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

【问题讨论】:

  • 如您所见,描述符只能用作类属性。如果您能提供更多详细信息,将更容易为您找到解决方案。
  • 在python中创建代理对象的常用方法是使用__getattr__(也可能是__setattr__)模拟代理对象的行为。除非在新式类的成员案例中,否则您的方式将行不通。
  • 为什么不试试 y = Value((2,1)) 而不是 y = [ Value(2), Value(3) ]

标签: python selenium


【解决方案1】:

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

self._x 和 self._y 是 true 值的内部私有访问器

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 ,那么代理它会更简单一些,并且对于用户访问它时发生的事情更加明确。

【讨论】:

    【解决方案2】:

    Python_noob 的回答很好。

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

    问题在于描述符协议只是与类属性“工作”。在您放置的示例 sn-p 中,类属性是列表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 实例)

    【讨论】:

      【解决方案3】:

      我解决了 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 
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多