【问题标题】:define property (with decorator) conditionally (e.g. try/except?)有条件地定义属性(使用装饰器)(例如尝试/除外?)
【发布时间】:2019-03-30 23:08:12
【问题描述】:

我正在寻找一种在 python 中有条件地定义属性的方法。 这个想法是在 try/except 块中定义属性

class PyObject(SomeOtherObj):

    def __init__(self, dbaObj):

        super(SomeOtherObj, self).__init__()

        self._CreateAttributes()

    def _CreateAttributes(self):

        try:
            self.GetProperty("MyProperty") #This method comes from the parent
        except:
            pass
        else:
            @property
            def MyProperty(self):
                return self.GetProperty("MyProperty")

            @MyProperty.setter
            def MyProperty(self, value):
                return self.ModifyProperty("MyProperty", value) #This method comes from the parent

我不知道在父对象中定义了哪些属性,因此我需要有条件地构建属性的东西。也欢迎任何其他解决方案。

[编辑] 另一个尝试...但是不仅属性被创建,而且尝试访问它会引发无限递归错误

class PyObject(SomeOtherObj):

    def __init__(self, dbaObj):

        super(SomeOtherObj, self).__init__()

        @property
        def MyProperty(self):
            try:
                return self.GetProperty("MyProperty")
            except:
                del self.MyProperty

        @MyProperty.setter
        def MyProperty(self, value):
             return self.ModifyProperty("MyProperty", value) #This method comes from the parent

         @MyProperty.deleter
         def MyProperty(self):
              self.__delattr__(self.MyProperty)

[EDIT2] 我在父级中有一个方法,可以让我知道哪些是属性。为了这个例子,假设我在SomeOtherObj C++ 类中有一个方法ListAttributes,它返回由父类动态创建的属性名称(字符串)列表。

【问题讨论】:

  • "我不知道父对象内部定义了哪些属性" => 这怎么可能?您只需要阅读父类的文档(或只是源代码)。
  • 这是一个 C++ 工厂,它根据变量输入启动其他 C++ 对象...我对它没有太多控制权。但是我认为这与我的问题无关(这也可能具有理论意义)
  • 好吧,有道理。

标签: python oop properties


【解决方案1】:

您可以将 python 类视为具有特殊绑定的代码命名空间。你可以在类主体中编写几乎任何东西(想想一个循环,以获得额外的疯狂),它将在类导入时执行(实际上是在模块导入中)。您唯一的问题是您无法在子类闭包中轻松访问父类(不使用元类)。

所以,如果你需要制作快速而肮脏的补丁,你实际上可以制作try/except like:

class Child(Parent):
    try:
        Parent.get_property
        @property
        def my_property(self):
            return self.get_property()

        @my_property.setter
        def my_property(self, value):
            self.set_property(value)
    except:
        pass

第一个问题——父类是硬编码的,如果你改变继承,你需要改变实现。这实际上是一个糟糕的设计,但如果你几乎没有约束力,那对你来说没问题。

更大的问题是,这样的界面很难使用。在大多数情况下,如果您提供属性/方法,您的用户会期望类具有此属性。仅仅因为某些父类在某处缺少方法而不断使用if/elsetry/except 并不好玩。而这部分无法按照你现在的方式修复。

所以,要考虑的主要问题是——当父类行为未知时,情况如何?如果它在安装时为用户所知,请考虑提供两个不同的子类,内部没有任何意外。

如果它仅在运行时已知,为什么还要费心检查呢?用户仍然会使用

try:
    child_obj.my_property
except AttributeError:
    ...

捕捉你的flappy界面,但有趣的是缺少父getter会产生同样的异常,如此简单

class Child(Parent):
    @property
    def my_property(self):
        return self.get_property()

    @my_property.setter
    def my_property(self, value):
        self.set_property(value)

使用几乎相同

【讨论】:

    【解决方案2】:

    怎么样:

    class A:
        def stuff(self):
            pass
    
    a = A()   
    
    
    if hasattr(a, 'another'):
        print(a.another)
    else:
        A.another = property(lambda self: 1)
        print(a.another)
    

    您可以通过在运行时设置属性来简单地修补类

    Result: 1
    

    【讨论】:

      【解决方案3】:

      第一点:@decorator 语法没有什么神奇之处,它只是语法糖,所以

      @decorator
      def func():
          pass
      

      实际上只是一个方便的快捷方式:

      def func():
          pass
      func = decorator(func)
      

      第二点:property 类型是the descriptor protocol 的通用实现(而且相当简单——这里再次强调,不涉及魔法)。描述符仅在解析为类属性时“起作用”,因此将属性设置为实例属性将不起作用,期间(查找它们将返回描述符对象本身,它不会调用描述符的 __get____set__ 方法)。

      第三点:您的代码甚至没有将创建的属性设置为实例属性(无论如何都不起作用),它们只是简单的 local 名称,仅在 _CreateAttributes 方法执行期间存在.

      这些都不能解决您的问题 - 您必须为某人提供更多上下文才能发布保证适用于您的具体用例的解决方案(基于此 C++ 父类的实际实现方式等) - 但至少你现在知道为什么你的尝试失败了。

      编辑:

      我在父级中有一个方法,可以让我知道哪些是属性。为了这个例子,假设我在 SomeOtherObj C++ 类中有一个 ListAttributes 方法,它返回由 Parent 类动态创建的属性名称(字符串)列表。

      如果那是类方法或静态方法,您可以使用类装饰器来创建属性(或者,为了减少样板,使用自定义描述符):

      class Parent(object):
          def __init__(self):
              self._props = {"foo": 42, "bar": None}
      
          @classmethod
          def ListAttributes(cls):
              return ["foo", "bar"]
      
          def GetProperty(self, name):
              return self._props[name]
      
          def ModifyProperty(self, name, value):
              self._props[name] = value
      
      
      class Prop(object):
          def __init__(self, name):
              self.name = name
      
          def __get__(self, instance, cls):
              return instance.GetProperty(self.name)
      
          def __set__(self, instance, value):
              instance.ModifyProperty(self.name, value)
      
      def setprops(cls):
          parent = cls.__bases__[0]
          for name in parent.ListAttributes():
              setattr(cls, name, Prop(name))
          return cls
      
      @setprops
      class Child(Parent):
          pass
      
      
      c = Child()
      print("foo : {}".format(c.foo))
      print("bar : {}".format(c.bar))
      
      c.bar = "yay!"
      print("bar: {}".format(c.bar))
      

      如果Parent.ListAttributes 是实例方法,那么您仍然可以使用__getattr____setattr__ 特殊方法:

      class Parent2(object):
          def __init__(self):
              self._props = {"foo": 42, "bar": None}
      
          def ListAttributes(self):
              return ["foo", "bar"]
      
          def GetProperty(self, name):
              return self._props[name]
      
          def ModifyProperty(self, name, value):
              self._props[name] = value
      
      
      class Child2(Parent2):
          def __getattr__(self, name):
              if name in self.ListAttributes():
                  return self.GetProperty(name)
              raise AttributeError("object {} has no attribute {}".format(type(self), name))
      
          def __setattr__(self, name, value):
              if name in self.ListAttributes():
                  self.ModifyProperty(name, value)
              super(Child2, self).__setattr__(name, value)
      
      
      
      c = Child2()
      print("foo : {}".format(c.foo))
      print("bar : {}".format(c.bar))
      
      c.bar = "yay!"
      print("bar: {}".format(c.bar))
      
      # check we didn't break the default __setattr__
      c.baaz = "ok"
      print("baaz: {}".format(c.baaz))
      

      注意:__getattr__ 仅在所有其他查找失败时才作为最后手段调用,因此它几乎无害,但 __setattr__ 是默认属性设置器,因此您必须确保明智地使用它。

      【讨论】:

      • 这非常有用,谢谢。我对这件事有了更清晰的认识。
      • 我在原始帖子中添加了一个编辑
      • @mg91 我适当地更新了我的答案。我不能保证这将适用于您自己的 Parent 类,但它确实有机会。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-01-23
      • 2013-04-10
      • 2016-12-17
      • 2018-07-20
      • 2011-07-11
      • 2020-11-30
      • 1970-01-01
      相关资源
      最近更新 更多