Python 进阶教程系列 5:获取和修改被保护的属性¶
本文是 Python 进阶教程系列 5,主要介绍了 Python 私有化及 _
下划线命名用途,以及使用 getter
、 setter
和 property
来修改被保护的属性。
本文部分转载了 Python 私有化及 _
下划线命名用途,已获得原作者授权。
Python 中没有真正的私有属性或方法,但有一些和命名有关的约定,让编程人员处理一些需要私有化的情况。我们常常需要区分私有方法、属性和公有方法、属性以方便管理和调用,在 Python 中如何做呢?
在变量、方法命名中有下列几种情况:
xx
公有变量/方法_xx
前置单下划线__xx
前置双下划线__xx__
前后双下划线xx_
后置单下划线
接下来分别介绍这几种带下划线命名的特性与区别。
几种带下划线命名的特性与区别¶
_
单前置下划线¶
前置单下划线的意思是 提示 其他程序员,以单下划线开头的变量或方法只在内部使用。PEP 8
中定义了这个约定( PEP 8
是最常用的 Python
代码风格指南)。
前置单下划线定义了一个被保护的属性。关于 Public, Protected, Private Members 的区别,可以参考 Python - Public, Protected, Private Members。
不过,这个约定对 Python 解释器并没有特殊含义。与 Java 不同,Python 在 “私有” 和 “公共” 变量之间并没有很强的区别。在变量名之前添加一个下划线更像是有人挂出了一个小小的下划线警告标志:“注意,这并不是这个类的公共接口。最好不要使用它。”
一般 Python 约定前置单下划线 _
的属性和方法为私有方法或属性,以提示该属性和方法 不应 在外部调用。
当然,在类中也可以用单下划线开头来命名属性或者方法,这只是表示类的定义者希望这些属性或者方法是 "私有的",但实际上并不会起任何作用。
这是因为 Python 中的前置单下划线 _
只是一个公认的约定,而不是必须遵守的规则,至少在涉及变量名和方法名时是这样的。
但是前置下划线会影响从模块中导入名称的方式,不会被 from somemodule import *
导入。
对此解释器会抛出异常:NameError: name '_key' is not defined
。
使用 通配符导入 从这个模块中导入所有名称,Python 不会 导入带有前置单下划线的名称(除非模块中定义了__all__
列表覆盖了这个行为。
但并非 demo.py
中的前置单下划线变量/方法在 test.py
中就不可以使用,完全可以 import module
,然后通过 module.xxx
方式,test.py
代码做如下调整:
# test.py
import demo
print(demo._key) # 正常使用
demo._set_key("789") # 正常调用
print(demo._key) # 正常使用
__
前置双下划线¶
用于对象的数据封装,以此命名的属性或者方法为类的私有属性或者私有方法。
# coding:utf8
class Foo(object):
def __init__(self):
self__name = "private attribute"
def getname():
return self.__name
def __method():
print("private method")
def run(self):
self.__method()
在外部访问直接访问私有属性或方法:
In [1]: # coding:utf8
...:
...:
...: class Foo(object):
...:
...: def __init__(self):
...: self.__name = "private attribute"
...:
...: def getname(self):
...: return self.__name
...:
...: def __method(self):
...: print("private method")
...:
...: def run(self):
...: self.__method()
...:
...:
In [2]: f = Foo()
In [3]: f.__name
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-3-332cfd3c796d> in <module>
----> 1 f.__name
AttributeError: 'Foo' object has no attribute '__name'
In [4]: f.__method()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-4-9cb5f81c61a6> in <module>
----> 1 f.__method()
AttributeError: 'Foo' object has no attribute '__method'
In [5]: f.getname()
Out[5]: 'private attribute'
In [6]: f.run()
private method
可以发现在外部访问直接访问私有属性或方法是不可行,这就起到了封装隐藏数据的作用。但是这种实现机制并不是很严格,这种机制是通过 名字重整 name mangling
实现的,目的就是以防类意外重写基类的方法或属性。但类中所有以双下划线开头的名称都会自动变成 _Class_object
的新名称,如 __name
>>> _Foo__name
,我们也可以用 dir()
来查看类中成员详情
python
In [7]: dir(f)
Out[7]:
['_Foo__method',
'_Foo__name',
'__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__',
'getname',
'run']
In [8]: f._Foo__name
Out[8]: 'private attribute'
In [9]: f._Foo__method()
private method
这种机制可以阻止继承类重新定义或者更改方法的实现,比如,定义一个 Foo
的字类 Goo
:
重写 __method
方法,运行:
In [11]: class Goo(Foo):
...: def __method(self):
...: print('private method of Goo')
...:
In [12]: g = Goo()
In [13]: g.run() # (1)!
private method
In [14]: dir(g)
Out[14]:
['_Foo__method',
'_Foo__name',
'_Goo__method',
...
...
]
- 调用
run()
方法的时候依然执行的是Foo
类的__method()
方法。
调用 run()
方法的时候依然执行的是 Foo
类的 __method()
方法,因为在 run()
方法的实现中,self.__method()
已自动变形为 self._Foo__method()
,Goo
继承的 run()
方法也是如此,而Goo
的 __method()
方法就变成了 _Goo__method()
。
名字重整 name mangling
的技术,又叫 name decoration
命名修饰。在很多现代编程语言中,这一技术用来解决 需要唯一名称而引起的问题,比如命名冲突/重载等。
__
前后双下划线__
¶
本节内容可以参考 Python 进阶教程系列 1:双下划线的魔法方法。
前后均带双下划线的命名,一般用于特殊方法的命名,用来实现对象的一些行为或者功能,比如 __new__()
方法用来创建实例,__init__()
方法用来初始化对象,x + y
操作被映射为方法 x.__add__(y)
,序列或者字典的索引操作 x[k]
映射为x.__getitem__(k)
,__len__()、__str__()
分别被内置函数 len()、str()
调用等等。
# coding:utf8
class Obj:
def __init__(self, num):
self.num = num
self.li = list()
self.li.append(num)
def __add__(self, value):
print("__add__() execute")
return self.num + value
def __getitem__(self, index):
print("__getitem__() execute")
return self.li[index]
def __len__(self):
print("__len__() execute")
return len(self.li)
def __str__(self):
print("__str__() execute")
return "< " + str(self.num) + " >"
def main():
a = Obj(5)
a = a + 2
print(a)
b = Obj(6)
print(b[0])
print(len(b))
print(b)
if __name__ == "__main__":
main()
测试结果:
__add__() execute
7
__getitem__() execute
6
__len__() execute
1
__str__() execute
< 6 >
[Finished in 0.1s]
后置单下划线 _
¶
后置单下划线,用于避免与 Python 关键词的冲突。如下:
总结¶
_xx
的变量、函数、类在使用from xxx import *
时都不会被导入。__xx
的实例属性、方法会被 名字重整name mangling >>> _类名__属性名
- 父类中属性名为
__xx
的,子类不继承,子类不能访问。 - 如果在子类中向
__xx
赋值,那么会在子类中定义的一个与父类相同名字的属性。 __xx__
魔法对象或属性,有着特殊作用。不要随意起这种命名。xx_
用于避免与 Python 关键词的冲突。
获取和修改被保护的属性¶
实现 getter
和 setter
¶
# Python program showing a use
# of get() and set() method in
# normal function
class Geek:
def __init__(self, age=0):
self._age = age
# getter method
def get_age(self):
return self._age
# setter method
def set_age(self, x):
self._age = x
raj = Geek()
# setting the age using setter
raj.set_age(21)
# retrieving age using getter
print(raj.get_age())
print(raj._age)
使用 property
¶
# Python program showing a
# use of property() function
class Geeks:
def __init__(self):
self._age = 0
# function to get value of _age
def get_age(self):
print("getter method called")
return self._age
# function to set value of _age
def set_age(self, a):
print("setter method called")
self._age = a
# function to delete _age attribute
def del_age(self):
del self._age
age = property(get_age, set_age, del_age)
mark = Geeks()
mark.age = 10
print(mark.age)
使用 @property
¶
在之前的方法中,为了实现 getter
和 setter
的行为,我们使用了 property()
函数。然而,正如在本帖中之前提到的,getter
和 setter
也用于验证属性值的获取和设置。还有一种实现 property
函数的方法,即使用装饰器。Python 的 @property
是内置的装饰器之一。任何装饰器的主要目的是以一种方式改变你的类方法或属性,使得你的类的用户不需要修改他们的代码。例如
# Python program showing the use of
# @property
class Geeks:
def __init__(self):
self._age = 0
# using property decorator
# a getter function
@property
def age(self):
print("getter method called")
return self._age
# a setter function
@age.setter
def age(self, a):
if a < 18:
raise ValueError("Sorry you age is below eligibility criteria")
print("setter method called")
self._age = a
mark = Geeks()
mark.age = 19
print(mark.age)
同时使用 @property
和 @abc.abstractmethod
如果要同时使用 @property
和 @abc.abstractmethod
,必须将 @property
放在上方,将 @abc.abstractmethod
放在下方。因为 @abstractmethod
装饰器来定义一个没有实现的抽象方法,而 @property
会引入一些实现。如果把 @abc.abstractmethod
放在上方,则会报错:AttributeError: attribute 'isabstractmethod' of 'property' objects is not writable
。
总结¶
在 Python 中,Getter 和 Setter 是一种用于访问和修改对象属性的方法。它们提供了一种间接访问对象属性的方式,以便在访问或修改属性时执行其他操作。
Getter 是用于获取对象属性值的方法。它们通常用于访问私有属性或执行一些计算操作后返回属性值。在 Python 中,可以使用@property 装饰器来定义 Getter 方法。
Setter 是用于设置对象属性值的方法。它们通常用于验证属性值的有效性或执行一些额外的操作后设置属性值。在 Python 中,可以使用@property 装饰器和对应的 Setter 方法来定义 Setter。
以下是 Getter 和 Setter 的示例代码:
class Person:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
# 创建 Person 对象
person = Person("Alice")
# 使用 Getter 获取属性值
print(person.name) # 输出:Alice
# 使用 Setter 设置属性值
person.name = "Bob"
print(person.name) # 输出:Bob
在上面的示例中,name
属性使用了 Getter 和 Setter 方法。Getter 方法被@property
装饰器修饰,允许通过person.name
的方式获取属性值。Setter 方法被@name.setter
装饰器修饰,允许通过person.name = value
的方式设置属性值。
Getter 和 Setter 提供了一种封装属性访问的方式,使得可以在访问或修改属性时执行其他操作,例如验证输入、计算属性值等。这种封装可以增加代码的可维护性和灵活性。
请注意,Python 中还有其他实现属性访问控制的方式,例如使用property()
函数或使用__getattr__()
和__setattr__()
魔术方法。Getter 和 Setter 是其中一种常见的实现方式,但并不是唯一的方式。
Python 进阶教程系列文章