目录
- 面对对象
- 什么是面对对象
- 类和对象
- 属性的访问顺序
- 初始化函数
- 绑定方法
- 绑定给类方法
- 非绑定方法
- 继承
- 组合
- 封装
- property
- 多态
- 多态之ABC模块
- 鸭子类型
- isinstance和issubclass
- 反射
- 类中的特殊的内置函数
- ——str——
- ——del——
- exec
- 元类
- ——call——
- ——init——和——doc——
- 元类总结:
- 单例模式
面对对象
什么是面对对象
在学习面对对象之前需先明确面向过程
在学习面向对象之前我们写的任何代码都是面向过程的
什么是面向过程?面向过程是一种编程思想
即面向 朝向过程去编写代码,在编写代码是要时刻想着过程
过程是什么?解决问题的步骤 流程,即第一步干什么
其目的是将一个复杂的问题,拆分为若干的小问题,按步骤一一解决
也可以说将一个复杂性的问题,流程化(为其制定一个固定的实现流程)
面向过程编程的优缺点
优点:复杂的问题简单化
缺点:由于其实现流程是固定的,一但中间某一个步骤发生了修改,将导致整体都需要修改,牵一发而动全身
实现某个功能固定流程,再在原来的基础上增加新功能,就需要重新设计一遍,扩展性差,维护性差
由于面向过程所存在的缺点,才催生了新的编程思想
面向对象
是一中编程思想,即编写代码的方法
oop ,object—oriented programming的简称,就是面向对象的英文缩写
将程序看做是一堆对象的集合,实现功能的方式就是对象之间交互来实现
什么是对象
一切皆对象
对象指的是具备某些特征与进的结合体,是实实在在存在的具体物体(描述完就能找到)
什么是类
类就是类型,类别,分类
类是一个抽象概念,不是实际存在的,是根据一些具备相同特征和技能的对象 抽取到的
类与对象之间的关系
类事包含一系列相同特征和技能的对象
对象是属于某个类的实例
先有类还是先有对象
在生活中是先有对象再根据对象特征和技能划分类
在程序中是先有类还是先有对象
程序员在程序中相当于上帝 需要什么就创造什么
在程序中先有类,才能通过类来产生对象,要先确定对象具备什么特征和行为才能产生对象
面向对象的优缺点
优点:
1、不用考虑繁琐的实现步骤,从一个操作工变成了指挥者。
2、扩展性高,当需要一个新功能时,加一个具备新功能的对象,命令它去完成任务
3、各个对象之间耦合度低,当一个一个对象出现了问题,不会对其他对象产生影响
4、可维护性高
缺点:
1、面对对象的复杂度比面向过程高
2、无法预知执行结果,就象lol的游戏过程,一定不能把过程写死,每个英雄的行为是不固定的
使用场景:需要较高的扩展性时,(直接与用户发生交互的程序,例如qq,微信)
对于不需要扩展性的程序而言,使用面对对象反而增加了复杂度
为什么要使用面对对象,因为在目前情况下,很多程序与用户直接打交道而用户的需求千变万化,所以对扩展性要求非常高
面对对象编程的本质就是使用使用不同的对象来完成
类和对象
在计算机创建一个人类对象
要得到对象必须要先告诉计算机,这个对象具备是吗特征和行为,所以需要先定义类
注:类名要按照大驼峰的方式来书写ThisIsPerson 每个单词的首字母都大写
class Person:
pass
class就是定义一个类
在类中面向对象的特征和行为
class Person:
#用变量来描述特征
name = "李四"
sex = "man"
age = 20
#得到对象 通过调用类也称之实例化 或创建对象
obj = Person()
print(obj)
#输出结果<__main__.Person object at 0x00000279452B7C50>
#使用对象的实现(说的就是特征)
print(obj.name)
print(obj.age)
print(obj.sex)
表示模块名为main 其包括一个Person类 通过Person类产生了一个对象地址为0x00000279452B7C50
这是一个Person类的对象,其地址为0x00000279452B7C50
class Student:
number = "007"
name = "嘉文"
sex = "man"
age = 20
# 学生的学校 由于每个学生的学校都是相同 所以将其放到类中
school = "Tsinghua"
# 创建了两个学生对象
stu1 = Student()
stu2 = Student()
print(stu1)
print(stu2)
# 每个对象内存地址都是不同的 , 在创建对象时,计算机会申请一个新的内存空间,并将对象中的内容存进去
print(id(stu1.name))
print(id(stu2.name))
# 由于name的值时声明在类中的,所以每个对象使用的都是同一份
print(stu1.name)
print(stu2.name)
# 为对象单独制定属性
stu1.name = "韩信"
stu2.name = "陈大炮"
print(stu1.name)
print(stu2.name)
# 每个对象的name属性都不同,则意味需要给每个对象单独指定name
存储属性的位置有两个 一个是类中,还有一个是对象中
当每个对象的某个特征都相同时则放到类中
当每个对象的某个特征多不同时则放到对象中
通过_ dict _ 可以获取一个对象中包含的内容
stu1.age = 30
print(stu1.__dict__)
print(stu2.__dict__)
#获取类中包含的内容
print(Student.__dict__)
属性的访问顺序
class Car:
c_type = "破鞋"
color = "red"
price = 400000
c1 = Car()
c2 = Car()
print(c1.__dict__)
print(c2.__dict__)
print(c1.c_type)
# 当对象中不存在是会到类中去找
c1.c_type = "法拉利"
print(c1.__dict__)
print(c2.__dict__)
print(c1.c_type)
print(c2.c_type)
# 如果对象中存在这个属性,优先访问对象中的属性
print(Car.__dict__)
查找顺序 先查对象 再查类
当创建一个类的时候 会产生名称空间,存储类中名称和值的绑定关系
当创建一个对象的时候 会产生名称空间,存储对象中名称和值的绑定关系
类还有另一个作用 就是 作为对象的模板,所有属于同一个类的对象,都具备类中的公共内容
即使我们什么都不写 类中也存在一些自带的属性,是从父类得到的(继承会详细讨论)
初始化函数
class Student:
# 定义一个函数 用于为对象设置属性
def set_attr(obj, name, sex, age):
obj.name = name
obj.sex = sex
obj.age = age
pass
stu1 = Student()
# 指定属性
stu1.name = "张无忌"
stu1.sex = "man"
stu1.age = 18
stu2 =Student()
stu2.name = "周芷若"
stu2.sex = "woman"
stu2.age = 78
# stu3 = Student()
# stu4 = Student()
# set_attr(stu3,"灭绝","woman",88)
# set_attr(stu4,"金毛狮王","man",20)
# print(stu3.__dict__)
# print(stu4.__dict__)
# set_attr这个函数目的是用于设置对象的属性 ,如果没有对象则该函数没有存在的意义,也就是初始化函数与类应该是一个整体
# 应该讲这个函数放到类中
stu3 = Student()
stu4 = Student()
Student.set_attr(stu3,"金毛狮王","man",80)
print(stu3.__dict__)
Student.set_attr(stu4,"张全蛋","man",28)
print(stu4.__dict__)
# 现在已经简化了代码 但是对象的创建和初始化步骤是分开的, 通常对象一旦创建 就应该进行初始化,所以最好时将创建与初始化进绑定
stu5 = Student()
print(stu5)
# 作为一个人 一旦出生 性别必须要指定
# 带有__开头__结尾的函数 是一些特殊内置函数,会在某个时间点自动触发执行
class Person:
# 初始化函数名称是固定 该函数会在调用类是时自动执行,self参数必须有,表示要进行初始化的对象,系统会自动传值
def __init__(self,name,age):
print("执行了 __init__")
print(self)
self.name = name
self.age =age
p1 = Person("张三丰",80)
print(p1.__dict__)
p2 = Person("李狗蛋",20)
print(p2.__dict__)
# init 函数用于初始化对象,它会在创建对象时,自动执行,并传入调用类时传递的参数,第一个参数表示要初始化的对象本身,
# self(第一个)参数不需要手动传递
# self表示对象自己 是一个形式参数,名字可以随便取,但是不建议修改
绑定方法
绑定方法
什么是绑定 把两个东西捆绑在一起
什么是方法 方法 就是 函数
函数是专业术语,不好理解,面向对象编程思想,是要我们模仿现实生活中的抽象概念,为了方便理解就把函数称之为方法
绑定方法就是 把对象与函数进行绑定
为什么要把把对象与函数进行绑定
调用函数 就变成了调用对象的方法
对象本质上就是一种存放数据的容器
函数是用于处理数据的代码
绑定方法就是将数据与处理数据的函数绑定在一起
最终问题是 为什么要把数据与处理数据的函数绑定在一起?
如何使用绑定方法
class Student:
school = "BeiJing"
def __init__(self,name,sex,age):
self.name = name
self.sex = sex
self.age = age
def learning(self):
print("正在学习..")
def sayHI(self):
print("hello my name is %s my age:%s my sex:%s" % (self.name,self.age,self.sex))
# 默认情况下 在类中定义的函数都是绑定方法,共同点是,都会讲对象作为第一个参数self
stu1 = Student("一个学生","man",18)
stu1.sayHI()
Student.sayHI(stu1)
# 当用用对象来调用类中的方法时,默认把对象传入方法中
# 而用类名来调用时,则需要手动传入对象
print(stu1.sayHI)
#>
# 这是一个绑定方法,本质上是Student类中的sayHI函数 现在把这个函数绑定给了地址为0x000001784F889C50的对象
stu2 = Student("李四","女",19)
stu2.sayHI()
print(stu2.sayHI)
# 只要拿到对象 就同时拿到了数据和处理数据的方法
为设么要有绑定
第一个问题传递参数,必须手动传递,很有可能传参顺序而发生错误
第二个问题每次处理数据 都需要手动传参数
第三个问题 当要处理的数据特别的多 就不能再定义为变量了 你可以使用列表出来存储要处理的数据
但是 每次处理 都需要先获取数据 在传递给处理数据的函数
所以将 要处理的数据与 处理数据的函数进行绑定
如何绑定? 就得用对象了
绑定给类方法
class Student:
school = "beijing"
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
# 绑定方法分为两种 一种是绑定给对象的,一种绑定给类的
# @classmethod
# def print_school_name():
# print("学校名为:%s" % Student.school)
# 绑定给类的方法 使用一个装饰器叫classmethod,必须有一个参数,表示当前类,参数名也可以自己定义,建议不要修改
@classmethod
def print_school(cls): # 输出类里面叫school的属性
print(cls.school)
# def print_school2(self): # 输出类里面叫school的属性
# print(self.school)
# 这是绑定给对象的方法
def sayHello(self):
print(self.name, " 说: 你好")
# Student.print_school_name()
Student.print_school()
Student.print_school()
stu1 = Student("印度阿三","woman",20)
stu1.print_school()
stu10 = Student("赵六","男",20)
Student.print_school2(stu10)
对象绑定方法 可以使用对象来调用 也可以使用类名来调用
在对象调用时会自动传入对象自己
类调用时不会自动传参
类的绑定方法,对象和类都能调用,并且都会自动传入这个类
一个方法到底应该绑定给对象还是帮对给类?
当要处理的数据包含在类中时,就应该绑定给类
当要处理的数据包含在对象中时,就应该绑定给对象
# 有一个Dog类 每一个Dog对象都应该会叫 会跑 请用面向对象来完成
class Dog:
def __init__(self,nikename,gender,age):
self.nikename = nikename
self.gender = gender
self.age = age
def run(self):
print("不好了 ",self.nikename,"跑了 ")
def bark(self):
print("听",self.nikename,"在瞎叫...")
d1 = Dog("大金毛","母的",2)
d2 = Dog("大黄","公的",3)
d1.run()
d2.bark()
类的绑定方法和对象的绑定方法的相同与不同
相同点:
1.都会自动传值
2.都可以被类和对象调用
不同点:
1.对象绑定方法再对象调用时 传的是对象自己 而类绑定方法字自动传的是类自己
2.第一个参数 个cls 一个叫self
非绑定方法
class Teacher:
def __init__(self,name,sex):
self.name = name
self.sex = sex
# @staticmethod 用于定义个非绑定方法
@staticmethod
def test_func(num):
print("test_func run!")
print(num)
Teacher.test_func(1)
t1 = Teacher("矮根","男")
t1.test_func(100)
print(t1.test_func)
什么是非绑定方法 在类中 即不绑定给类 也不绑定给对象
特点:没有自动传参数的效果 ,类和对象向都能调用,就是一个普通函数
当你的这个功能不需要访问类的数据 也不需要访问对象的数据,就可以作为一个非绑定方法
使用场景较少
练习
1.创建Student类
2.拥有以下属性: 姓名 性别 年龄 学校 班级
3.拥有以下方法
save(name) 其作用是将这个对象序列化到文件中
get_obj(name) 其作用是根据name从文件中反序列化为得到一个对象
分析save方法和get_obj 应该作为绑定给对象还是绑定给类
import json
class Student:
school = "beijing"
def __init__(self,name,sex,age,classes):
self.name = name
self.age = age
self.sex = sex
self.classes = classes
def save(self):
dic = {"name":self.name,"sex":self.sex,
"age":self.age,"classes":self.classes}
with open(self.name,"wt",encoding="utf-8") as f:
json.dump(dic,f)
@classmethod
def get_obj(cls,name):
with open(name,"rt",encoding="utf-8") as f:
dic = json.load(f)
obj = cls(dic["name"],dic["sex"],dic["age"],dic["classes"])
return obj
# stu1 = Student("阿尔法","man",20,"py5期")
# stu2 = Student("张三","woman",20,"py5期")
# stu1.save()
# stu2.save()
stu = Student.get_obj("阿尔法")
print(stu)
print(stu.name)
继承
1、什么是继承
继承是一种关系,必须存两个对象才可能产生这种关系,在现实生活中的继承,儿子继承父亲的就叫继承
被继承的称之为父,继承的一方称之为子
在程序中继承指的是类与类之间的关系
2、为什么要使用继承
在生活中,通过继承,子可以直接享受父提供的内容,例如财产
在程序中,通过继承可以直接使用父类已有的代码
3、继承的使用
缺定继承关系:在子类中类名后面加上括号,括号内加上父类名称即可
在python中一个子类可以有多个父类,多个父类在括号中用逗号隔开
子类可以使用父类中的属性
子类也可以使用父类中的函数
抽象:相同属性抽象出来放到共同的父类中
class Father:
pass
class GanDie:
pass
class Son(Father,Gandie)# 继承的方法
pass
class Person::
def __init__(self, name age, sex)
self.name = name
self.age = age
self.sex = sex
def eat(self):
print("正在吃饭")
def student(self)
print("正在学习")
class Teacher(person):
def terching(self):
print("老师正在上课")
t1 = Teacher("blex",30,"woman")
t1.eat()
t1.study()
class Student(Person):
pass
stu1 = Student
stu1.eat()
stu1.study()
#通过继承 避免了重复代码的编写
#通过抽象 避免了继承到一些不应该有的内容
#应该先抽象再继承
#在抽取过程中 可能会有一些跟业务需求无关的类,这是正常的 这些称之为公共父类
#公共父类的作用是存储多个自乐相同属性和技能
4、派生与覆盖
什么是派生
派生指的是 子类继承某个父类 并且拥有自己独特的属性或技能 该子类称之为派生类
子类出现了与父类重复的名字,称之为覆盖
子类出现与父类不同的名字称之为派生
class Person:
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def sayHI(self):
prtit('hello 我是%S 今年%s岁 性别:%s'%(self.name, self.age, self.sex))
class Test(Person): #该类不能称之为派生类,因为没有与任何独特的内容与父类完全一致
pass
class Student(Person): #Student类就称之为派生类(有自己独特的属性与内容)
def __init__(self,name,age,sex,number):
self.name = name
self.age = age
self.sex = sex
self.number = number
# 上课
def up_class(self):
print("%s 正在上课.....")
5、子类访问父类父方法
子类访问父类有两种方法
1、指名道姓的方法访问
2、使用super的方法访问
class Person:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
print(self)
def sayHI(self):
print("hello 我是%s 今年%s岁 性别:%s" % (self.name,self.age,self.sex))
class Student(Person):
def __init__(self,name,age,sex,number):
# self.name = name
# self.age = age
# self.sex = sex
#上述代码与父类中完全相同
Person.__init__(self,name,age,sex)
self.number = number
# 上课
def up_class(self):
print("%s 正在上课.....")
def sayHI(self):
# print("hello 我是%s 今年%s岁 性别:%s" % (self.name,self.age,self.sex))
# 访问父类中的方法来简化代码
# 指名道姓
# Person.sayHI(self)
super.sayHI()
print("学号:",self.number)
stu1 = Student("阿三",20,"woman","9527")
# print(stu1)
# print(stu1.name,stu1.age,stu1.sex)
stu1.sayHI()
class Student(Person):
def __init__(self,name,age,sex,number):
super(Student,self).__init__(name,age,sex)
self.number = number
# 上课
def up_class(self):
print("%s 正在上课.....")
stu1 = Student("阿三",20,"woman","9527")
print(stu1)
print(stu1.name,stu1.age,stu1.sex)
7、存在继承关系后的属性查找顺序
对象——》类——》父类——》父类的父类。。。。
优先找对象,如果没有对象,则找类,如果类没有,会沿着继承关系一直找到最顶层的父类
无论属性还是方法 查找方法都是一样的
存在多个继承的话就按照继承的顺序,先继承谁就找谁
8、经典类与新式类
所有直接继承或间接继承object的类 都是新式类
object 称之为 根类 意思是所有类 都源自于object类
为什么这么设计
例如:创建对象时,需要申请内存空间,创建新的名称空间将对象的属性放入名称空间,这一系列复杂的基础操作,都由object来完成
简单地说object提供了一下常用的基础操作
即所有类都属于新式类(在python3中)
在python3中默认所有类都是新式类
而python2中默认的是经典类(不会自动基础object)
——bases——用于查看父类
显示属性的查找顺序类别,继承查找属性就是按照mro来查找的
super访问父类内容时 按照mro列表属性查找
组合
什么叫组合
一个对象将另一个(或多个)对象作为属性
组合的目的
降低冗余,降低耦合度
class Person:
def __init__(self,name,sex,age):
self.name = name
self.sex = sex
self.age = age
class Student(Person):
def __init__(self,name,sex,age,number):
super().__init__(name, sex, age)
self.number = number
def show_info(self):
print(self.__dict__)
def select_cursor(self):
print("%s 正在选课...." % self.name)
class Teacher(Person):
def __init__(self,name,sex,age,salary,level):
super().__init__(name,sex,age)
self.salary = salary
self.level = level
def set_score(self):
print("%s 正在为学生打分..." % self.name)
# 上述代码通过继承减少了代码冗余
# # 需要给学生添加手机这个属性,(手机号码,运营商,归属地,可以打电话)
# stu = Student("李寻欢","女",20,"飞刀一班")
# print(stu.name)
# print(stu.phonenumber)
stu = Student("李寻欢","女",20,"飞刀一班")
stu.show_info()
学生会增加各种各样的新的属性 比如手机 比如电脑, 这样Student中会增加大量的属性和方法
后期的维护成本非常高
这时就需要使用组合 来完成减少代码冗余
class Phone:
def __init__(self,phonenumber,operator,address):
self.phonenumber = phonenumber
self.operator = operator
self.address = address
def call(self):
print("%s 正在拨号" % self.phonenumber)
class Person:
def __init__(self,name,sex,age):
self.name = name
self.sex = sex
self.age = age
class Student(Person):
def __init__(self,name,sex,age,number):
super().__init__(name, sex, age)
self.number = number
def show_info(self):
# print("name:%s sex:%s, age:%")
print(self.__dict__)
def select_cursor(self):
print("%s 正在选课...." % self.name)
class Teacher(Person):
def __init__(self,name,sex,age,salary,level):
super().__init__(name,sex,age)
self.salary = salary
self.level = level
def set_score(self):
print("%s 正在为学生打分..." % self.name)
stu = Student("乔峰","男",38,"007")
# 学生买了一台手机 所以增加一个手机属性
p1 = Phone("1999999999","中国小米移动","上海浦东")
stu.phone = p1
# 学生要打电话
stu.phone.call()
# 通过将手机对象和学生对象进行组合 完成了需求,并且相比较继承而言
# 耦合度低 手机和学生在修改的时候 互不影响
# 同时也减少了代码冗余
# 继承是一种关系 什么是什么 学生是人类 老师是人类 (如果把手机相关内容插入到人类中,相当于学生是手机???)
# 组合是 对象之间的关系 学生对象拥有手机对象
封装
1、什么是封装
生活中的封装类似工厂,将一堆产品装入纸箱,拿胶带封起来
在程序中,封装指的是将内容隐藏起来,在面向对象中有什么内容可以被隐藏?就是属性和方法
2、为什么要封装
1、提高安全性(例如:cup频率 缓冲区大小,电脑,手机汽车等)
对于封装属性而言,是通过给范围和修改增加额外的逻辑判断来实现的
2、封装是为了明确区分内部和外部
3、如何使用封装
在属性或方法名称前 加上两个下划线,就可以将其设置为私有
另外补充:python中权限只有两种,公开(谁都能访问)的和私有的(只有自己能访问)
属性的封装,通过需要提供相应的设置器和访问器
4、什么时候用
5、封装的实现原理
6、封装的特点
被隐藏的内容 在内部是可以直接访问的,外部无法访问
封装的方法:
class Student:
def __init__(self,name,sex,age,idCard):
self.name = name
self.age = age
self.sex = sex
self.__idCard = idCard
def say_hi(self):
print("hello i am %s age is : %s sex is %s" %
(self.name,self.age,self.sex))
def test(self):
print(self.__idCard)
# 可以使用方法 将内部封装的内容返回出去
def get_idCard(self):
return self.__idCard
# 如果直接返回的话 就没必要设置隐藏了 ,谁都可以通过调用方法来获取,我们再方法中加上自己的判断逻辑
def get_idCard2(self,pwd): # 如果密码正确我就给你身份证号码
if pwd == "1111":
return self.__idCard
else:
print("滚 你没有资格知道我的身份证...")
def set_idCard(self,pwd,new_idCard): # 如果密码正确就允许修改
if pwd == "1111":
self.__idCard = new_idCard
else:
print("滚 你没有资格修改我的身份证...")
stu = Student("步惊云","男",20,"3206661998445132")
stu.say_hi()
# print(stu.__idCard) # 加上__也访问不了
# stu.test() # 但在内部是可以访问的
# idcard = stu.get_idCard()
# print(idcard)
# 即实现了隐藏 又提供了访问的接口 这就是封装的用法
# idcard = stu.get_idCard2("1111")
# print(idcard)
# 身份证填错了 要修改
# stu.__idCard = "123" # 这样是无法修改原来的身份证信息的 而是添加了新的属性
#
# print(stu.__idCard)
# print(stu.get_idCard2("1111"))
# 调用方法来修改隐藏的属性
stu.set_idCard("1111","xxxxxxxxxxxxxxxxxx")
print(stu.get_idCard2("1111"))
# 总结:对于被隐藏的属性 访问和修改都需要通过方法 get用于获取 set用于设置(也称之为设置器和访问器)
class A:
def __f1(self):
print("f1 run")
def run_f1(self):
self.__f1()
a = A()
a.run_f1()
# ATM 的取款功能
# 1.插入银行卡 2.输入密码 3.选择取款金额 4.取款
class ATM:
def __insert_card(self):
print("插入银行卡...")
def __input_pwd(self):
print("输入密码...")
def __select_money(self):
print("选择取款金额...")
def withdraw(self):
self.__insert_card()
self.__input_pwd()
self.__select_money()
print("取款成功!....")
atm = ATM()
atm.withdraw() # 外部调用这个简单的接口 就能完成一系列复杂的操作
# atm.select_money() #直接调用内部的方法 是没有意义的无法完成整个功能
# 当然用户按照流程一一调用也可以完成功能 但是太麻烦
# atm.insert_card()
# atm.input_pwd()
# atm.select_money()
# 封装方法 如何封装 给方法名字前面加上双下划线
# 封装方法的好处:
# 1.提高安全性
# 2.隔离复杂度 (将复杂的内容隔离到内部 外部只留下简单的接口 对于使用者 难度降低)
封装的实现原理
class Person:
def __init__(self,name,sex,age,idCard):
self.name = name
self.sex = sex
self.__age = age
self.__idCard = idCard
def get_idCard(self):
return self.__idCard
def __test(self):
pass
print("aaaaaaaaaa")
# p = Person("比尔盖茨","男",20,"322323232332332")
#
# # print(p.__idCard)
# p.__idCard = "XXXXXX"
# print(p.__idCard)
#
# print(p.get_idCard())
#
# print(p.__dict__)
# print(Person.__dict__)
#
#
# p.__xxxxxxxxxxxx = 1
#
# print(p.__dict__)
# 通过__dict__ 可以发现
# 1.私有的属性和方法名称 前自动加上了_类名 python就是通过这样的转换方式来实现封装
# 2.只有在类的内部的双下划线才会被自动转换,并且这个转换过程只执行一次,在类定义完成后 后续添加的双下划线开头的名称是不会自动转换的
# 3.父类中私有的方法 子类中无法使用
#在父类中定义的私有方法 能不能被子类所覆盖?
总结
封装指的是隐藏内部的实现细节,对外提供访问的接口
封装是隐藏,但是不是单纯的隐藏
能封装的内容:属性和方法
如何封装:名字前加__双下划线
对于被隐藏的属性访问和修改都需要通过方法 一般get用于获取,set用于设置(也称之为设置器和访问器)
property
当一些属性的值,不是固定的而是通过计算得来的时候,我们必须为这个属性增加方法才能完成计算
但是一旦使用方法后,该属性的范围的就成了方法的调用,很明显与其他属性访问方式不同,这个给使用者造成迷惑
所有需要将这个方法伪装成普通属性,这就用到了Property
property可以将方法伪装成属性,利用这个特点我们也可以将其使用到封装中
之前没有这个装饰器我们需要为私有的属性提供两个方法,但是这样一来访问私有属性的方法就发生了变化
这时候就可以使用property来进行伪装,使的访问访问私有属性与访问普通属性的方式一致
另外property还提供了setter(用于修改属性的值)和deleter(删除属性的值)
# BIM案例:
class Person:
def __init__(self,name,weight,height):
self.name = name
self.weight = weight
self.height = height
# self.bmi = weight/(height*height)
# def bmi(self):
# return self.weight / (self.height * self.height)
@property
def bmi(self):
return self.weight / (self.height * self.height)
p = Person("尔晴",50,1.5)
# print(p.bmi)
# p.weight = 90
# print(p.bmi)
# 现在 虽然可以实现需求 但是我们把一个属性变成了一个行为 这是不合理的
# print(p.bmi())
# p.weight = 90
# print(p.bmi())
# 使用property装饰器 可以将一个方法伪装成一个属性
print(p.bmi)
p.height += 0.2
print(p.bmi)
class Student:
def __init__(self,name,sex,idCard):
self.name = name
self.sex = sex
self.__idCard = idCard
def get_idCard(self):
return self.__idCard
def set_idCard(self,new_id):
self.__idCard = new_id
@property # 需要掌握
def idCard(self):
return self.__idCard
@idCard.setter #了解的
def idCard(self,new_id):
self.__idCard = new_id
@idCard.deleter # 了解的
def idCard(self):
print("身份证属性被删除了.....")
del self.__idCard
stu = Student("尔康","男","323254554554")
# print(stu.get_idCard())
# stu.set_idCard("xxxx")
print(stu.get_idCard()) # 使用装饰器前
print(stu.name) # 普通属性的访问
print(stu.idCard) # 使用装饰器后
stu.idCard = "aaaaaaa" # 使用装饰器后的修改操作
print(stu.idCard)
del stu.idCard
print(stu.__dict__)
print(Student.__dict__)
多态
什么是多态
多种状态 形态
生活中具备多种形态的事物 水(水蒸气 冰 液态水)
一种事物具备多种形态或状态 就称之为多态
官方解释:不同对象 可以响应同一方法,并作出不同的行为,产生不同结果
如何实现多态?
让几个不同类拥有相同的父类,这样一来他们就具备了形态的方法,每个子类要覆盖父类的方法
程序中的多态 例如动物
class Animal:
def eat(self):
print("动物在吃东西...")
def sleep(self):
print("动物在睡觉...")
def drink(self):
print("动物需要水.....")
class Person(Animal):
def eat(self):
print("人吃粮食...")
class Pig(Animal):
def eat(self):
print("猪吃饲料...")
class Dog(Animal):
def eat(self):
print("狗吃骨头...")
person = Person()
pig = Pig()
dog = Dog()
person.eat()
pig.eat()
dog.eat()
#假设你学习了C1驾照 意味着 所有C1类的汽车都能开 因为每种C1汽车的驾驶方式相同
# 当使用了多态之后 对象的使用者不需要关心这个对象具体的实现,只需要知道该对象属于哪个基类,就能直接使用它
# 如此扩展性变高了
class Phone:
def call(self):
print("手机就能打电话..")
def send_msg(self):
print("手机能发短信..")
class WindowsPhone(Phone):
def call(self):
print("拨号打电话..")
def send_msg(self):
print("输入号码发短信..")
class IPhone(Phone):
def call(self):
print("拨号打电话..")
def send_msg(self):
print("输入号码发短信..")
# 可以定义一个方法接受一个手机为参数 无论是是类型的手机 都可以被使用
def CALL(phone):
phone.call()
wp = WindowsPhone()
ipx = IPhone()
CALL(wp)
CALL(ipx)
# 系统内置的方法有很多都体现了多态
print(len("abc"))
print(len([1,2,3,4,]))
print(len({"name":"123","sex":"man"}))
print("abc".__len__())
print([1,2,3,4,].__len__())
print({"name":"123","sex":"man"}.__len__())
print(len({1,23,4,5}))
多态之ABC模块
多态是多个对象拥有相同的方法,但是我们没有从严格要求说必须要提供这些方法,子类完全可以不提供这些方法
现在要做的就是,严格要求 子类必须实现父类声明的方法
import abc
# abstract class 是抽象类的缩写 抽象的意思是 不清晰 不具体 看不懂
#使用ABC模块来限制子类 的步骤
#1.为类中指定元类为abc.ABCMeta
#2.在相应的方法上加上abc.abstractmethod装饰器
class Animal(metaclass=abc.ABCMeta):
@abc.abstractmethod
def eat(self):
pass
@abc.abstractmethod
def drink(self):
pass
class Cat(Animal):
def eat(self):
print("猫爱吃鱼肉...")
def drink(self):
print("用舌头舔..")
class Dog(Animal):
def eat(self):
print("狗爱吃骨头...")
def drink(self):
print("用舌头舔..")
class Pig(Animal):
def eat(self):
print("猪 爱吃草...")
def drink(self):
print("用嘴吸的..")
p = Pig()
# p.eat()
c = Cat()
# c.eat()
# 多态的好处 完全不需要考虑得到的对象时声明类型 只要知道了其基类中的内容就能使用
def feeding(animal):
animal.eat()
animal.drink()
鸭子类型
python推崇简单的编程方法
鸭子类型 如果一个对象叫声象鸭子 走路也象鸭子 那就把它当成鸭子
对应到代码中就是:只要你的行为一样 那就把你当成同一个类型来看待
class Duck:
def bark(self):
print("鸭子嘎嘎叫...")
def run(self):
print("摇摇晃晃走....")
class Chicken:
def bark(self):
print("鸡咯咯叫...")
def run(self):
print("摇摇晃晃走....")
def test(obj):
obj.bark()
obj.run()
duck = Duck()
c = Chicken()
test(duck)
test(c)
# 如果你足够自觉 你可以不使用abc模块 也不需要基类 自觉地将方法名字都写成一样 同样可以实现多态
# 这种方式称之为鸭子类型
isinstance和issubclass
class Person:
pass
class Student:
pass
stu = Student():
print(type(1) == type(1)) # 判断两对象是不是同一个类型
print(isinstance(stu,Student)) # 判断stu对象是不是S图dent类的实例
print(issubclass(Student,Person)) # 判断一个类是不是另一个类的子类(所有类都是object的子类或子子类)
反射
反射 其实说的就是反省
简单的说 就是对象具备一种修正错误的能力
hasattr 是否存在某个属性
getattr 获取某个属性的值
setattr 设置摸个属性的值
delattr 删除某个属性的值
这几方法有一个共同点 都是通过字符串来操作属性
可以理解为,通过字符串来操作属性,就叫反省
class Student(object):
def __init__(self,name,sex,age):
self.name = name
self.sex = sex
self.age = age
def study(self):
print("学生正在学习")
stu = Student("egon","woman")
#当你获取到一个对象时,但是并不清楚这个对象内部细节是,就需要使用反射了
def test(obj):
if hasattr(obj,"name")
print(getattr(obj,"name","没有name属性"))
test(stu)
setattr(stu,"school","beijing")
delattr(stu,"shchool")
print(getattr(stu,"school",,"没有学校属性"))
delattr(stu,"age")
print(stu.age)
总结:
如果在编码期间 就能明确知道我要访问的属性,就没有必要使用反射
如果在编码期间,无法明确知道我要访问的属性,就应该使用反射
class Student:
def study(self):
print("学习中....")
stu = Student()
res = getattr(stu,"study",None)
print(res)
def eat(self):
print("正在吃饭...")
# 可以通过反射的方式为对象增加一个方法 但是注意 这样增加的方法就是一个普通函数 不会自动传值
setattr(stu,"eat",eat)
print(getattr(stu,"eat",None))
练习:
# 需要编写一个CMD工具 这个工具可以支持两个命令 dir ,tasklist
class CMD:
def dir(self):
print("列出当前文件夹目录....")
def tasklist(self):
print("查看任务列表.....")
cmd = CMD()
res = input("请输入指令:").strip()
if hasattr(cmd,res):
func = getattr(cmd,res)
print(func)
func()
else:
print("输入的指令不正确....")
类中的特殊的内置函数
前后带双下滑线的都是特殊的内置函数 会在莫些时机自动执行,一般不情况下我们不应该直接调用
——str——
当我们需要自定义打印显示内容时,就需要实现——str——方法
该方法必须返回一个字符串,返回的是什么,打印出来就是什么
class Test:
def __init__(self,name):
self.name = name
def __str__(self):
print("str run....")
return self.name
t = Test("安米")
print(int(1).__str__())
# print([1,2,3,5])
# print(t)
# 在讲一个对象转换字符串时 本质就是在调用这个对象 __str__方法
print(str(t))
——del——
当对象被从内存中删除时,会自动执行
另一种情况时 程序员手动删除了这个对象也会自动执行
什么时候使用它
在Python中有自动内存管理机制,所有python自己创建的数据不需要我们做任何操作
但是有一种情况,我们使用python打开了一个不属于python管理的数据时
比如打开一个文件,这个文件一定是操作系统在打开的,会占用系统内存,而python解释器无法操作系统内存的
所以当你python解释器运行结束后,文件依然处于打开状态,这时候就需要使用——del——来关闭操作文件
简单的说,当程序运行结束时,需要做一些清理操作 就使用——del——
——del——也称之为析构函数:分析构造,并拆除对象
class Student:
def __del__(self):
print("对象被删除了....")
stu = Student()
# 手动删除 立即执行__del__
del stu
import time
time.sleep(5)
class TextFile:
def __init__(self,filepath,mode="rt",encoding="utf-8"):
self.file = open(filepath,mode=mode,encoding=encoding)
def read(self):
return self.file.read()
def write(self,text):
self.file.write(text)
# 该方法其实就是一个通知性质 仅仅是告诉程序员 对象即将被删除
def __del__(self):
# 在这里关闭系统的文件 妥妥的
self.file.close()
tf = TextFile("2.今日内容.txt")
print(tf.read())
# tf.file.close() 不需要手动关闭了 在对象删除时会自动关闭
tf.read()
exec
execute的缩写
表示执行的意思
其作用,是帮你解析python代码,并且将得到的名称存储到指定的名称空间,解释器内部也是调用它来执行代码的
exec需要三个参数
1、需要一个字符串对象,表示需要执行的python语句
2、是一个字典,表示全局名称空间
3、也是一个字典,表示局部名称空间
# 如果同时制定了 全局和局部 则 会字符串中包含名称 解析后存到局部中
# print(globalsdic)
print(localsdic)
localsdic["func1"]()
# # 如果只传了一个传参数 则 将字符串中包含名称 解析后存到全局中
exec("""
aaaaaaaaaaaaaaaaaaaa = 1
bbbbbbbbbbbbbbbbbbbbbbbbbbbb = 2
""",localsdic)
元类
元类是指 用于产生类的类,type就是元类
所有的自定义都是通过type实例化得来
创建模块的过程
1、创建一个空的名称空间
2、执行内部的代码
3、将得到的名字放到名称空间中
# class也是一个对象
class Student(object):
school = "北京大学!"
def study(self):
print("学习中...")
# 使用type可以发现 类其实是type类型的实例(对象)
print(type(Student))
# 我们可以自己调用type来实例化产生一个类
# myclass 包含的代码
code = """
name = "张三"
age = 18
def hello(self):
print("hello %s" % self.name)
"""
#类的名字
class_name = "MyClass"
#类的的父类们
base_classes = (object,)
#类的名称空间
namespace = {}
exec(code,{},namespace)
res = type(class_name,base_classes,namespace)
print(Student)
print(res.name)
print(res.age)
print(res.hello)
# 1.类是由type实例化产生的
# 2.我们可以使用type来产生一个类
# 3.一个类是由 类名字 类的父类元祖 类的名称空间 三个部分组成
class Test(object): #Test = type("Test",(object,),{})
pass
——call——
调用的意思
在对象呗调用时执行(函数,类)
自定义元类的目的
1、可以通过——call——来控制对象的创建过程
2、可用控制类的创建过程
# 自定义一个元类 元类也是一个类 但是需要继承type
class MyMeta(type):
# self 表示要创建对象的那个类(Person) *args是调用Person类时传入的参数
def __call__(self, *args, **kwargs):
print("MyMte中的 call run'")
print(self,*args,**kwargs)
# 下面的三步是固定写法 一个模板 只要你需要控制对象的创建过程 就应该先把模板写出来
# 1.创建空对象
obj = object.__new__(self)
# 2.调用初始化方法
self.__init__(obj,*args,**kwargs)
# self.__init__(obj)
# 3.得到一个完整的对象
return obj
# 修改Person类的元类为MyMeta
class Person(metaclass=MyMeta):
def __init__(self,name,age):
self.name = name
self.age = age
def __call__(self, *args, **kwargs):
print("call run...")
#调用Person这个对象时 执行的是 Person的类(type)中__call__ 方法
p = Person("张三疯",80)
print(p)
# 当调用对象时 会执行该对象所属类中的__call__方法
# p()
print(p.name)
print(p.age)
——init——和——doc——
要控制类的创建过程,只要找到类所属中的——init——即可
——doc——代表的是文档注释
def __init__(self,class_name,bases,namespace):
print("============================")
#print(self.__dict__)
# 我要控制 类的名字 必须 是大写开头
if not class_name.istitle():
print("类名 必须大写开头...... ")
# 该代码是主动抛出异常
raise TypeError("类名 必须大写开头...... ")
#要空类的创建 必须包含__doc__这个属性
if not self.__doc__:
raise TypeError("类中必须有文档注释.....")
pass
class Student(metaclass=MyMeta): # Student = MyMeta("Student",(object,),{})
"""
这是文档注释 可以通过__doc__来获取
这是一个学生类
"""
# 在类的__init__中可以控制该类对象的创建过程
def __init__(self,name):
print("-----------------------")
print(self.__dict__)
self.name = name
print(Student.__doc__)
元类总结:
元类是用于创建类的类
学习元类是为了能控制类的创建过程以及类实例化对象的过程
一、控制类创造的过程
1、创建一个元类(需要继承type)、
2、覆盖——init——方法,该方法会将新建的类对象,类名、父类名,名称空间都传进来,可以利用这些信息再做处理
3、对于需要 被控制的类,需要指定metaclass为上面的元类
二、控制类实例化对象的过程
1、创建一个元类(需要继承type)
2、覆盖——call——方法会将正在实例化对象的类,调用类是传入的参数都传进来
3、在——call——方法中,必须先编写模板代码
3.1、创建空对象
3.2、调用类的——init——方法来初始化这个空对象
3.3返回该对象
4、加入你需要控制的逻辑
类的三个组成部分
类名,父类们,名称空间
元类——》实例化产生——》类——》实例化产生——》对象
单例模式
一种设计模式(套路)
单个实例
一个类如果只有一个实例,那么该类就称之为单例
class MyMeta(type):
obj = None
def __call__(self, *args, **kwargs):
if not MyMeta.obj:
obj = object.__new__(self)
self.__init__(obj,*args,**kwargs)
MyMeta.obj = obj
return MyMeta.obj
#打印机类
class Printer(metaclass=MyMeta):
"""
这是一个单例类 请不要直接实例化 使用get方法来获取实例
"""
obj = None
def __init__(self,name,brand,type):
self.name = name
self.brand = brand
self.type = type
def printing(self,text):
print("正在打印 %s" % text)
# 通过该方法来获取对象 可以保证只有一个对象
# 但是这还不够 因为 还是可以通过调用类产生新对象
# 就应该使用元类 来控制实例化的过程 __call__
# 在__call__ 中编写代码 保证每次调用call 都返回同一个实例 即可
@classmethod
def get_printer(cls):
if not cls.obj:
obj = cls("ES005","爱普生","彩色打印机")
cls.obj = obj
print("创建了新的对象")
return cls.obj
# 以下三个对象 的数据完全相同 但是却 占用三分内存空间
# p1 = Printer("ES005","爱普生","彩色打印机")
# p2 = Printer("ES005","爱普生","彩色打印机")
# p3 = Printer("ES005","爱普生","彩色打印机")
# 现在要处理问题就是 如何能够限制该类 只能实例化一个对象
p = Printer.get_printer()
print(p)
p = Printer.get_printer()
print(p)
p = Printer.get_printer()
print(p)
p = Printer.get_printer()
print(p)
p1 = Printer("ES005","爱普生","彩色打印机")
p2 = Printer("ES005","爱普生","彩色打印机")
print(p1)
print(p2)
# print(p1,p2,p3)