浅谈Python中的setter、getter、property

面向对象三个重要的特性:封装、继承、多态;

封装就意味着一个类或对象内部的属性或方法,不能随意被外部访问或者修改;java等语言中,可以在定义变量前面加上private来声明变量是私有变量,而python中没有关键字用于声明私有变量,但是约定俗成地使用双下划开头的变量作为私有变量;

比如下面的代码:

class Person(object):
    def __init__(self, age, name):
        self.name = name
        self.__age = age
        
        
if __name__ == '__main__':
    p = Person(18, "张三")
    print("p.name: ", p.name)
    
    p.name = "李四"
    print("p.name: ", p.name)  # 可以被随意修改

    print("p.__age: ", p.__age)

运行代码,报错信息如下:

p.name:  张三
Traceback (most recent call last):
    print("p.__age: ", p.__age)
AttributeError: 'Person' object has no attribute '__age'

但是如果有需要访问__age属性的情况要怎么办?如何保证其内部数据的安全性呢?

此时就需要在类的内部实现获取数据和修改数据的方法, 同时可以增加传入参数的判断,以防止类似于年龄这样的数据会出现负数或者几百上千的值:

class Person(object):
    def __init__(self, age, name):
        self.name = name
        self.__age = age
    
    def get_age(self):
        """获取age"""
        return self.__age
    
    def set_age(self, age):
        """设置age"""
        # 可以在这里面做一些判断,比如年龄的范围可以设置在0~120
        self.__age = age if age > 0 and age <= 120 else "年龄有误"


if __name__ == '__main__':
    p = Person(18, "张三")
    print("p.name: ", p.name)

    p.name = "李四"
    print("p.name: ", p.name)  # 可以被随意修改

    # print("p.__age: ", p.__age)
    print("p.get_age(): ", p.get_age())

    p.set_age(100)
    print("p.get_age(): ", p.get_age())

    p.set_age(150)
    print("p.get_age(): ", p.get_age())

每次都要get_age()set_age(age)这样做会显得很繁琐,不够pythonic,此时,就可以用到settergetterproperty的思想了:

class Person(object):
    def __init__(self, age, name):
        self.name = name
        self.__age = age

    @property
    def age(self):
        """获取age"""
        return self.__age

    @age.setter
    def age(self, age):
        """设置age"""
        # 可以在这里面做一些判断,比如年龄的范围可以设置在0~120
        self.__age = age if age > 0 and age <= 120 else "年龄有误"


if __name__ == '__main__':
    
    p = Person(18, "张三")
    print("p.name: ", p.name)
    
    p.name = "李四"
    print("p.name: ", p.name)  # 可以被随意修改

    print("p.age: ", p.age)
    
    p.age = 32
    print("p.age: ", p.age)
    
    p.age = 999
    print("p.age: ", p.age)
    
    p.age = -18
    print("p.age: ", p.age)

    
"""
p.name:  张三
p.name:  李四
p.age:  18
p.age:  32
p.age:  年龄有误
p.age:  年龄有误
"""

这样的话,在外部看来,age属性的获取就和普通的实例属性差不多了,但是修改age属性的值就不是那么随意了。

另外,如果不使用settergetter这种方式,或者之前定义的set_age(age)get_age()方法已经投入使用又不想改变,就可以在类中添加:age = property(get_age, set_age),切记这里的参数是函数名,不是函数调用,所以不能加小括号;代码如下:

class Person(object):
    def __init__(self, age, name):
        self.name = name
        self.__age = age
    
    def get_age(self):
        """获取age"""
        return self.__age
    
    def set_age(self, age):
        """设置age"""
        # 可以在这里面做一些判断,比如年龄的范围可以设置在0~120
        self.__age = age if age > 0 and age <= 120 else "年龄有误"
        
    age = property(get_age, set_age)  # 这里函数名顺序不能错,当然也可以按关键字传参
    # 如: age = property(fset=set_age, fget=get_age) 
    

if __name__ == '__main__':
    
    p = Person(18, "张三")
    print("p.name: ", p.name)
    
    p.name = "李四"
    print("p.name: ", p.name)  # 可以被随意修改

    print("p.age: ", p.age)
    
    p.age = 32
    print("p.age: ", p.age)
    
    p.age = 999
    print("p.age: ", p.age)
    
    p.age = -18
    print("p.age: ", p.age)


"""
p.name:  张三
p.name:  李四
p.age:  18
p.age:  32
p.age:  年龄有误
p.age:  年龄有误
"""

顺便贴上property部分源码:

class property(object):
    """
    Property attribute.
    
      fget
        function to be used for getting an attribute value
      fset
        function to be used for setting an attribute value
      fdel
        function to be used for del'ing an attribute
      doc
        docstring
    
    Typical use is to define a managed attribute x:
    
    class C(object):
        def getx(self): return self._x
        def setx(self, value): self._x = value
        def delx(self): del self._x
        x = property(getx, setx, delx, "I'm the 'x' property.")
    
    Decorators make defining new properties or modifying existing ones easy:
    
    class C(object):
        @property
        def x(self):
            "I am the 'x' property."
            return self._x
        @x.setter
        def x(self, value):
            self._x = value
        @x.deleter
        def x(self):
            del self._x
    """
    def deleter(self, *args, **kwargs): # real signature unknown
        """ Descriptor to change the deleter on a property. """
        pass

    def getter(self, *args, **kwargs): # real signature unknown
        """ Descriptor to change the getter on a property. """
        pass

    def setter(self, *args, **kwargs): # real signature unknown
        """ Descriptor to change the setter on a property. """
        pass

    def __delete__(self, *args, **kwargs): # real signature unknown
        """ Delete an attribute of instance. """
        pass

    def __getattribute__(self, *args, **kwargs): # real signature unknown
        """ Return getattr(self, name). """
        pass

    def __get__(self, *args, **kwargs): # real signature unknown
        """ Return an attribute of instance, which is of type owner. """
        pass

    def __init__(self, fget=None, fset=None, fdel=None, doc=None): # known special case of property.__init__
        """
        Property attribute.
        
          fget
            function to be used for getting an attribute value
          fset
            function to be used for setting an attribute value
          fdel
            function to be used for del'ing an attribute
          doc
            docstring
        
        Typical use is to define a managed attribute x:
        
        class C(object):
            def getx(self): return self._x
            def setx(self, value): self._x = value
            def delx(self): del self._x
            x = property(getx, setx, delx, "I'm the 'x' property.")
        
        Decorators make defining new properties or modifying existing ones easy:
        
        class C(object):
            @property
            def x(self):
                "I am the 'x' property."
                return self._x
            @x.setter
            def x(self, value):
                self._x = value
            @x.deleter
            def x(self):
                del self._x
        # (copied from class doc)
        """
        pass

    @staticmethod # known case of __new__
    def __new__(*args, **kwargs): # real signature unknown
        """ Create and return a new object.  See help(type) for accurate signature. """
        pass

    def __set__(self, *args, **kwargs): # real signature unknown
        """ Set an attribute of instance to value. """
        pass

    fdel = property(lambda self: object(), lambda self, v: None, lambda self: None)  # default

    fget = property(lambda self: object(), lambda self, v: None, lambda self: None)  # default

    fset = property(lambda self: object(), lambda self, v: None, lambda self: None)  # default

    __isabstractmethod__ = property(lambda self: object(), lambda self, v: None, lambda self: None)  # default