一.面向过程和面向对象(oop)

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从文件中反序列化为得到一个对象

分析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)

六.面向对象中的常用方法

"""



"""
# 判断某个对象是不是某个类的实例
# 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))
View Code

 

七.反射

其实就是反省,简单的说就是对象要具备一种修正错误的能力

 

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)
View Code

十一.元类

"""
    一切皆对象
    元类是指 用于产生类的类  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
View Code
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__来使用 

"""
View Code

 

十二.通过__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
View Code

十三.通过元类控制类的创建过程

# 要控制类的创建过程 只要找到类所属的类 中的__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.加入你需要控制的逻辑 
     
类的三个组成部分
类名 父类们 名称空间

元类 -> 实例化产生 -> 类 -> 实例化产生 -> 对象


"""
View Code

十四.单例模式

单例(一种设计模式(套路))
是指一个类中只有一个对象
什么时候使用单例

当要处理的数据只有一份时
当所有对象的属性都相同时

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)
View Code