Python property()函数:定义属性

我们一直在用“类对象.属性”的方式访问类中定义的属性,其实这种做法是欠妥的,因为它破坏了类的封装原则。正常情况下,类包含的属性应该是隐藏的,只允许通过类提供的方法来间接实现对类属性的访问和操作。

因此,在不破坏类封装原则的基础上,为了能够有效操作类中的属性,类中应包含读(或写)类属性的多个 getter(或 setter)方法,这样就可以通过“类对象.方法(参数)”的方式操作属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class CLanguage:
def __init__(self,name):
self.name = name
#设置 name 属性值的函数
def setname(self,name):
self.name = name
#访问nema属性值的函数
def getname(self):
return self.name
#删除name属性值的函数
def delname(self):
self.name="xxx"
clang = CLanguage("漂亮鬼")
print(clang.getname())
clang.setname("Python")
print(clang.getname())
clang.delname()
print(clang.getname())

Python 中提供了 property() 函数,可以实现在不破坏类封装原则的前提下,让开发者依旧使用“类对象.属性”的方式操作类中的属性

property() 函数的基本使用格式:
属性名=property(fget=None, fset=None, fdel=None, doc=None)

fget 参数用于指定获取该属性值的类方法,
fset 参数用于指定设置该属性值的方法,
fdel 参数用于指定删除该属性值的方法,
最后的 doc 是一个文档字符串,用于说明此函数的作用。

注意,在使用 property() 函数时,以上 4 个参数可以仅指定第 1 个、或者前 2 个、或者前 3 个,当前也可以全部指定。也就是说,property() 函数中参数的指定并不是完全随意的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class CLanguage:
def __init__(self,n):
self.__name = n
def setname(self,n):
self.__name = n
def getname(self):
return self.__name
def delname(self):
self.__name="xxx"
name = property(getname, setname, delname, '指明出处')
#调取说明文档的 2 种方式
#print(CLanguage.name.__doc__)
help(CLanguage.name)
clang = CLanguage("漂亮鬼")
#调用 getname() 方法
print(clang.name)
#调用 setname() 方法
clang.name="Python"
print(clang.name)
#调用 delname() 方法
del clang.name
print(clang.name)

注意:
在此程序中,由于 getname() 方法中需要返回 name 属性,如果使用 self.name 的话,其本身又被调用 getname(),这将会先入无限死循环。为了避免这种情况的出现,程序中的 name 属性必须设置为私有属性,即使用 __name(前面有 2 个下划线)

Python @property装饰器详解

既要保护类的封装特性,又要让开发者可以使用“对象.属性”的方式操作操作类属性,除了使用 property() 函数,Python 还提供了 @property 装饰器。通过 @property 装饰器,可以直接通过方法名来访问方法,不需要在方法名后添加一对“()”小括号。
@property 的语法格式如下:

@property
def 方法名(self)
代码块

1
2
3
4
5
6
7
8
9
class Rect:
def __init__(self,area):
self.__area = area
@property
def area(self):
return self.__area
rect = Rect(30)
#直接通过方法名来访问 area 方法
print("矩形的面积是:",rect.area)

使用 @property 修饰了 area() 方法,这样就使得该方法变成了 area 属性的 getter 方法。需要注意的是,如果类中只包含该方法,那么 area 属性将是一个只读属性。

也就是说,在使用 Rect 类时,无法对 area 属性重新赋值
运行如下代码会报错:
rect.area = 90
print(“修改后的面积:”,rect.area)

而要想实现修改 area 属性的值,还需要为 area 属性添加 setter 方法,就需要用到 setter 装饰器
语法格式:
@方法名.setter
def 方法名(self, value):
代码块

为 Rect 类中的 area 方法添加 setter 方法

1
2
3
@area.setter
def area(self, value):
self.__area = value

然后就可以修改area的值了

除此之外,还可以使用 deleter 装饰器来删除指定属性
语法格式:
@方法名.deleter
def 方法名(self):
代码块
为 Rect 类中,给 area() 方法添加 deleter 方法

1
2
3
@area.deleter
def area(self):
self.__area = 0

@property相当于前面的getname() 函数
@setter相当于setname()函数
@deleter 相当于delete()函数

Python封装机制及实现方法

简单的理解封装(Encapsulation),即在设计类时,刻意地将一些属性和方法隐藏在类的内部,这样在使用此类时,将无法直接以“类对象.属性名”(或者“类对象.方法名(参数)”)的形式调用这些属性(或方法),而只能用未隐藏的类方法间接操作这些隐藏的属性和方法。
大致相当于c++中的将里面的属性设置为私有,然后外面不能直接通过对象名直接访问,必须通过函数留一些接口才能访问到

封装绝不是将类中所有的方法都隐藏起来,一定要留一些像键盘、鼠标这样可供外界使用的类方法
对一个类实现良好的封装,用户只能借助暴露出来的类方法来访问数据,我们只需要在这些暴露的方法中加入适当的控制逻辑,即可轻松实现用户对类中属性或方法的不合理操作。
并且,对类进行良好的封装,还可以提高代码的复用性。

Python 类进行封装

和其它面向对象的编程语言(如 C++、Java)不同,Python 类中的变量和函数,不是公有的(类似 public 属性),就是私有的(类似 private),这 2 种属性的区别如下:
public:公有属性的类变量和类函数,在类的外部、类内部以及子类中,都可以正常访问;
private:私有属性的类变量和类函数,只能在本类内部使用,类的外部以及子类都无法使用。

但是,Python 并没有提供 public、private 这些修饰符。为了实现类的封装

Python 采取了下面的方法:
默认情况下,Python 类中的变量和方法都是公有(public)的,它们的名称前都没有下划线(_);
如果类中的变量和函数,其名称以双下划线“__”开头,则该变量(函数)为私有变量(私有函数),其属性等同于 private。

除此之外,还可以定义以单下划线“_”开头的类属性或者类方法

(例如 _name、_display(self)),这种类属性和类方法通常被视为私有属性和私有方法,虽然它们也能通过类对象正常访问,但这是一种约定俗称的用法,初学者一定要遵守。

注意,Python 类中还有以双下划线开头和结尾的类方法(例如类的构造函数__init__(self)),这些都是 Python 内部定义的,用于 Python 内部调用。我们自己定义类属性或者类方法时,不要使用这种格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class CLanguage :
def setname(self, name):
if len(name) < 3:
raise ValueError('名称长度必须大于3!')
self.__name = name
def getname(self):
return self.__name
name = property(getname, setname)
def setadd(self, add):
if add.startswith("http://"):
self.__add = add
else:
raise ValueError('地址必须以 http:// 开头')
def getadd(self):
return self.__add
#为 add 配置 setter 和 getter 方法
add = property(getadd, setadd)
#定义个私有方法
def __display(self):
print(self.__name,self.__add)

clang = CLanguage()
clang.name = "漂亮鬼blog"
clang.add = "http://xiaoyangzst.vercel.app/"
print(clang.name)
print(clang.add)

会做详细的讲解,这里可简单理解成,如果用户输入不规范,程序将会报错。
通过此程序的运行逻辑不难看出,通过对 CLanguage 类进行良好的封装,使得用户仅能通过暴露的 setter() 和 getter() 方法操作 name 和 add 属性,而通过对 setname() 和 setadd() 方法进行适当的设计,可以避免用户对类中属性的不合理操作,从而提高了类的可维护性和安全性。
细心的读者可能还发现,CLanguage 类中还有一个 __display() 方法,由于该类方法为私有(private)方法,且该类没有提供操作该私有方法的“窗口”,因此我们无法在类的外部使用它。

Python封装底层实现原理详解

类对象无法直接调用以双下划线开头命名的类属性和类方法,是因为其底层实现时,Python 偷偷改变了它们的名称。

是不是类似 display() 这种的私有方法,真的没有方法调用吗?如果你深入了解 Python 封装机制的底层实现原理,就可以调用它。
事实上,对于以双下划线开头命名的类属性或类方法,Python 在底层实现时,将它们的名称都偷偷改成了 “_类名__属性(方法)名” 的格式

前面display私有函数的调用:

1
2
3
4
5
6
7
clang = CLanguage()
#调用name的setname()方法
clang.name = "漂亮鬼"
#调用add的setadd()方法
clang.add = "https://xiaoyangzst.vercel.app"
#直接调用隐藏的display()方法
clang._CLanguage__display()

不仅如此,那些原本我们认为是私有的类属性(例如 __name 和 __add),其底层的名称也改成了“_类名__属性名”的这种格式

1
2
3
4
5
clang = CLanguage()
clang.name = "漂亮鬼"
clang.add = "https://xiaoyangzst.vercel.app"
#直接调用 name 和 add 私有属性
print(clang._CLanguage__name, clang._CLanguage_)

甚至于,我们还可以通过这种方式修改 clang 对象的私有属性

1
2
3
clang._CLanguage__name = "Python"
clang._CLanguage__add = "https://xiaoyangzst.vercel.app"
print(clang._CLanguage__name,clang._CLanguage__add)