面向对象之 —— 封装
隐藏:
在python中用双下划线开头的方式将属性/方法隐藏起来(设置成私有的),外部无法通过__x这个名字访问(正常情况下可以通过__dict__获取属性)。注:通常在防止重名(重写)方法时使用_x,但是其仍可以在外部通过这个名字访问,所以此时的x非隐藏。
类中所有双下划线开头的名称如__x都会在类定义时自动变形成:_类名__x的形式,但是这种变形操作只在类定义阶段发生,此后再添加的双下划线开头的名称不会发生变形。父类中定义的私有方法子类无法访问也无法覆盖(名称不再相同,子类的方法一定是子类独有的)
class A: def __fa(self): #在定义时就变形为_A__fa print('from A') def test(self): self.__fa() #只会与自己所在的类为准,即调用_A__fa class B(A): def __fa(self): print('from B') b=B() b.test() from A
封装
封装指的是:隐藏内部的实现细节,对外提供访问的接口。封装是隐藏,但不是单纯的隐藏
能封装的内容:属性和方法
封装能够区分内外,封装的属性可以直接在内部使用,而不能被外部直接使用,需要为其提供接口(可以在接口附加上对该数据操作的限制,以此控制对数据属性的操作)
封装的目的:隔离复杂度(将复杂内容隔离到内部 外部只留下简单接口)
封装的方法
第一步:将内容封装到某处
class Atm: def __init__(self,type,size): #__init__称为构造方法,根据类创建对象时自动执行
self.type=type self.size=size
def __insert(self): print("step1") def __pwd(self): print("step2") def __get_money(self): print("step3") def withdraw(self): self.__insert() self.__pwd() self.__get_money() print("取款完成") # 根据类Atm创建对象
# 自动执行Atm类的__init__方法 atm1 = Atm("12306",120) # 讲"12306"和120分别封装到atm self的type和size属性中
# 执行Atm类的withdraw方法 atm2=atm1.withdraw() # 调用__insert()、__pwd()、__get_money()方法
self 是一个形式参数
- 当执行 atm1 = Atm("12306",120)时,self 等于 atm1
- 当执行atm2 = atm.withdraw()时,self 等于atm2
所以,内容其实被封装到了对象 atm1和 atm2 中,每个对象中都有 type和 size 属性
第二步:从某处调用被封装的内容
调用被封装的内容时,有两种情况:
- 通过对象直接调用
- 通过self间接调用
1、通过对象直接调用被封装的内容
根据保存格式可以调用被封装的内容:对象.属性名
class Foo: def __init__(self, name, age): self.name = name self.age = age obj1 = Foo('wupeiqi', 18) print obj1.name # 直接调用obj1对象的name属性 print obj1.age # 直接调用obj1对象的age属性 obj2 = Foo('alex', 73) print obj2.name # 直接调用obj2对象的name属性 print obj2.age # 直接调用obj2对象的age属性
2、通过self间接调用被封装的内容
执行类中的方法时,需要通过self间接调用被封装的内容
class Foo: def __init__(self, name, age): self.name = name self.age = age def detail(self): print self.name print self.age obj1 = Foo('wupeiqi', 18) obj1.detail() # Python默认会将obj1传给self参数,即:obj1.detail(obj1),所以,此时方法内部的 self = obj1,即:self.name 是 wupeiqi ;self.age 是 18 obj2 = Foo('alex', 73) obj2.detail() # Python默认会将obj2传给self参数,即:obj1.detail(obj2),所以,此时方法内部的 self = obj2,即:self.name 是 alex ; self.age 是 78
综上所述,对于面向对象的封装来说,其实就是使用构造方法将内容封装到 对象 中,然后通过对象直接或者self间接获取被封装的内容。
import pickle class Student(object): '''定义一个Student类''' school = "AUST" def __init__(self,name,gender,age,grade): self.name = name self.gender = gender self.age = age self.grade = grade def save(self): with open("%s.pkl"%self.name,"wb",) as f: pickle.dump(self,f) @classmethod def get_ob(cls,name): with open("%s.pkl"%name,"rb",) as f: obj = pickle.load(f) return obj gc = Student("gc","man","20","5") # 序列化 持久化 # gc.save() # 反序列化 obj = Student.get_ob("gc") print(obj.name,obj.gender) # 如果用json 就不能直接将self 序列化,json不支持这个类型 # 将信息写到字典里,再序列化 # dict1 = {"name":self.name ......} #------------------------------------- import json class Student(object): school = "oldboy" def __init__(self,name,gender,age,grade): self.name = name self.gender = gender self.age = age self.grade = grade def save(self): dict1={"name":self.name,"gender":self.gender,"age":self.age,"grade":self.grade} with open("%s.json"%self.name,"wt",encoding="utf8") as f: json.dump(dict1,f) @staticmethod def get_obj(name): with open("%s.json" %name, "rt", encoding="utf8") as f: dict1 = json.load(f) obj = Student(dict1["name"],dict1["gender"],dict1["age"],dict1["grade"]) return obj # ---------------------------------- # @classmethod # 也可以写成 staticmethod 通过类去调用 # def get_obj(cls,name): # with open("%s.json" %name, "rt", encoding="utf8") as f: # dict1 = json.load(f) # obj = cls(dict1["name"],dict1["gender"],dict1["age"],dict1["grade"]) # return obj stu1 = Student("tony","man","20","5") # # stu1.save() # #类方法可以通过类 或者实例去调用 # stu2 = stu1.get_obj("gc") # #静态方法不访问类中的任何值 可以使用类 or 对象去调用 stu2 = Student.get_obj("gc") print(stu2.name)
prooerty装饰器(一种特殊的属性,访问其时会执行一段功能/函数然后返回值)
当一些属性的值,不是固定的而是通过计算得来的时候,必须为这个属性增加方法才能完成计算,但是一旦使用方法后,该属性的访问就变成了方法的调用,很明显与其他的属性访问方式不同,这样给使用者造成迷惑,所以需要将这个方法伪装成普通属性,这就用到了Property。
property可以将方法伪装成属性,利用这个特点,我们也可以将其使用到封装中。
之前我们需要为私有的属性提供两个方法, 但是这样一来访问私有属性时的方式就发生了变化,这时候就可以使用property来进行伪装 使得访问私有属性与访问普通属性的方式一致。
这样将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
面向对象的封装有三种方式:
public:不封装,对外公开
protected:对外不公开,但对朋友(friend)???或者子类公开
private:对谁都不公开
另外 property还提供了 setter(用于修改属性的值) 和 deleter(删除属性的值)