1 面向过程:将一个复杂的问题,拆分成多个简单的小问题(流程化,按照固定的实现流程)从而简单化
优点:复杂的问题简单化
缺点:流程固定,牵一发动全身,扩展性差,维护性差
应用场景:对扩展性要求低的软件,比如系统内核,脚本程序,文本编辑器这些
2 面向对象:实际上在寻找一堆对象,让他们来完成任务
优点:扩展性高,维护性高,各对象耦合度低(一个对象有问题,不会对其他对象产生影响)
缺点:复杂程度比面向过程高,无法预知执行的结果(对于比如lol这样的游戏,不能把程序写死)
应用场景:需要较高扩展性的软件(比如直接与用户交互的程序, qq与微信)
当然对于一些不需要扩展的程序而言,使用面向对象反而增加了复杂度
二.类与对象
什么是类
是一个抽象概念,不是具体存在的,
类是通过提取一系列对象的相同特征和技能得到的
类的作用是用于标识对象与对象之间的差异,通过类就能大致了解一个对象的特征和行为
什么是对象
具备某些特征和技能的结合体,是具体存在的某个物体,(一切皆对象)
在生活中,先有对象再有类
再程序中,先有类再有对象
# 在类中描述对象的特征和行为
class Person:#类名首字母要大写
# 用变量来描述特征
name = "李四"
sex = "man"
age = 20
# 得到对象 通过调用类 ,也称之为实例化 或 创建对象
obj = Person()
print(obj)
#<__main__.Person object at 0x00000279452B7C50>
# 模块名为main 其包含一个Person类 通过Person类产生了一个对象 地址为0x00000279452B7C50
# 这是一个Person类的对象 其地址为0x00000279452B7C50
# 使用对象的属性(说的就是特征)
print(obj.name)
print(obj.age)
print(obj.sex)
# 通过__dict__可以获取一个对象中包含的内容
print(obj.__dict__)
{}
# 获取类中包含的内容
print(Person.__dict__)
{'__module__': '__main__', 'name': '李四', 'sex': 'man', 'age': 20, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
三.初始化函数
# 带有__开头__结尾的函数 是一些特殊内置函数,会在某个时间点自动触发执行
class Person:
# 初始化函数名称是固定 该函数会在调用类是时自动执行,self参数必须有,表示要进行初始化的对象,系统会自动传值
def __init__(self,name,age):
print("执行了 __init__")
print(self)
self.name = name
self.age =age
p1 = Person("张三丰",80)
print(p1.__dict__)
执行了 __init__
<__main__.Person object at 0x0000025BB20CD4A8>
{'name': '张三丰', 'age': 80}
p2 = Person("李狗蛋",20)
print(p2.__dict__)
执行了 __init__
<__main__.Person object at 0x0000025BB20CD2B0>
{'name': '李狗蛋', 'age': 20}
# init 函数用于初始化对象,它会在创建对象时,自动执行,并传入调用类时传递的参数,第一个参数表示要初始化的对象本身,
# self(第一个)参数不需要手动传递
# self表示对象自己 是一个形式参数,名字可以随便取,但是不建议修改
四.属性的访问顺序
"属性的访问顺序"
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__) # {'c_type': '法拉利'}
print(c2.__dict__) # {}
print(c1.c_type) # 法拉利
print(c2.c_type) #破鞋
# 如果对象中存在这个属性,优先问对象中的属性
print(Car.__dict__) # {'__module__': '__main__', 'c_type': '破鞋', 'color': 'red', 'price': 400000, '__dict__': <attribute '__dict__' of 'Car' objects>, '__weakref__': <attribute '__weakref__' of 'Car' objects>, '__doc__': None}
# 查找顺序为 对象 -> 类
# 当创建一个类的时候 会产生名称空间,存储类中名称和值的绑定关系
# 当创建一个对象的时候 会产生名称空间,存储对象中名称和值的绑定关系
# 类还有另一个作用 就是 作为对象的模板,所有属于同一个类的对象,都具备类中的公共内容
五.绑定方法
"""
绑定方法
什么是绑定 把两个东西捆绑在一起
什么是方法 方法 就是 函数
函数是专业术语,不好理解,面向对象编程思想,是要我们模仿现实生活中的抽象概念,为了方便理解就把函数称之为方法
绑定方法就是 把对象与函数进行绑定
为什么要把把对象与函数进行绑定
调用函数 就变成了调用对象的方法
对象本质上就是一种存放数据的容器
函数是用于处理数据的代码
绑定方法就是将数据与处理数据的函数绑定在一起
最终问题是 为什么要把数据与处理数据的函数绑定在一起?
如何使用绑定方法
"""
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)
#<bound method Student.sayHI of <__main__.Student object at 0x000001784F889C50>>
# 这是一个绑定方法,本质上是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,必须有一个参数,表示当前类,参数名也可以自己定义,建议不要修改
@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()
# 如何定义对象绑定方法 以及类的绑定方法
# 在调用时的区别
# 一个方法到底应该绑定给对象还是帮对给类?
# 当要处理的数据包含在类中时,就应该绑定给类
# 当要处理的数据包含在对象中时,就应该绑定给对象
# 有一个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()
Dog.run(d1)
"""
# 类的绑定方法和对象的绑定方法的相同与不同
相同点:
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从文件中反序列化为得到一个对象
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)
六.面向对象中的常用方法
""" """ # 判断某个对象是不是某个类的实例 # isinstance() class Person: pass class Student(Person): pass stu = Student() #判断 两个对象是不是同一个类型 print(type(1) == type(1)) # 判断stu对象是不是Student类的实例 print(isinstance(stu,Student)) # 是不是子类 # issubclass() # 判断一个类是不是 另一个类子类 (所有类都是object的子类或子子类) print(issubclass(Student,Person))
七.反射
其实就是反省,简单的说就是对象要具备一种修正错误的能力
hasattr 是否存在某个属性
getattr 获取某个属性的指
setattr 设置某个属性的指
delattr 删除某个属性
这个几个方法都有一个共同点: 都是通过字符串来操作属性,
class Student:
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
def study(self):
print("学生正在学习...")
stu = Student("杨彬","woman",38)
# 当你获取到一个对象 但是并不清楚搞对象的内部细节时 就需要使用反射了
def test(obj):
if hasattr(obj,"name"):
print(getattr(obj,"name","没有name属性"))
test(stu) # 杨彬
setattr(stu,"school","beijing") # 为stu这个对象设置school为beijing
delattr(stu,"school") # 删除学校这个属性
print(getattr(stu,"school","没有学校属性")) # 这样打印出来就会出现没有学校属性
delattr(stu,"age") # 删除age这个属性
print(stu.age) # 由于被删除会报错 AttributeError: 'Student' object has no attribute 'age'
八.str
这个主要作用时自定义打印的内容,要注意的是,这个方法必须返回一个字符串 返回的是什么,就打印什么
class Test:
def __init__(self,name):
self.name = name
def __str__(self):
print("str run....")
return self.name
t = Test("安米")
print(t)
#str run....
#安米
# 在讲一个对象转换字符串时 本质就是在调用这个对象 __str__方法
print(str(t))
#str run....
#安米
九.del (析构函数)
将对象从内存中删除时会自动执行 但是注意 并不说用这个方法来删除对象 删除对象时解释器干的
该方法仅仅是 通知你的程序 这个对象即将被删除
在python中 有自动内存管理机制 所以 python自己创建的数据 不需要我们做任何操作
但是有一种情况 我们使用python打开了一个不属于python管理的数据
比如打开了一个文件 这个文件一定是操作系统在打开 会占用系统内存 而python解释器无法操作系统内存的
所以 当你的python解释器运行结束后 文件依然处于打开状态 这时候就需要使用del来关闭系统资源
简单地说 当程序运行结束时 需要做一些清理操作 就使用del
十.exec 方法
""" exec execute的缩写 表示执行的意思 其作用 是帮你解析执行python代码 并且将得到的名称 存储到制定的名称空间 解释器内部也是调用它来执行代码的 """ # 参数一 需要一个字符串对象 表示需要被执行的python语句 # 参数二 是一个字典 表示全局名称空间 # 参数三 也是一个字典 表示局部名称空间 globalsdic = {} localsdic = {} exec(""" aaaaaaaaaaaaaaaaaaaa = 1 bbbbbbbbbbbbbbbbbbbbbbbbbbbb = 2 def func1(): print("我是func1") """,globalsdic,localsdic) # 如果同时制定了 全局和局部 则 会字符串中包含名称 解析后存到局部中 # 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
class MyMeta(type): # 用于新建类对象 def __new__(cls, *args, **kwargs): print("new run") # print(MyMeta) # print(*args) # 调用type通过的__new__方法来创建一个空的类对象 已经将三个组成部分都放到类对象中了 res = type.__new__(cls,*args) return res def __init__(self,class_name,bases,namespace): print("init run") print(self) class Student(metaclass=MyMeta): pass print(Student) """ new 与 init的区 __new__ 比__init__先执行 其作用是创建一个空的类对象 作为一个类对象 必须具备是三个组成部分 所以调用type中的__new__来完成组装 得到这个类对象后需要将其返回 以供__init__来使用 """
十二.通过__call__方法来控制对象的实例化过程
""" __call__ 调用的意思 在在对象被调用时 执行 函数 类 自定义元类 的目的 1.可以通过__call__ 来控制对象的创建过程 2.可用控制类的创建过程 """ # 自定义一个元类 元类也是一个类 但是需要继承type class MyMeta(type): # self 表示要创建对象的那个类(Person) *args是调用Person类时传入的参数 def __call__(self, *args, **kwargs): print("MyMte中的 call run'") print(self,*args,**kwargsl) # 下面的三步是固定写法 一个模板 只要你需要控制对象的创建过程 就应该先把模板写出来 # 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) # class People: # def __init__(self,name): # self.name = name # pass # # # p = People() # p.anme = 1
十三.通过元类控制类的创建过程
# 要控制类的创建过程 只要找到类所属的类 中的__init__即可 class MyMeta(type): # self 刚建出来的类 # 第二个 类的名字 # 第三个 类的父类们 元组 # 第四个 这个类传进来的名称空间 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.加入你需要控制的逻辑 类的三个组成部分 类名 父类们 名称空间 元类 -> 实例化产生 -> 类 -> 实例化产生 -> 对象 """
十四.单例模式
单例(一种设计模式(套路)) 是指一个类中只有一个对象 什么时候使用单例 当要处理的数据只有一份时 当所有对象的属性都相同时 1.通过classmethod 会有问题 依然可以通过调用类 产生新的对象 2.通过元类中的__call__方法 来实现 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)