类与对象

面向对象编程思想 OOP

什么是面向对象

面向对象是一种编程思想,是前辈们总结出的经验,指导程序员如何编写出更好的程序 ,

核心是对象,程序就是一系列对象的集合, 程序眼负责调度控制这些对象来交互着完成任务:

案例:1 把大象装进冰箱 ?

​ 面向过程:

​ 1.打开冰箱

​ 2.装入大象

​ 3.关闭冰箱

​ 面向对象:

​ 找个具备装大象的技能的对象

​ 在面向对象中程序员的角度发生改变,从具体的操作者变成了指挥者;

​ 强调:对象不是凭空产生的,需要我们自己设计

案例2:

​ 西天取经

​ 如来有一堆破书要传出去,他没有自己干,而是找了五个对象帮他去干,

​ 如来只要负责控制调度的对象即可 ,如果某个对象发生变化,也不会影响其他的对象 , 扩展性

三大优点

​ 1.扩展性

​ 2.灵活性

​ 3.重用性

缺点:

​ 1.程序的复杂度提高了

​ 2.无法准确预知结果

使用场景

对扩展性要求较高的程序 ,通常是直接面向 用户的,例如:qq,微信

面向过程编程思想

关注的核心是过程,过程是就是一步一步的执行步骤,既先干啥在干啥

优点:逻辑清晰 , 复杂问题简单化,流程化

缺点: 扩展性差,可维护性差

使用场景:

对扩展性要求较低的程序例如:系统内核,git,计算器

记住:不是所有程序都要面向对象,得分析具体需求

类和对象

oop(面向对象)

这是OOP中的最核心的两个概念

既类型,类别 ,是一种抽象概念

是一系列具备相同特征和相同行为的对象的集合

对象

就是具体存在的某个事物,具备自己的特征和行为

对象就是特征和技能的结合体

类和对象的关系

类包含一系列对象

对象属于某个类

在生活中是先有对象再有类

而在程序中是先有类才能有对象,我们必须先告诉计算机这类的对象有什么特征有什么行为

总结出一个结论:在使用面向对象编程时,第一步就是思考需要什么样的对象,对象具备什么样的特征和行为,从而根据 这些信息总结出需要的类型

创建类和对象

定义类的语法

class 类的名称:
    # 类中的内容 描述属性和技能 
    #描述属性用变量
    #描述行为用函数 

#类名称 书写规范  首先是见名知意 名称是大驼峰命名法  
#驼峰就是单词首字母大写 , 大驼峰是第一个字母大写,小驼峰是第一个字母小写

创建对象的语法:

# 创建类
class Person:				
    pass

#创建对象 
p = Person()

属性的写法:

属性可以写在类中 
类中的属性,是所有对象公共的 

也可以写在对象中 
对象中的属性,是每个对象独特的(不一样的)


如果类中和对象中存在同样的属性,先访问对象 如果没有在访问类 


练习: 描述一个老师类 需要包含 一个公共属性和 一个独特的属性 

class Teacher:
    school = "oldboy"
    
t1 = Teacher()
t1.name = "jack"
t1.age = 28

属性的增删改查

增加属性 
对象变量名称.属性名称 = 属性值

删除属性
del 对象的变量名称.属性名称 

修改 
对象.属性 = 新的值 

查看属性  访问的是对象的所有属性 
print(对象.__dict__)

访问对象的类信息
print(对象.__class__)

init方法

叫做初始化方法,本质上就是一个函数

特点1: 当实例化 对象时,会自动执行init方法

特点2:会自动将对象作为第一个参数传入,参数名称位self ,self可以是别的名字,但不建议改

功能:用户给对象赋初始值

练习:创建一个类具备几个属性,通过初始化方法来给他设置属性

class Dog:
    def __init__(self,kind,color,age):
        self.kind = kind				# 相当于创建对象,增加属性名称等于属性值
        self.color = color
        self.age = age

d1 = Dog("二哈","黑白",1)
d1 = Dog("泰迪","棕色",2)


注意:该函数不能有任何返回值/.... 只能是None  规定如此..

对象的精髓就是将数据和处理数据的函数整合到一起 ,这样一来拿到一个对象就同时拿到了需要处理的数据以及处理数据的函数

对象的绑定方法

默认情况下类中的方法都是对象绑定方法

其特殊之处在于,

当使用对象调用该函数时会自动传入对象本身,作为第一个参数

当使用类名来调用时他就是一个普通函数,有几个参数就得传几个参数

练习:写一个学生类,具备一个打招呼的技能 要能输出自己的名字信息

class Student:
    
    def __init__(self,name):
        self.name = name
     
    def say_hi(self):
        print("hello my name is %s" % self.name)
        
s = Student("tcy")
s.say_hi()						# hello my name is tcy

类绑定方法

类绑定方法用@classmethod来装饰

特殊之处:不管用类还是对象调用,都会自动传入类本身,作为第一个参数

什么时候绑定给对象:当函数逻辑需要访问对象中的数据时

什么时候绑定给类:当函数逻辑需要访问类中的数据时

非绑定方法

或叫做静态方法,就是即不需访问类的数据,.也不需要访问对象的数据

语法:@staticmethod

不常用

l练习:为学生类添加一个save方法 一个get方法

save是将对象存储到文件中

get是从文件中获取对象

import pickle


class Student:
    def __init__(self,name):
        self.name = name

    def say_hi(self):
        print("name",self.name)

    def save(self):
        with open(self.name,"wb") as f:
            pickle.dump(self,f)

    @staticmethod
    def get(name):
        with open(name,"rb") as f:
            obj = pickle.load(f)
            return obj

# stu = Student("ccc")
# stu.save()
#
# stu1 = Student("jack")
# stu1.save()

obj = Student.get("jack")
print(obj.name)

继承

什么是继承

继承是一种关系,描述两个对象之间,什么是什么的关系

例如麦兜,佩奇,猪刚鬣 都是猪啊,

在程序中,继承描述的是类和类之间的关系

例如a继承了b, a就能直接使用b已经存在的方法和属性

a称之为子类,b称之为父类,也称之为基类

为什么要使用继承:

继承的一方可以直接使用被继承一方已经有的东西

其目的是为了重用已经有的代码,提高重用性

如何使用继承

语法:

class 类名称(父类的名称):
    类的内容 
    
#在python中 一个子类可以同时继承多个父类 

抽象:

不具体,不清晰,很模糊,看不懂

将多个子类中相同的部分,进行抽取,形成一个新的类,这个过程也称之为抽象的过程

正确的使用继承:

​ 1.先抽象在继承

​ 2.继承一个已经现存的类,扩展或是修改原始的功能

属性的查找顺序

class A:
    text = "haha"

class B(A):
    text = "heihei"
    pass

b = B()
b.text = "xixi"

print(b.text)

对象自己的 - > 所在类中 -> 找父类 - >父类的父类 ->Object

派生

当一个子类中出现了与父类中不同的内容时,这个子类就称之为派生类

通常子类都会写一些新的代码,不可能和父类完全一样 , 既通常都是派生类,

所以派生类指的就是子类

覆盖

也称之为重写 overrides

当子类出现了与父类名称完全一致的属性或是方法 ,根据查找顺序先查找子类,所以父类被覆盖

子类中访问父类的内容

语法:

方式1:
super(当前类名称,self).你要调的父类的属性或方法
方式2:
super().你要调的父类的属性或方法
方式3:
类名称.你要调的父类的属性或方法(self)  
#方式3与继承无关 

# 代码展示
class Parent:
    text = 'abc'

    def say_something(self):
        print('anything')

class Sub(Parent):

    def show_info(self):
        # 子类访问父类,方式1
        # print(super(Sub, self).text)
        # super(Sub, self).say_something()


        # 子类访问父类,方式2 python3中新语法(最常用)
        # print(super().text)
        # super().say_something()

        # 子类访问父类,直接指定类名称,方法三
        # print(Parent.text)
        # Parent.say_something(self)

# sub = Sub()
# sub.show_info()

小案例:

# 实现一个可以限制元素类型的容器 (字典,列表,元组,集合,字符串)自定义仅能传入的类型

class Mylist(list):
    def __init__(self,element_type):				  
        self.element_type = element_type

    def append(self,object):
        if type(object) == self.element_type:			# 判断对象传入类型和函数调用类型是否一致
            # 子类访问父类的append函数
            super(Mylist,self).append(object)			# 一致则调用父类方法
        else:
            print('sorry sir you element type not is %s'%self.element_type)

m = Mylist(int)					# 这里的int 传入的是初始化里面的element_type
m.append(1)						# 这里的1 传入的是函数append里面的object
print(m[0])						# 查看索引0 结果为1
m.append('123')					# sorry sir you element type not is <class 'int'>

强调在强调:

当你继承一个现有的类,并且你覆盖了父类的init方法时,必须在初始化方法的第一行调用父类的初始化方法,并传入父类所需的参数 ,避免后续出现无法继承父类方法问题

# 代码展示:
class Student(list)							# 这是子类在继承父类,父类名为class
	def __init__(self,name,gender)			 # 子类进行初始化
		super().__init__()					# 避免子类调用父类出现问题,需要调用父类的初始化方法

组合

也是一种关系,描述两个对象之间 是什么有什么的关系

例如,学生有手机 ,游戏中角色拥有某些装备

将一个对象作为另一个对象的属性,(既什么有什么)

组合的目的:

也是为了重用现有代码

什么时候使用继承:分析两个类的关系,到底是不是:什么是什么的关系

什么时候使用组合:如果两个类之间 没有太大的关系,完全不属于同类

另外组合相比继承,耦合度更低了

class Phone:
    def __init__(self,price,kind,color):
        self.price = price
        self.kind = kind
        self.color = color


    def call(self):
        print("正在呼叫")

    def send_message(self):
        print("正在发送短信")

class Student:
    def __init__(self,name,gender,phone):
        self.name = name
        self.gender = gender
        self.phone = phone

    def show_info(self):
        print("name:%s gender:%s" %self.name,self.gender)



phone = Phone(1000,"apple","red")
stu1 = Student("rose","male",phone)		
stu1.phone.call()						# 学生类调用phone类,直接组合起来掉用使用

了解知识点

菱形继承

首先明确python支持多继承

补充:新式类与经典类

python3中任何类都是直接或间接继承了Object

新式类,任何显式或隐式地继承自object的类就称之为新式类, python3中全都是新式类

经典类,既不是Object的子类 ,仅在python2中出现

当出现了菱形继承时,新式类,先深度,当遇到了共同父类时就广度 ,也就是具备共同父类找广度,各自的父类深度优先,新式类,就是深度优先

封装

​ 什么是封装,就是将复杂的丑陋的,隐私的细节隐藏到内部,对外提供简单的使用接口

​ 对外隐藏内部实现细节,并提供访问的接口

为什么需要封装

​ 两个目的

​ 1.为了保证 关键数据的安全性

​ 2.对外部隐藏实现细节,隔离复杂度

什么时候应该封装

​ 当有一些数据不希望外界可以直接修改时

​ 当有一些函数不希望给外界使用时,

如何使用:

语法:

class Person:
    def __init__(self,id_number,name,age)
    	self.__id_number = id_number	# 加上双下划线就被封装,无法修改
        self.name = name
        self.age = age
        
    def show_id(self):
        print(self.__id_number)
   
p = Person('11111111111',"jack",29)
p.id_number = '222'
p.show_id()							# 11111111111 并未被修改
print(p.id_number)					# 222 打印对象值

被封装的内容的特点:

​ 1.外界不能直接访问

​ 2.内部依然可以使用

class Pc:
    def __init__(self,price,kind,color):
        self.price = price
        self.kind = kind
        self.color = color

    def open(self):
        print("检查硬件")
        self.__check_divice()

    def __check_divice(self):
        print("硬件检测1")
        print("硬件检测2")
        print("硬件检测3")
        print("硬件检测4")

p = Pc(666,'mac','black')
p.open()				    # 输出硬件检查的print结果
p.__check_divice()			# 调用报错 被隐藏了 无法直接访问

权限

学习了封装后就可以控制属性的权限

在python只要两种权限,

1.公开的.默认就是公开的

2.私有的,只能由当前类自己使用

在外界访问私有的内容

属性虽然被封装了,但是还是需要使用的,在外界如何访问

通过定义方法类完成对私有属性的修改和访问

"""
这是一个下载器类,需要提供一个缓存大小这样的属性
缓存大小不能超过内存限制

"""
class Downloader:
    def __init__(self,filename,url,buffer_size):
        self.filename = filename
        self.url = url
        self.__buffer_size = buffer_size

    def start_download(self):
        if self.__buffer_size <= 1024*1024:
            print("开始下载....")
            print("当前缓冲器大小",self.__buffer_size)
        else:
            print("内存炸了! ")


    def set_buffer_size(self,size):
        #可以在方法中添加额外的逻辑
        if not type(size) == int:
            print("大哥 缓冲器必须是整型") 
        else:
            print("缓冲区大小修改成功!")
            self.__buffer_size = size		

    def get_buffer_size(self):
        return self.__buffer_size

d = Downloader("葫芦娃","http://www.baicu.com",1024*1024)


# 通过函数取修改内部封装的属性
d.set_buffer_size(1024*512)

# 通过函数访问内部封装的属性
print(d.get_buffer_size())

d.start_download()

这样一来我们可以在外界修改这个关键数据时,做一些限制 ,只能通过调用内部方法,然后通过内容方法对内部进行修改。

property装饰器

通过方法来修改或访问属性,本身没什么问题,但是这给对象的使用者带来了麻烦.

使用必须知道哪些是普通属性,哪些是私有属性,需要使用不同的方式来调用他们

property装饰就是为了使得调用方式一致

有三个相关的装饰器

1.property   该装器用在获取属性的方法上 
2.@key.setter  该装器用在修改属性的方法上 
3.@key.deleter 该装器用在删除属性的方法上 


注意:key是property装饰的方法的名称 也就是属性的名称 
内部会创建一个对象 变量名称就是函数名称  
所以在使用setter和deleter时 必须保证使用对象的名称取调用方法 
所以是 key.setter,当名称不为案例中的key时,对应的也是修改为@名称.setter 或者@名称.deleter

案例:

class A:
    def __init__(self,name,key):
        self.__name = name
        self.__key = key					# 隐藏的初始值外部无法调用

    @property								# 将方法伪装成普通属性
    def key(self):							# 用加装饰器的方式,外部调用可当做普通属性获取
        return self.__key					

    @key.setter
    def key(self,new_key):
        if new_key <= 100:
            self.__key = new_key			# 对隐藏的初始值修改
        else:
            print("key 必须小于等于100")

    
    @key.deleter
    def key(self):
        print("不允许删除该属性")
        del self.__key
        
a = A("jack",123)
print(a.key)						# 123		
a.key = 321							# 修改key调用的@key.setter
print(a.key)						# 321
del a.key							# 删除key调用属性key对应的@key.deleter

python实现封装的原理

就是在加载类的时候,把__替换成了 _类名__

python一般不会强制要求程序必须怎么怎么的,

封装:

对外部隐藏内部的实现细节,并提供访问的接口

好处:

​ 1.提高安全性

​ 2.隔离复杂度

语法:将要封装的属性或方法名称前加上双下划线

访问被隐藏的属性:

​ 提供用于访问和修改的方法

使用property装饰器可以将一个方法伪装成普通属性,保证属性之间调用方法一致

封装的实现原理 ,替换变量名称

property 可以用来实现计算属性

计算属性指的是:属性的值,不能直接获得,必须通过计算才能获取

例如:正方形求面积

接口 了解

接口是一组功能的集合,但是接口中仅包含功能的名字,不包含具体的实现代码

接口本质是一套协议标准,遵循这个标准的对象就能被调用

接口目的就是为了提高扩展性:

例如电脑提前指定制定一套USB接口协议,只要你遵循该协议,你的设备就可以被电脑使用,不需要关心到底是鼠标还是键盘

案例:

class USB:
    def open(self):
        pass

    def close(self):
        pass

    def  read(self):
        pass

    def write(self):
        pass

class Mouse(USB):
    def open(self):
        print("鼠标开机.....")

    def close(self):
        print("鼠标关机了...")

    def read(self):
        print("获取了光标位置....")

    def write(self):
        print("鼠标不支持写入....")


def pc(usb_device):
    usb_device.open()
    usb_device.read()
    usb_device.write()
    usb_device.close()

m = Mouse()
# 将鼠标传给电脑
pc(m)

class KeyBoard(USB):
    def open(self):
        print("键盘开机.....")

    def close(self):
        print("键盘关机了...")

    def read(self):
        print("获取了按键字符....")

    def write(self):
        print("可以写入灯光颜色....")

# 来了一个键盘对象
k = KeyBoard()
pc(k)

在上述案例中,PC的代码一旦完成,后期无论什么样的设备 只要遵循了USB接口协议,都能够被电脑所调用

接口主要是方便了对象的使用者,降低使用者的 学习难度,只要学习一套使用方法,就可以以不变应万变

问题:

如果子类没有按照你的协议来设计,也没办法限制他,将导致代码无法运行

抽象类

指的是包含抽象方法(没有函数体的方法)的类,

作用:可以限制子类必须类中定义的抽象方法

abc模块:abstract class 翻译为抽象类,取单词首位

最后:python一般不会限制你必须怎么写,作为一个优秀的程序员,就应该自觉遵守相关协议

所以有了鸭子类型这么一说:

如果这个对象长得像鸭子,走路像鸭子,那就他是鸭子

你只要保证你的类按照相关的协议类编写,也可以达到提高扩展性的目的

抽象引出:

import abc
class AClass(metaclass=abc.ABCMeta):

    @abc.abstractmethod                 # 抽象方法
    def run(self):
        pass


class B(AClass):                        # 子类继承父类,如果父类是抽象方法
    def run(self):                      # 那么在子类中必须类中定义父类的抽象方法
        print('runrunrun')            

b = B()

案例:

调用之前的案例,笔记本提供一个接口供鼠标键盘等使用,但是鼠标和键盘有可能不按照笔记本接口的定义方式调用,由此引出抽象方法

import abc
class USB(metaclass=abc.ABCMeta):

    @abc.abstractmethod						# 定义接口的每个方法都是抽象类
    def open(self):
        pass

    @abc.abstractmethod						# 定义接口的每个方法都是抽象类		
    def close(self):
        pass

    @abc.abstractmethod						# 定义接口的每个方法都是抽象类
    def  read(self):
        pass

    @abc.abstractmethod						# 定义接口的每个方法都是抽象类
    def write(self):
        pass

class Mouse(USB):							# 这里子类继承了父类
    def open(self):							# 必须鼠标的每个方法都和接口保持一致
        print("鼠标开机.....")

    def close(self):
        print("鼠标关机了...")

    def read(self):
        print("获取了光标位置....")

    def write(self):
        print("鼠标不支持写入....")


def pc(usb_device):
    usb_device.open()
    usb_device.read()
    usb_device.write()
    usb_device.close()

m = Mouse()
# 将鼠标传给电脑
pc(m)

# 注意:
上面的抽象类实际上可以不进行定义,想想,如果每个鼠标和键盘都本身就做的符合usb接口的规则,那抽象类是不是多次一举了

接口是一套协议规范,明确子类们应该具备哪些功能

抽象类是用于强制要求子类必须按照协议中规定的来实现

然而,python不推崇限制你的语法, 我们可以设计成鸭子类型,既让多个不同类对象具备相同的属性和方法

对于使用者而言,就可以以不变应万变,轻松的使用各种对象

多态

概念:

一种事物具备多种不同的形态

例如:水 固态 气态 液态

​ 大黄蜂:汽车人,汽车,飞机

官方解释: 多个不同类对象可以响应同一个方法,产生不同的结果

首先强调多态不是一种特殊的语法,而是一种状态,特性(既多个不同对象可以响应同一个方法,产生不同的结果 )

既多个对象有相同的使用方法,

好处:

​ 对于使用者而言,大大的降低了使用难度

​ 我们之前写的USB接口,下的鼠标,键盘,就属于多态

实现多态:

接口 抽象类 鸭子类型 都可以写出具备多态的代码,最简单的就是鸭子类型

案例:

"""
要管理 鸡 鸭 鹅
如何能够最方便的 管理,就是我说同一句话,他们都能理解
既它们拥有相同的方法

"""
class JI:
    def bark(self):
        print("哥哥哥")

    def spawn(self):
        print("下鸡蛋..")

class Duck:
    def bark(self):
        print("嘎嘎嘎")

    def spawn(self):
        print("下鸭蛋")

class E:
    def bark(self):
        print("饿饿饿....")

    def spawn(self):
        print("下鹅蛋..")

j = JI()
y = Duck()
e = E()

def mange(obj):
    obj.spawn()


mange(j)
mange(y)
mange(e)


# python中到处都有多态  
a = 10
b = "10"
c = [10]

print(type(a))
print(type(b))
print(type(c))

oop中的魔法函数:

isinstance

判断一个对象是否是某个类的实例

参数1 要判断的对象

参数2 要判断的类型

def add_num(a,b):
    if isinstance(a,int) and isinstance(b,int):
        return a+b
    return None

print(add_num(10,20))

issubclass

判断一个类是否是另一个类的子类

参数一是子类

参数二是父类

class Animal:

    def eat(self):
        print('动物得吃东西')

class Pig(Animal):
    def eat(self):
        print('猪得吃猪食')



class Tree:
    def light(self):
        print('植物光合作用')

pig = Pig()
t = Tree()

def manage(obj):
    if issubclass(type(obj),Animal):            # 判断传入的obj子类的父类是否是Animal
        obj.eat()
    else:
        print('不是一头动物')

manage(pig)

manage(t)

str

__str__  会在对象被转换为字符串时,转换的结果就是这个函数的返回值 
使用场景:我们可以利用该函数来自定义,对象的是打印格式
    
    
    
class Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def __str__(self):
        return "这是一个person对象 name:%s ,age:%s "%(self.name,self.age)


p = Person("jack",20)
print(p)					# 这是一个person对象 name:jack ,age:20 

del

# 执行时机: 手动删除对象时立马执行,或是程序运行结束时也会自动执行 
    
import time
class Person:
    def __init__(self,name,age):
        self.name = name

    def __del__(self):
        print("del run")

p = Person("jack",20)

time.sleep(10)
print("over")           # over del run 之所以没有执行删除类操作,依然调用调用del,因为程序运行结束,释放内存


# 使用场景:当你的对象在使用过程中,打开了不属于解释器的资源:例如文件,网络端口,也就是在运行代码过程中,打开了一个文件,但是程序运行结束,文件并不会自动关闭,可以使用del方法进行关闭
# del使用案例

# class FileTool:
#     """该类用于简化文件的读写操作 """
# 
#     def __init__(self,path):
#         self.file = open(path,"rt",encoding="utf-8")
#         self.a = 100
# 
#     def read(self):
#         return self.file.read()
# 
#     # 在这里可以确定一个事,这个对象肯定不使用了 所以可以放心的关闭问文件了
#     def __del__(self):
#         self.file.close()
# 
# 
# tool = FileTool("a.txt")
# print(tool.read())

call

执行时机:在调用对象时自动执行,(既对象加括号)

测试:

#call 的执行时机
class A:
    def __call__(self, *args, **kwargs):
        print("call run")
        print(args)
        print(kwargs)

a = A()
a(1,a=100)

slots

该属性是一个类属性,用于优化对象内存占用
优化的原理,将原本不固定的属性数量,变得固定了
这样的解释器就不会为这个对象创建名称空间,所以__dict__也没了  
从而达到减少内存开销的效果 

另外当类中出现了slots时将导致这个类的对象无法在添加新的属性  
# slots的使用
class Person:

    __slots__ = ["name"]					# 声明属性数量
    def __init__(self,name):
        self.name = name
  
p =  Person("jck")

# 查看内存占用
print(sys.getsizeof(p))
p.age = 20 								  # 限制了属性数量,这里无法添加属性

# dict 没有了
print(p.__dict__)						  # 查看类的名称空间报错,发现类的名称空间不在了

getattr setattr delattr

getattr 用点访问属性的时如果属性不存在时执行
setattr 用点设置属性时
delattr 用del 对象.属性  删除属性时 执行


这几个函数反映了 python解释器是如何实现 用点来访问属性 
  
getattribute 该函数也是用来获取属性
在获取属性时如果存在getattribute则先执行该函数,如果没有拿到属性则继续调用 getattr函数,如果拿到了则直接返回  

.

[] 的实原理

getitem setitem delitem

任何的符号 都会被解释器解释成特殊含义 ,例如 . [] ()

getitem 当你用中括号去获取属性时 执行
setitem  当你用中括号去设置属性时 执行
delitem 当你用中括号去删除属性时 执行

运算符重载

当我们在使用某个符号时,python解释器都会为这个符号定义一个含义,同时调用对应的处理函数, 当我们需要自定义对象的比较规则时,就可在子类中覆盖 大于 等于 等一系列方法....

案例:

原本自定义对象无法直接使用大于小于来进行比较 ,我们可自定义运算符来实现,让自定义对象也支持比较运算符

class Student(object):
    def __init__(self,name,height,age):
        self.name = name
        self.height = height
        self.age = age

    def __gt__(self, other):
        # print(self)
        # print(other)
        # print("__gt__")
        return self.height > other.height
    
    def __lt__(self, other):
        return self.height < other.height

    def __eq__(self, other):
        if self.name == other.name and  self.age == other.age and self.height == other.height:
            return True
        return False

stu1 = Student("jack",180,28)
stu2 = Student("jack",180,28)
# print(stu1 < stu2)
print(stu1 == stu2)

上述代码中,other指的是另一个参与比较的对象,

大于和小于只要实现一个即可,符号如果不同 解释器会自动交换两个对象的位置

迭代器协议

迭代器是指具有__iter__和__next__的对象
我们可以为对象增加这两个方法来让对象变成一个迭代器 

案例:

class MyRange:

    def __init__(self,start,end,step):
        self.start = start
        self.end = end
        self.step = step

    def __iter__(self):
        return self

    def __next__(self):
        a = self.start
        self.start += self.step
        if a < self.end:
            return a
        else:
            raise StopIteration
            
for i in MyRange(1,10,2):
    print(i)
   

上下文管理

上下文 context

这个概念属于语言学科,指的是一段话的意义,要参考当前的场景,既上下文

在python中,上下文可以理解为是一个代码区间,一个范围 ,例如with open 打开的文件仅在这个上下文中有效

涉及到的两个方法:

enter

​ 表示进入上下文,(进入某个场景 了)

exit

表示退出上下文,(退出某个场景 了)

当执行with 语句时,会先执行enter ,

当代码执行完毕后执行exit,或者代码遇到了异常会立即执行exit,并传入错误信息

包含错误的类型.错误的信息.错误的追踪信息

注意:

enter 函数应该返回对象自己 
exit函数 可以有返回值,是一个bool类型,用于表示异常是否被处理,仅在上下文中出现异常有用
如果为True 则意味着,异常以及被处理了 
False,异常未被处理,程序将中断报错

# 案例
class Mvopen:
    def __enter__(self):
        print('enter.......')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('exter')
        print(exc_tb,exc_val,exc_tb)


with Mvopen() as m:
    print("start")
    "a+b+123" + 1				# 这段代码处理为异常,所以一旦检测执行__exit__内容
    print("over")				# 这里直接不走

多态

是一种状态,如果程序具备这种状态,对象的使用者,可以很方便忽略对象之间的差异

我们可以通过鸭子类型来让程序具备多态性

posted @ 2021-03-17 13:57  元气少女郭德纲!!  阅读(148)  评论(1编辑  收藏  举报