Python之@property详解及底层实现介绍

转自:https://blog.csdn.net/weixin_42681866/article/details/83376484

前文

Python内置有三大装饰器:@staticmethod(静态方法)、@classmethod(类方法)、@property(描述符),其中静态方法就是定义在类里的函数,并没有非要定义的必要;类方法则是在调用类属性、传递类对象时使用;而@property则是一个非常好用的语法糖。@property最大的好处就是在类中把一个方法变成属性调用,起到既能检查属性,还能用属性的方式来访问该属性的作用

@property应用

让我们先看下@property的应用,其功能1是可定义只读属性,也就是真正意义上的私有属性(属性前双下划线的私有属性也是可以访问的,具体参照这篇文章:私有属性真的是私有的吗?)实例需求是定义类Person,具有年龄和姓名,要求年龄必须等于18,则代码如下:

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

    @property
    def age(self):
        return self.__age

    
xm = Person('xiaoming')  # 定义一个人名小明
print(xm.age)  # 结果为18
xm.age = -4	 # 报错无法给年龄赋值
print(xm.age)

结果如下:

18
Traceback (most recent call last):
	xm.age = 18
AttributeError: can't set attribute

在python中定义只读属性非@property莫属,如果细心留意大部分源码,都跑不了@property的身影。而定义只读属性也很简单:以需要定义的属性为方法名(上例age属性定义为方法),其上装饰内置装饰器@property就ok了。

@property真正强大的是可以限制属性的定义。往往我们定义类,希望其中的属性必须符合实际,但因为在__ init __里定义的属性可以随意的修改,导致很难实现。如我想实现Person类,规定每个人(即创建的实例)的年龄必须大于18岁,正常实现的话,则必须将属性age设为只读属性,然后通过方法来赋值,代码如下:

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

    @property
    def age(self):
        return self.__age

    def set_age(self, age):  # 定义函数来给self.__age赋值
        if age < 18:
            print('年龄必须大于18岁')
            return
        self.__age = age
        return self.__age

    
xm = Person('xiaoming', 20)    
print(xm.age)
print('----------')
xm.set_age(10)
print(xm.age)
print('----------')
xm.set_age(20)
print(xm.age)

为了便于区分结果,增加了分隔符,结果如下:

18
----------
年龄必须大于18岁
18
----------
20

可以看到,通过方法的确能限制输入,但是不是每个人都记得方法名,有什么简化的方法呢?@property就能很轻松办到,修改代码如下:

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

    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, age):
        if age < 18:
            print('年龄必须大于18岁')
            return
        self.__age = age
        return self.__age


xm = Person('xiaoming', 20)
print(xm.age)
print('----------')
xm.age = 10
print(xm.age)
print('----------')
xm.age = 20
print(xm.age)

结果和上图一致。两段代码变化的内容:将set_age修改为age,并且在上方加入装饰器@age.setter。这就是@property定义可访问属性的语法,即仍旧以属性名为方法名,并在方法名上增加@属性.setter就行了

@property实现原理

在开头说过,@property是个描述符(decorator),实际上他本身是类,不信的话,可以将上述代码修改如下:

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

    def get_age(self):  # 恢复用方法名来获取以及定义
        return self.__age

    def set_age(self, age): 
        if age < 18:
            print('年龄必须大于18岁')
            return
        self.__age = age
        return self.__age

    
age = property(get_age, set_age)  # 增加property类

上述代码的运行结果和前面一致,将@property装饰的属性方法再次修改回定义方法名,然后再类的最下方,定义:属性=property(get,set,del),这个格式是固定的,通过源码可以知道原因,property部分源码如下:

class property(object):
	def __init__(self, fget=None, fset=None, fdel=None, doc=None):
		"""
        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
		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
  		"""
 		pass	
     def __set__(self, *args, **kwargs):  # real signature unknown
       """ 
       Set an attribute of instance to value. 
       """
       	pass
     def __get__(self, *args, **kwargs):  # real signature unknown
	    """ 
	    Return an attribute of instance, which is of type owner.
        """
	    pass
     def __delete__(self, *args, **kwargs):  # real signature unknown
        """ 
        Delete an attribute of instance.
        """
        pass

为了不让篇幅过长,同时影响到阅读效果,这边截取了部分,可以看到代码注释里写了官方的@property用法,同时如果是用类来实现的话,必须得按__ init __ 的参数里顺序fget/fset/fdel依次填入get、set、del方法,因为都是默认参数,所以都可以省略不写。

介绍下python的描述符,定义:如果一个类里定义了__ set __ 、__ get __ 、__ delete __三个方法之一,同时给另一个类的属性赋值为实例,那么该类可以称之为描述符。因为描述符的使用目前就python,所以了解下就行了。

总结

关于@property的介绍到此为止,@property装饰器就是一个语法糖(语法糖指那些没有给计算机语言添加新功能,而只是对人类来说更“甜蜜”的语法。 语法糖往往给程序员提供了更实用的编码方式,有益于更好的编码风格,更易读。)。

posted @ 2021-08-06 10:37  我在吃大西瓜呢  阅读(249)  评论(0编辑  收藏  举报