先看下面代码:
class Movie(object):
def __init__(self, name, year):
self.name = name
self.year = year
def print_detail(self):
print("Name is {}, year is {}".format(self.name, self.year))
movie = Movie("囧妈", 2020)
print(movie.name) # 囧妈
movie.name = "流浪地球"
print(movie.name) # 流浪地球
看到创建的两个属性和一个方法都被暴露在外面,可被 movie
调用。这样的话,这些属性就会被任意修改(电影名称由 【囧妈】变为【流浪地球】)。
要想类的属性不被任意修改,可以使用下面两种方法来解决:
- 定义公共函数来访问和修改私有属性;
- 使用
property
类来访问和修改私有属性;
1. 使用公共函数
分别定义公共函数来对私有属性进行访问和修改。
1.1 读取私有属性
如果想避免属性 name
被修改,可以将它变为私有变量。改动方法:属性前加 2 个 _
后,变为私有属性。
-
以单下划线开头(
_foo
)的代表不能直接访问的类属性,表示的是protected
类型的变量,即保护类型只能允许其本身与子类进行访问,不能用from xxx import *
导入; -
以双下划线开头的(
__foo
)代表类的私有成员(private
)的变量,只能是允许这个类本身进行访问了。; -
以双下划线开头和结尾的(
__ foo__
)代表Python
里特殊方法专用的标识,如__init()__
代表类的构造函数;
如:
class Movie(object):
def __init__(self, name, year):
self.__name = name
self.__year = year
def print_detail(self):
print("Name is {}, year is {}".format(self.__name, self.__year))
movie = Movie("囧妈", 2020)
movie.print_detail()
print(movie.__name) # 囧妈
结果显示:
Name is 囧妈, year is 2020
...
AttributeError: 'Movie' object has no attribute '__name'
可以看到私有属性 __name
是无法直接访问的。
但是这样改动后,属性 __name
不能被访问了,也就无法得知 movie
的名字叫啥。不过,这个问题有一种简单的解决方法,直接新定义一个方法就行:
def get_name(self):
return self.__name
综合代码如下:
class Movie(object):
def __init__(self, name, year):
self.__name = name
self.__year = year
def print_detail(self):
print("Name is {}, year is {}".format(self.__name, self.__year))
def get_name(self):
return self.__name
movie = Movie("囧妈", 2020)
movie.print_detail()
print(movie.get_name()) # 囧妈
1.2 设置私有属性
class Movie(object):
def __init__(self, name, year):
self.__name = name
self.__year = year
def print_detail(self):
print("Name is {}, year is {}".format(self.__name, self.__year))
def get_name(self):
return self.__name
def set_name(self, name):
self.__name = name
movie = Movie("囧妈", 2020)
movie.print_detail()
print(movie.get_name()) # 囧妈
movie.set_name("wohu")
print(movie.get_name()) # wohu
2. 使用 property 类
使用 Python
自带的 property
类,就会优雅地将 name
变为只读的。
2.1 读取私有属性
@property
def name(self):
return self.__name
完整代码如下:
class Movie(object):
def __init__(self, name, year):
self.__name = name
self.__year = year
def print_detail(self):
print("Name is {}, year is {}".format(self.__name, self.__year))
@property
def name(self):
return self.__name
movie = Movie("囧妈", 2020)
movie.print_detail()
print(movie.name) # 囧妈
#movie.name = "流浪地球"
2.2 设置私有属性
如果再尝试使用
movie.name = "流浪地球"
修改 name
属性时,会报如下错误:
Traceback (most recent call last):
movie.name = "流浪地球"
AttributeError: can't set attribute
使用 @property
装饰后 name
变为属性,意味着 .name
就会返回电影的名字,而不是通过 .get_name()
这种函数调用的方法。这样变为真正的属性后,可读性更好。
如果使 name
既可读又可写,就再增加一个装饰器 @name.setter
。
class Movie(object):
def __init__(self, name, year):
self.__name = name
self.__year = year
@property
def name(self):
return self.__name
@name.setter
def name(self, new_name):
self.__name = new_name
movie = Movie("囧妈", 2020)
print(movie.name) # 囧妈
movie.name = "流浪地球"
print(movie.name) # 流浪地球
注意这种装饰器写法:name.setter
,name
已经被包装为 property
实例,调用实例上的 setter
函数再包装 name
后就会可写。