在Python中进行名称处理

时间:2020-01-09 10:44:19  来源:igfitidea点击:

如果我们正在用Python编写类并希望遵循Python的封装OOPS概念,那么我们将如何停止对变量的外部访问,因为没有显式的访问修饰符,例如public,private,在Python中受保护并且默认情况下所有变量都是public 。在Python中,对将类成员设为私有的支持有限,该过程在Python中称为名称修改。

Python名称处理机制

在名称处理机制中,具有至少两个前导下划线的标识符(最多一个尾随下划线)在文本上均被_classname__identifier替换,其中classname是当前类名。例如,如果类中有一个变量__test,则将其替换为_classname__test。

由于名称是由解释器在内部更改的,因此我们无法使用其原始名称访问变量,这就是在Python中隐藏数据的方式。
名称修饰有助于让子类覆盖方法而又不中断类内方法调用。

Python名称修改示例

class User:
    def __init__(self, name, age):
        self.name = name
        self.__age = age

    def display_user(self):
        print('User Name:', self.name)
        print('User Age:', self.__age)

user = User('Mike Dallas', 34)
# calling class method
user.display_user()
# Accessing variables directly
print(user.name)
print(user.__age)

输出:

User Name: Mike Dallas
User Age: 34
Mike Dallas
Traceback (most recent call last):
  File "F:/theitroad/Python/Programs/NameMangling.py", line 16, in 
    print(user.__age)
AttributeError: 'User' object has no attribute '__age'

在类User中,使用确定的类方法访问时,存在一个__age字段(用双下划线声明),但是尝试通过名称处理机制将其名称更改为(_User__age)直接尝试访问它会导致错误。

我们可以使用dir()函数检查名称更改,该函数为传递的对象返回有效属性的列表。

class User:
    def __init__(self, name, age):
        self.name = name
        self.__age = age

    def display_user(self):
        print('User Name:', self.name)
        print('User Age:', self.__age)

user = User('Mike Dallas', 34)
print(dir(user))

输出:

['_User__age', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
 '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__',
 '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
 '__subclasshook__', '__weakref__', 'display_user', 'name']

在这里,我们可以看到__age更改为_User__age。

名称与方法名称混用

由于任何具有至少两个前导下划线的类成员,在文本上最多将一个结尾的下划线替换为_classname__identifier,因此名称修饰也将应用于方法名称。

class User:
    def __init__(self, name, age):
        self.name = name
        self.__age = age

    def __display_user(self):
        print('User Name:', self.name)
        print('User Age:', self.__age)

user = User('Mike Dallas', 34)
user.__display_user()

输出:

Traceback (most recent call last):
  File "F:/theitroad/Python/Programs/NameMangling.py", line 12, in 
    user.__display_user()
AttributeError: 'User' object has no attribute '__display_user'

如何访问名称混乱的变量

在名称更改中,进程名称被_classname__membername替换,因此我们仍然可以使用更改的名称来访问成员名称。这就是为什么Python声明只有有限的支持将类成员设为私有。

class User:
    def __init__(self, name, age):
        self.name = name
        self.__age = age

    def display_user(self):
        print('User Name:', self.name)
        print('User Age:', self.__age)

user = User('Mike Dallas', 34)
# calling class method
user.display_user()
# Accessing variables directly
print(user.name)
# Accessing using the mangled name
print(user._User__age)

输出:

User Name: Mike Dallas
User Age: 34
Mike Dallas
34

使用方法重写处理Python名称

名称修饰也有助于Python中的方法覆盖。它允许子类覆盖方法而不会中断类内方法调用。考虑下面的示例,其中B类扩展了A类并覆盖了父类测试方法。在类A的init()中,还调用了test方法。

class A:
    def __init__(self):
        print('in init')
        self.test()

    def test(self):
        print('In test method of class A')

class B(A):
    def test(self):
        print('In test method of class B')

obj = B()
obj.test()

输出:

in init
In test method of class B
In test method of class B

如我们所见,类B的test()方法被两次调用,而引用是对类B的调用。但是我们打算在做self.test()时调用类A的test()方法。为了避免在这种情况下中断类内方法调用,我们可以创建原始方法的私有副本。

class A:
    def __init__(self):
        print('in init')
        self.__test()

    def test(self):
        print('In test method of class A')
        
    # private copy
    __test = test

class B(A):
    def test(self):
        print('In test method of class B')

obj = B()
obj.test()

输出:

in init
In test method of class A
In test method of class B