孤灯寒照雨,
湿竹暗浮烟。
——司空曙《云阳馆与韩绅宿别》
好久不见
本期文章9038字
根据之前文章的后台统计数据推算
本期预计所需阅读时间50分钟
本系列文章已加入“维权骑士”(rightknights.com)的版权保护计划
本文原创内容受到保护
如无特殊声明
文章中“方法”一词均为名词,意为类中的函数
详细定义见第16期文章
非本义时,表述用词为“方式”等
数据的隐藏
面向对象编程的一个关键部分是封装。通俗来讲,封装就是将相关变量和函数整合到一个易于使用的对象中。
对于封装,一个相关的概念是数据隐藏。数据隐藏要求我们隐藏类中的具体功能的实现过程的细节,并为想要使用该类的人提供明确的标准接口。也就是说,在编写一个类的时候,我们要尽量做到:在类的外部只可以调用我们最终使这个类具有的功能,而尽量不允许在类的外部直接访问类内部实现某个功能的过程中的具体某个步骤(比如用到的内部变量(类的属性)或者只在类内部使用过的函数(类的内部方法)),这种满足数据隐藏要求的类,通常需要设计一些相关的接口来与外界沟通消息,但是我们在这种类的外部不可以直接访问类内部的某个变量(属性)或函数(方法)。
但是在Python中,略有不同。在Python的哲学中,人们可以做任何事情(官方称法为"we are all consenting adults here"(“我们认为大家都是成年人了”),意为程序不应对人们加以限制,人们自己会承担全部责任),这意味着这门语言编写出来的程序不应该对访问某个类的一部分内容加以任意限制。因此,在Python代码中,我们没有办法强制使某个方法或属性保持严格地对外部隐藏。
当然,说是这么说,但是如果我们非要写一个满足数据隐藏要求的类,也是可以的。这时,我们要用到弱私有方法和属性(weakly private methods and attributes)。
弱私有方法和属性在名字的开头有一个下划线。这种名字就表示它们是私有的,不应该(注意,不是“不能”)被外部代码使用。但是,这只是一个约定,并不会真正阻止外部代码访问它们。所以它们只是“弱”(weakly)私有的,不是真正的私有。
这种“弱私有”的声明,唯一的实际效果是在我们用import语句导入这些类的时候。import语句不会导入以单下划线开头的变量。只有在这种时候,“弱私有”才真正对外界的访问有阻碍作用。
来看例子:
class Queue:
def__init__(self, contents):
self._hiddenlist= list(contents)
defpush(self, value):
self._hiddenlist.insert(0, value)
defpop(self):
return self._hiddenlist.pop(-1)
def__repr__(self):
return "Queue({})".format(self._hiddenlist)
queue = Queue([1, 2, 3])
print(queue)
queue.push(0)
print(queue)
queue.pop()
print(queue)
print(queue._hiddenlist)
运行结果:
>>>
Queue([1, 2, 3])
Queue([0, 1, 2, 3])
Queue([0, 1, 2])
[0, 1, 2]
>>>
在上面的代码中,属性_hiddenlist由于名字以单下划线开头,所以意思是它被标记为了私有,但如上文所述,Python中没有绝对的私有,只是“弱私有”,所以仍然可以在外部代码中访问这个属性。
注:__repr__魔术方法的作用为以字符串的形式表示对象。
测试题18.1.
选择。使用单个下划线作为一个方法的名字的开头,目的是什么?
防止外部代码调用它
使它的运行速度更快
将其标记为私有
点击下方空白区域查看答案
▼
将其标记为私有
只是“标记为”,并不能真正防止外部代码调用它。前文已多次说明这一点。
Python中,其实也有“强私有方法”(strongly private methods)的表示方法。
我们这里说到的Python中的强私有方法和属性,在其名字的开头有双下划线。在Python中,这个双下划线会导致它们的名称被破坏,而名字被破坏自然意味着它们无法从类的外部访问(因为访问它们需要用到它们的名字,但是名字被破坏掉了)。也就是说,我们通过破坏我们想设为“私有”的方法和属性的名字,变相地使它们成为了“私有的”方法和属性。
Python设计这样一种“名称破坏”机制,最初的目的不是为了实现私有,而是为了避免在类的继承的过程中,编写存在具有同名的方法或属性的子类时出现错误。
名称被“破坏”了的方法和属性,仍然可以从外部访问,但要注意的是,在访问这种方法和属性时,我们要使用它们的特殊格式的名称。这种特殊格式是:在原来含有双下划线的名字前加上单下划线和所属类的名字。
例如,我们可以使用_Spam__private这个名字在类的外部访问Spam类的__private方法。
来看例子:
class Spam:
__egg = 7
defprint_egg(self):
print(self.__egg)
s = Spam()
s.print_egg()
print(s._Spam__egg)
print(s.__egg)
运行结果:
>>>
7
7
AttributeError: 'Spam' object has no attribute '__egg'
>>>
可以看到,在名字前加双下划线之后,我们就不能直接用这个含有双下划线的名字来访问相应的方法或属性。如果真的这样做了,程序会报错“找不到所提供的名字所对应的方法或属性”。
测试题18.2.
假设我们已经实例化出了类a的一个对象c,那么如何在类a和对象c的外部访问它的属性__b?
点击下方空白区域查看答案
▼
c._a__b
类方法和静态方法
到目前为止,我们所见到的所有方法,都由类的对象使用。在用到这些方法时,把对象本身传递给方法的self参数。这种方法是最常见的一种方法。
类方法是与之不同的——类方法不在对象中调用,而是由类调用,在调用时,类本身被传递给方法的cls参数。
类方法用classmethod装饰器标记。
class Rectangle:
def__init__(self, width, height):
self.width = width
self.height = height
defcalculate_area(self):
return self.width * self.height
@classmethod#这行就叫装饰器
defnew_square(cls, side_length):
return cls(side_length, side_length)
square =Rectangle.new_square(5)
print(square.calculate_area())
运行结果:
可以看到,new_square就是一个类方法,在类上调用,而不是在类的实例上调用。它返回的是类cls(也就是Rectangle)的新对象。
讲道理,参数self和cls只是一种约定俗成的名字,它们可以改成其他任何名字。只要是参数列表(也就是那个括号)内的第一个参数就行。然而,这几个名字是普遍遵循的一个习惯,所以坚持使用它们是明智的,这样有助于别人阅读自己写的代码。
测试题18.3.
填空,使sayHi()成为一个类方法。
class Person:
def __init__(self, name):
self.name = name
______________
_____ sayHi( _____ ):
print("Hi")
领取专属 10元无门槛券
私享最新 技术干货