优秀的编程知识分享平台

网站首页 > 技术文章 正文

Python开发必会技巧:访问限制(python访问数据)

nanyue 2025-07-27 22:40:10 技术文章 1 ℃

点赞、收藏、加关注,下次找我不迷路

自己写了一个类,里面的属性和方法不想被随便修改或访问,该怎么办呢?别着急,今天咱们就来好好聊聊 Python 里的访问限制技巧。




一、为啥需要访问限制?

咱先打个比方,你开了一家银行,里面有很多重要的信息,比如客户的存款金额、密码等。这些信息肯定不能让所有人都随便查看和修改,得有一定的保护措施。在 Python 里,类就像是这家银行,里面的属性和方法就是银行的信息和操作,我们需要通过访问限制来确保这些 “信息” 的安全性和完整性。

比如说,我们有一个学生类,里面有学生的姓名和成绩。成绩肯定是一个敏感信息,我们不希望外部代码随意修改成绩,或者不小心写错了成绩。这时候,访问限制就派上用场了,它可以让我们控制哪些属性和方法可以被外部访问,哪些只能在类内部使用。


二、Python 访问限制的三种 “防护盾”

在 Python 中,主要通过属性和方法命名的约定来实现访问限制,虽然不像 Java 等语言有严格的 public、private、protected 关键字,但通过一些命名规则,也能达到类似的效果。常见的有公开属性、保护属性和私有属性这三种,咱们一个一个来详细说。

(一)公开属性:大大方方随便看(默认情况)

在 Python 中,默认情况下,类的属性和方法都是公开的,也就是说,外部代码可以直接访问和修改。比如我们定义一个简单的类:

class Student:
    def __init__(self, name, age):
        self.name = name  # 公开属性
        self.age = age    # 公开属性

    def say_hello(self):  # 公开方法
        print(f"Hello, my name is {self.name}, I'm {self.age} years old.")


创建实例后,就可以直接访问和修改这些属性:

stu = Student("小明", 18)
print(stu.name)  # 输出:小明
stu.age = 20     # 直接修改年龄
print(stu.age)   # 输出:20
stu.say_hello()  # 调用公开方法,输出:Hello, my name is 小明, I'm 20 years old.

这就相当于银行的大厅,任何人都可以进来看看,虽然方便,但也缺乏保护。在实际开发中,如果属性可以被随意修改,可能会导致数据不一致等问题。比如成绩属性,如果外部代码不小心写成了负数,就会出现错误。所以,我们需要更严格的访问限制。

(二)保护属性:加个 “小提醒”(单下划线开头)

保护属性是在属性名前加一个单下划线,比如_attr。这其实是一种约定俗成的方式,告诉其他开发者,这个属性是 “受保护的”,虽然外部代码仍然可以访问和修改,但最好不要这么做,应该在类内部或子类中使用。

比如我们修改一下学生类,把年龄设为保护属性:

class Student:
    def __init__(self, name, age):
        self.name = name  
        self._age = age    # 保护属性,单下划线开头

    def say_age(self):
        print(f"My age is {self._age}")


外部代码仍然可以访问和修改_age:

stu = Student("小红", 19)
print(stu._age)  # 输出:19
stu._age = 21    # 还是能修改
print(stu._age)  # 输出:21

但是,作为有素质的开发者,我们应该遵守这个约定,不要轻易修改保护属性。就像银行的员工通道,虽然外人也能进去,但这是给员工用的,外人最好别进去捣乱。保护属性主要用于子类继承时,子类可以访问父类的保护属性,进行扩展和重写。

(三)私有属性:藏得严严实实(双下划线开头)

私有属性是在属性名前加两个下划线,比如__attr。这时候,Python 会对这个属性进行名称改写,将其变为_类名__attr,这样外部代码就不能直接通过原来的属性名访问了,从而实现了更严格的访问限制。

咱们再改改学生类,把成绩设为私有属性:

class Student:
    def __init__(self, name, score):
        self.name = name  
        self.__score = score  # 私有属性,双下划线开头

    def get_score(self):  # 获取成绩的方法
        return self.__score

    def set_score(self, score):  # 设置成绩的方法,添加验证
        if 0 <= score <= 100:
            self.__score = score
        else:
            print("成绩必须在0到100之间!")


这时候,如果外部代码直接访问__score,就会报错:

stu = Student("小刚", 85)
print(stu.__score)  # 报错:AttributeError: 'Student' object has no attribute '__score'

那怎么访问私有属性呢?可以通过类提供的公共方法,比如上面的get_score()和set_score()。在设置成绩的方法中,我们还可以添加验证逻辑,确保成绩在合理范围内,这就保证了数据的有效性。

不过,虽然私有属性不能直接通过原来的名称访问,但通过名称改写后的名称还是可以访问的,比如stu._Student__score:

print(stu._Student__score)  # 输出:85
stu._Student__score = 120   # 虽然能修改,但这是不推荐的做法
print(stu.get_score())      # 输出:120,不过我们设置的验证逻辑没起作用,所以千万别这么干!

这就相当于银行的保险库,虽然有办法强行打开,但这是违反规定的,会带来安全隐患。所以,我们一定要通过类提供的公共方法来操作私有属性,保证数据的安全。


三、三种属性对比,一目了然

为了让大家更清楚地理解这三种属性的区别,咱们做个表格来总结一下:

类型

命名规则

访问权限

示例代码

注意事项

公开属性

普通命名

外部可直接访问和修改

self.name

无特殊限制,使用方便但缺乏保护

保护属性

单下划线开头

外部可访问修改,但约定不建议这么做

self._age

子类可继承访问,用于内部或子类使用

私有属性

双下划线开头

外部不能直接访问,需通过公共方法

self.__score

Python 会进行名称改写,可通过_类名__attr 访问,但不推荐

通过这个表格,是不是一下子就清楚了?以后看到不同命名的属性,就知道该怎么处理啦。


四、@property 装饰器,让属性访问更优雅

上面我们通过定义get_score()和set_score()方法来操作私有属性,虽然实现了访问限制,但调用的时候需要像方法一样加括号,比如stu.get_score()。有没有更优雅的方式,让我们可以像访问公开属性一样访问私有属性呢?这时候,@property装饰器就派上用场了。

(一)啥是 @property?把方法变成属性来用

@property装饰器可以将一个方法转换为属性,这样我们就可以像访问普通属性一样访问这个方法,不需要加括号。同时,我们还可以通过@属性名.setter装饰器来定义设置属性的方法,实现对属性的读写控制。

还是以学生类的成绩为例,我们用@property来改写:

class Student:
    def __init__(self, name, score):
        self.name = name  
        self.__score = score  # 私有属性

    @property
    def score(self):  # getter方法,转换为属性
        return self.__score

    @score.setter
    def score(self, score):  # setter方法,设置属性
        if 0 <= score <= 100:
            self.__score = score
        else:
            print("成绩必须在0到100之间!")


现在,访问和设置成绩就像操作公开属性一样方便:

stu = Student("小芳", 90)
print(stu.score)   # 像属性一样访问,输出:90
stu.score = 85     # 像属性一样设置,合法
print(stu.score)   # 输出:85
stu.score = 150    # 不合法,输出提示信息
print(stu.score)   # 成绩不变,还是85

这样是不是更简洁、更符合我们的使用习惯呢?而且,通过@property,我们可以在获取和设置属性时添加各种逻辑,比如验证、计算等,让代码更灵活。

(二)只读属性:只定义 getter,不定义 setter

如果我们希望某个属性只能读取,不能设置,比如一些计算出来的属性,只需要定义@property装饰的 getter 方法,不定义 setter 方法就可以了。

比如,我们计算学生的等级,根据成绩来划分:

class Student:
    def __init__(self, name, score):
        self.name = name  
        self.__score = score  

    @property
    def score(self):
        return self.__score

    @score.setter
    def score(self, score):
        if 0 <= score <= 100:
            self.__score = score
        else:
            print("成绩必须在0到100之间!")

    @property
    def grade(self):  # 只读属性,根据成绩计算等级
        if self.score >= 90:
            return "A"
        elif self.score >= 80:
            return "B"
        elif self.score >= 60:
            return "C"
        else:
            return "D"


这时候,grade属性只能读取,不能设置:

stu = Student("小强", 75)
print(stu.grade)  # 输出:B
stu.grade = "A"   # 报错:AttributeError: can't set attribute 'grade'

这样就保证了等级属性的只读性,避免外部代码错误地修改。


五、slots,限制实例属性的种类

有时候,我们希望限制一个类的实例只能拥有特定的属性,不能随意添加其他属性,这时候可以使用__slots__魔法方法。__slots__定义了一个类允许的属性列表,实例只能拥有列表中的属性,这样可以节省内存,提高效率,同时也能防止误添加属性。

(一)怎么用?定义__slots__元组

比如,我们定义一个学生类,只允许有name和age属性,不允许添加其他属性:

class Student:
    __slots__ = ("name", "age")  # 定义允许的属性

    def __init__(self, name, age):
        self.name = name
        self.age = age


创建实例后,只能设置name和age属性,添加其他属性会报错:

stu = Student("小伟", 20)
stu.name = "小辉"  # 合法
stu.age = 21      # 合法
stu.score = 90    # 报错:AttributeError: 'Student' object has no attribute 'score'

(二)注意:__slots__对继承的影响

如果一个子类没有定义__slots__,那么它会继承父类的__slots__,同时还可以添加自己的属性。但如果子类定义了__slots__,那么它的实例只能拥有父类__slots__和自己__slots__中的属性,除非在子类的__slots__中包含'__dict__',这样才能允许实例有动态属性。

比如:

class GraduateStudent(Student):
    __slots__ = ("major",)  # 子类定义自己的slots

    def __init__(self, name, age, major):
        super().__init__(name, age)
        self.major = major


这时候,GraduateStudent的实例只能有name、age、major属性,不能添加其他属性。如果想让子类实例可以有动态属性,需要在__slots__中加入'__dict__':

class GraduateStudent(Student):
    __slots__ = ("major", "__dict__")  # 允许动态属性



六、整个记忆口诀

为了让大家更容易记住这些访问限制的技巧,咱们来编个口诀:

单下划线是提醒,保护属性别乱改;

双下划线真隐藏,名称改写来帮忙;

@property 装饰器,属性访问变优雅;

slots 来限制,属性种类跑不了。


七、隐藏在背后的问题

(一)私有属性真的完全不能被外部访问吗?

不是的,通过名称改写后的名称_类名__attr还是可以访问的,但这是不推荐的做法,会破坏封装性,所以一定要通过类提供的公共方法来操作。

(二)子类能继承父类的私有属性吗?

子类不能直接访问父类的私有属性,因为私有属性在父类中被名称改写了,子类中的同名属性不会冲突。如果父类有公共方法访问私有属性,子类可以调用这些方法。

(三)什么时候用保护属性,什么时候用私有属性?

如果希望子类可以访问和修改该属性,用保护属性(单下划线);如果不希望任何外部代码(包括子类)直接访问,必须通过父类的公共方法操作,用私有属性(双下划线)。

(四)__slots__和__dict__有什么关系?

默认情况下,Python 实例有一个__dict__属性,用于存储动态添加的属性,这会占用一定的内存。使用__slots__后,实例不再有__dict__,只能有__slots__中定义的属性,从而节省内存。如果需要动态添加属性,需要在__slots__中包含'__dict__',但这样会部分失去__slots__节省内存的优势。


以后在写类的时候,根据不同的需求选择合适的访问限制方式,比如敏感属性用私有属性加@property,需要限制实例属性种类时用__slots__。多练习、多实践,你会越来越熟练的。

Tags:

最近发表
标签列表