【问题标题】:Python class attribute validation on __init____init__ 上的 Python 类属性验证
【发布时间】:2018-07-05 09:04:34
【问题描述】:

我正在尝试在下面的代码中使用 setter 验证我的类的一个属性。我要验证的属性称为“__x”,并设置为通过“init”方法传递的参数。当我将 'self__x' 更改为 'self.x' 时,它会按我的预期工作。我想要的是它如何与'self.x'一起工作,而我没有在getter和setter方法的任何地方返回'x'属性,为什么它不能与'self.__x'一起工作?

class P:
    def __init__(self, x):
        self.__x = x  # not working
        # self.x = x  # working

    @property
    def x(self):
        return self.__x

    @x.setter
    def x(self, x):
        if x < 0:
            self.__x = 0
        else:
            self.__x = x


p = P(-5)
print(p.x)  # prints -5

【问题讨论】:

  • 如果您输入self.x = x,您将调用您编写的设置器。如果您输入self.__x = x,则表示属性self.__x 正在包装x
  • 但是当我将 self.x = x 并在 setter 中说 self__x = 0 时,为什么它不返回 AttributeError?这是我无法理解的一点。
  • 为什么会报错?您可以分配您喜欢的任何属性。 self.__x = x 为您的对象创建属性 __x
  • 有些类不能有属性,例如整数。否则,属性在 Python 中是免费的。如果您来自其他语言,例如 Java(只能具有您在类中定义的属性)或 Ruby(可以具有它想要的任何属性,但它们都是私有的),这可能会很奇怪。
  • 正如我从您的回答中了解到的那样,python 在“init”函数之后隐式调用“setter”和“getter”(如果它们存在)。而在“property”装饰器下定义的“x”实际上覆盖了“init”函数中定义的“x”属性。这是真的吗?

标签: python oop getter-setter


【解决方案1】:

是这样的。想象一下有一个校园恶霸,我们就叫他丹吧,他以你为目标。还有你很喜欢的贝丝。通常情况下,您想避开 Dan 并与 Beth 见面,但 Dan 不在乎,如果他看到您,他会打您的头。

现在你也和乔交朋友了。他是个温柔的巨人。非常好的人。他说到他的地方来,他会确保他不让丹进来。一切都很好:当丹走到乔的门口时,他已经转身离开了。贝丝来了,乔让她进来。

关键是:它只在丹打开门时有效。如果您听到门铃并且您自己出去,它就不再起作用了。


所以在这里,如果你选择self.x = -5,乔会检查号码,发现是丹,然后用零给他打包。但如果你这样做self.__x = -5,乔永远不会看到丹。你的头会受到打击。

self.__x 只是一个变量,它自己不能做任何检查。 self.x 虽然是一个函数(其中有两个,一个用于阅读,一个用于编写),它可以做任何想做的事情 - 设置 self.__x 或拒绝。

【讨论】:

    【解决方案2】:

    让我们从“@decorator”语法开始。它实际上只是语法糖,所以

    @decorate
    def myfunc():
        pass
    

    只是简写

    def myfunc():
        pass
    
    myfunc = decorate(myfunc)
    

    请注意,python 函数也是对象(以及 FWIW 的类和模块),因此您可以将函数作为参数传递给其他函数、从函数返回函数、将函数存储为变量或属性等。

    现在有了property 类(是的,它是一个类):它只是the descriptor protocol 的通用实现,这是支持计算属性的python 机制。

    property 的朴素 python 实现大多看起来像(我忽略了 fdel__del__ 部分):

    class propertytype(type):
        # this is what will allow you 
        # to use `property` as decorator,
        # it will return a new `property` instance
        # with `func` as setter
        def __call__(cls, func):
            return cls(func)
    
    class property(metaclass=propertytype):
    
        def __init__(self, fget, fset=None):
            self.fget = fget
            self.fset = fset
    
       # this is the getter
       def __get__(self, instance, cls=None):
           if instance is None:
               return self
           return self.fget(instance)
    
       # this is the setter (if there's one)
       def __set__(self, instance, value):
           if not self.fset:
               raise AttributeError("Attribute is read-only")
           self.fset(instance, value)
    
       # and this allows you to use`@myprop.setter` 
       # in your class definition
       def setter(self, func):
           self.fset = func
           return self
    

    最后:虽然在初始化程序中创建对象的所有实例属性是一种很好的做法(__init__ 方法),但您实际上可以随时随地设置现有或新属性。除了少数类型(主要是出于实现原因)使用完全不同的方式来存储属性(如果您想了解更多信息,可以查找slots),普通的 Python 对象主要是变相的字典,所以myobj.foo = 'bar' 通常只会将 'bar' 存储在 self.__dict__['foo'] 中。好吧,如果你不使用计算属性,当然 ;)

    好的,现在我们有了构建模块,让我们来分析一下你的班级发生了什么:

    class P:
        # let's ignore the initializer for now
    
        @property
        def x(self):
            return self.__x
    
        @x.setter
        def x(self, x):
            if x < 0:
                self.__x = 0
            else:
                self.__x = x
    

    这可以改写为

    class P:
        # let's ignore the initializer for now
    
        def _getx(self):
            return self.__x
    
        def _setx(self):
            if x < 0:
                self.__x = 0
            else:
                self.__x = x
    
        x = property(_getx, setx)
    

    所以现在

    p = P()
    

    当我们这样做时:

    p.x = 5
    

    属性解析规则(在object.__setattr__(self, name, value)中实现)实际上会在“P”上查找“x”,找到我们的“x”属性,因为它是一个绑定描述符(它有一个__set__方法),所以调用@ 987654341@,它又会调用self.fset(p, 5)(参见property.__set__()定义),它会调用p._setx(5)

    如果我们找回了初始化器:

    class P:
        def __init__(self, x):
            self.x = x
    
        # getter / setter / property definition  here
    

    然后发生了非常确切的事情(除了 P 实例被命名为 self 而不是 p ) - 它实际上最终调用 P._setx(self, x)

    与原始实现的唯一区别是使用 property 有一个装饰器,getter 和 setter 函数不会成为类的方法,它们仅作为 @ 的 fgetfset 属性存在987654353@ 属性对象。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-01
      • 1970-01-01
      • 2020-06-04
      • 2018-02-23
      • 1970-01-01
      • 2014-01-28
      相关资源
      最近更新 更多