为什么需要使用封装技术?
为了保障数据的安全性,降低代码的耦合度。
如何定义封装?
将具有统一功能或相关的代码块进行高度抽象的处理过程。
封装的具体表现形式?
其主要的表现形式就是将一段代码块高度抽象成一个函数、一个类或类中的方法。
在类中定义的变量还是方法,基本上都可以通过实例对象或类本身进行快速访问调用。而我们在设计类的时候,有些变量或方法不希望让类外部进行调用,其仅供类内部使用。这样可以在一定程度上保护数据,以免受到外部误操作的意外篡改,导致数据不一致。
所以为了更好地保护数据,Python提供了三种访问权限级别: 公有、私有 和 受保护
公有修饰的变量和方法,类的内部或外部均可调用访问。即为大家公共的数据。
私有修饰的变量和方法,仅供类内部使用,本类外部无法访问调用。即不开放数据。
受保护修饰的变量和方法,在程序中只有本类及其子类可以调用。即受限的数据。
Python编程语言很巧妙地使用“下划线+标识符”的命名规则,实现了对变量和方法的访问控制。
1、标识符开头无下划线,该变量或方法为公有权限(即类内外均可访问调用)如:self.name or def show(self)
2、标识符开头双下划线,该变量或方法为私有权限(即仅类内部自己可以访问调用)如:self.__name def __show(self)
3、标识符开头单下划线,该变量或方法为保护权限(即类本身及其子类可以访问调用)如:self._name def _show(self)
class ClassName():
def __init__(self,arg1,arg2,arg3):
#定义一个公有实例变量
self.publicVairable=arg1
#定义一个受保护实例变量
self._projectVairable=arg2
#定义一个私有实例变量
self.__privateVairable=arg3
if __name__=='__main__':
obj1=ClassName(1,2,3)
print(obj1.publicVairable)#1
print(obj1._projectVairable)#2
print(obj1.__privateVairable)#报错
但是可以通过创建公有实例方法,公有实例方法属于类本身,因此其可以访问私有的实例变量。从而达到私有变量对外隐藏,但通过公有方法可以访问的目的。
class ClassName():
def __init__(self,arg1,arg2,arg3):
#定义一个公有实例变量
self.publicVairable=arg1
#定义一个受保护实例变量
self._projectVairable=arg2
#定义一个私有实例变量
self.__privateVairable=arg3
def show(self):
print(self.publicVairable)
print(self._projectVairable)
print(self.__privateVairable)
def __str__(self):
print(self.publicVairable)
print(self._projectVairable)
print(self.__privateVairable)
return ''
if __name__=='__main__':
obj1=ClassName(1,2,3)
obj1.show()
print(obj1)
同样的道理,访问修饰符下划线,也可以在类的方法中使用;
这样,我们的类中方法也进行了调用管控。
class ClassName():
def __init__(self,arg1,arg2,arg3):
#定义一个公有实例变量
self.publicVairable=arg1
#定义一个受保护实例变量
self._projectVairable=arg2
#定义一个私有实例变量
self.__privateVairable=arg3
#调用私有方法
self.__privateMethod()
def __privateMethod(self):
print('私有实例方法,仅在类内部调用')
if __name__=='__main__':
obj1=ClassName(1,2,3)
obj1.__privateMethod()#报错
利用面向对象的编程思想,我们会将所有的事物都先抽象成一个类,而类中有描述该事物的属性和行为。称属性为实例变量(其实不是很好理解)。
Python提供了装饰器@property,可以将方法定义成属性,后续可以使用 obj.属性名称的方式输出。
为了更好地体现封装,在面向对象编程中创建类的规范:
所有的属性都必须为私有(安全保护,防止类外直接访问操作);
使用公有的setter(写入)和 getter(读取)方法操作(对外暴露操作,可间接访问私有变量)。
# 创建一个类
class Person():
# 构造方法
def __init__(self):
# 定义一个私有的实例变量但未初始化
self.__name = None
pass
# 定义name属性
@property
def name(self):
return self.__name
# 定义设置name的实例方法
@name.setter
def name(self, value):
print('>> 调用name属性的name.setter')
if not isinstance(value, str):
raise TypeError('应该为一个字符串类型')
self.__name = value
# 设置获取name的实例方法
@name.getter
def name(self):
print('>> 调用name属性的getter')
return self.__name
# 脚本程序入口
if __name__ == '__main__':
# 使用Person类创建一个对象
obj = Person()
# 设置obj的name属性
obj.name = '张三'
# 输出obj的name属性
print(obj.name)
一个标准的类在创建时,需遵循以下四必须标准规范:
1、类必须定义构造方法 __init__
2、类必须定义对象输出 __str__
3、类属性必须为私有
4、类必须设置公有属性访问函数
继承,面向对象中的继承和现实生活中的继承相同,即:子可以继承父的内容。
父类-子类的纵向编程模式。
创建父类
class MainClass():
…………
pass
创建子类继承父类
class SubClass(MainClass):
…………
pass
一般共有的方法可以抽象出来放在父类中,子类在继承父类的同时凸显出自己个性的方法。
注意:子类不重写 __init__,实例化子类时,会自动调用父类定义的__init__。
重写__init__时,实例化子类,就不会调用父类已经定义的__init__。
class Animal():
def eat(self):
print('%s 吃' %self.name)
def drink(self):
print('%s 喝' %self.name)
class Cat(Animal):
def __init__(self,name):
self.name=name
def call(self):
print('%s 喵喵' %self.name)
class Dog(Animal):
def __init__(self,name):
self.name=name
def call(self):
print('%s 旺旺' %self.name)
if __name__=='__main__':
cat=Cat('折耳猫')
cat.eat()
cat.drink()
cat.call()
dog=Dog('二哈')
dog.eat()
dog.drink()
dog.call()
Python的类可以继承多个类,不像很多高级编程语言Java和C#那样只能继承一个类(单根语系)。
特别注意:在Python2.x版本中,类如果继承了多个类,那么其对象调用寻找方法的方式有两种,分别是:深度优先 和 广度优先(python3中默认均为新式类)。
当类是经典类时,多继承情况下,会按照深度优先方式查找
当类是新式类时,多继承情况下,会按照广度优先方式查找
在Python面向对象的继承特征下,若子类需调用父类的方法,则需要使用super()来实现。
class Cat(Animal):
def __init__(self,name):
self.name=name
def call(self):
print('%s 喵喵' %self.name)
print(super().eat())
Python不支持多态并且也用不到多态,多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚鸭子类型(英语:duck typing),其是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定。这个概念的名字来源于由James Whitcomb Riley提出的鸭子测试。“鸭子测试”可以这样表述:当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子”。在 duck typing 中,关注的不是对象的类型本身,而是它是如何使用的。与此同时,与 duck typing 相对应的风格是 normal typing,后者由对象类型决定了对象的特性。在使用 duck typing 的语言中,也可以编写一个 “鸭子动作” 函数,它能够接受一个任意类型的对象,并调用它的 “飞”、“跑” 等方法。换言之,只要传入对象具有正确的 “飞”、“跑” 等方法,就都可以被函数正常接受和调用。