python对象的“权限”控制

本文将对权限控制这个问题进行简单探讨

阅读前说明

此篇并不是一篇技术教程,而是本人对python对象的权限的一点不一样的理解

以下内容仅为个人理解,如有偏差之处请自行忽略

或许是受到C#的设计哲学的影响,又或许是对代码质量的要求,在做一些项目开发时对各种对象、变量的权限控制有了更高的要求,有些变量涉及到外部交互,需要设置为public; 而有些变量不希望被外部访问,需要设置为private等。

可能有的人会想,有必要这么在意这些问题吗?诚然,如果你只是个人开发,似乎全部使用public属性也没有什么不妥,也不会影响开发1,就目前我个人的开发经验来看,可能涉及到多人协作开发的时候,做好必要的限制是有必要的,对于这些细节相信读者都在python面向对象部分的教程中都有听过,这里就不作过多的细节介绍。

在某次阅读某本书籍时,意外发现python中也有类似protected属性的概念,也就是使用单个下划线,即_variable的写法,但随着开发,发现python的protected属性与其他OOP语言(比如C#)还不太一样,以下举一些例子来说明

首先先简单说明protected属性的意义是什么。以上我提到变量可以设置为public和private,但如果涉及到继承的时候,使用public会导致外部被访问,但使用private又无法被继承,可是我既想要让继承类获取父类的对象,但又不希望外部被访问,这个时候protected属性就起到作用了。

看起来很美好,但是。我要说但是,python的protected属性和C#提供的有些不一样2。以下是两段代码示例

说明

以下代码看不懂不需要担心,直接看注释

Python
class Student:
    def __init__(self, name, age):
        self.__name = name # 定义private属性的name
        self.age = age # 定义public属性的age
        self._id = 1 # 定义protected属性的id


student = Student("gcc", "10")
# 尝试访问name
try:
    print(student.name)
except Exception as e:
    print(e)
# 'Student' object has no attribute 'name'
# 尝试访问age
try:
    print(student.age)
except Exception as e:
    print(e)
# 10
# 尝试访问id
try:
    print(student._id)
except Exception as e:
    print(e)
# 1
# 尝试修改id
try:
    student._id += 1
    print(student._id)
except Exception as e:
    print(e)
# 2
C#
var student = new Student("gcc", 10);
// 尝试访问name
try
{
    Console.WriteLine(student.Name);
}
catch (Exception e)
{
    Console.WriteLine(e);
}
// 无法编译通过
// 尝试访问age
try
{
    Console.WriteLine(student.Age);
}
catch (Exception e)
{
    Console.WriteLine(e);
}
// 10
// 尝试访问id
try
{
    Console.WriteLine(student.Id);
}
catch (Exception e)
{
    Console.WriteLine(e);
}
// 无法编译通过
class Student
{
    private string Name; // 定义private属性的name
    public int Age; // 定义public属性的age
    protected int Id; // 定义protected属性的id

    public Student(string name, int age)
    {
        Name = name;
        Age = age;
        Id = 1;
    }
}

在类似C#对权限要求比较高的语言中,如果要从外部访问private和protected属性,甚至都无法编译通过;而在python中,无法访问private属性,但是可以正常访问protected属性并且还能修改3

如果你曾经看过某些视频教程,其中还会教你怎么写以绕过private限制

Python
print(student._Student__name)
# gcc
student._Student__name = "gcc1"
print(student._Student__name)
# gcc1

在python官方文档中,对于这个现象的说明是

那种仅限从一个对象内部访问的“私有”实例变量在 Python 中并不存在。 但是,大多数 Python 代码都遵循这样一个约定:带有一个下划线的名称 (例如 _spam) 应该被当作是 API 的非公有部分 (无论它是函数、方法或是数据成员)。 这应当被视为一个实现细节,可能不经通知即加以改变。

说的简单一点,python并没有提供那种非常严格的权限控制,更多的时候只是提供一种约定、协议,大家都遵照这样的写法,如果是非public属性就不要强行调用修改,而且根据目前python官方文档来看,似乎并没有严格意义上的protected属性,更多的还是一种约定俗成的作法。但如果你非要调用并修改,也不是不行,python并没有严格的做限制

关于private属性问题

如果你打算用反射调用某个对象内部的private属性的变量,你要格外注意了,你大概率会遇到报错,比如这样

Python
class Student:
    ...
    def report(self, text):
        value = getattr(self, text)
        print(value)  
...
try:
    student.report("__name")
except Exception as e:
    print(e)
# 'Student' object has no attribute '__name'

此时只有改成_name,就不会出现报错了,有些迷惑

这大概也属于python的“灵活性”吧,如果你对这方面也比较在意的话,在对python做权限控制的时候需要好好考虑一下该如何设置,比如设置为只读不可写属性等,以尽可能避免被外部程序意外调用修改等


  1. 当然你也必须知道每个变量的作用是什么,能不能被意外乱改,这一点需要好好考虑 

  2. 虽然以上介绍的protected概念听起来很美好,但是真正在开发时,会发现PyCharm并不会对protected属性的变量做一些代码提示,但是手写后也不会报错,所谓的protected这个问题在下面的介绍中会提到 

  3. 不过在PyCharm等IDE中,会对这种写法有警告提示,如果不理会这个提示是可以正常运行的