封装、继承、多态、反射

面向对象(OOP)

#什么是对象
	面向对象是一种编程思想,是指导程序员如何编写出更好的程序的思想
    核心是对象,程序就是一系列对象的集合,我们程序员只需要调度、控制这些对象来交互完成工作
    
#案例1,把对象装进冰箱
	面向过程:
    	1.打开冰箱
        2.装入冰箱
		3.关闭冰箱
    面向对象:
    	找个具备装大象的技能的对象
    
在面向对象中,程序员的角度发生改变,从具体的操作者变成了指挥者
强调:对象不是凭空产生的,需要我们自己设计
    
#案例2,西天取经
	面向过程:
    	如来找5个人,经历九九八十一难来取经,在把经书送回去
    面向对象:
    	如来只需要找5个对象,自己负责调度即可,如果某个对象发生变化,也不会影响其他对象的扩展性
    
#案例3,
	面向过程:
        曹操吟诗
        喝酒吃肉,人生真爽	后改为
        喝酒吃肉,人生几何	后改为
        对酒当歌,人生几何
	面向对象:
    	把字拆成一个个对象,用的时候组装
    	优点:
        	1.扩展性强
    		2.灵活性强
    		3.重用性强
    	缺点:
        	1.程序的复杂度提高了
    		2.无法准确预知结果
    
#使用场景
    1.对扩展性要求较高的程序,通常是直接面向用户的(QQ、微信)
    
#不是所有的程序都要面向对象,要分析具体的需求

面向过程

#面向过程
	面向过程关注的是过程,就是先干什么、后干什么
    
#优点
	逻辑清晰,可以将复杂的问题简单化、流程化
    
#缺点
	扩展性差,可维护性差,重用性低
    
#使用场景
    1.对扩展性要求较低的程序,比如系统内核、git、计算器

类和对象

#类和对象
	类和对象是面向对象编程的最核心的两个概念

#类	
	类就是类型、类别,类就是一个抽象的概念
    类是一系列具备相同特征和相同行为的对象的集合

对象

#对象
	对象就是具体存在的某个事物,有自己的特征和行为
    
#类和对象的关系
	类包含一系列对象
    一个对象属于某个类
    
#先有类,还是先有对象
	生活中,是先有对象,然后有类
    程序中,先有类,才能有对象,我们必须先告诉计算机这类对象有什么特征、有什么行为(也就是说要先创建类)
    所以说,在使用面向对象编程时,第一步就是考虑需要使用什么对象,再考虑使用什么类,然后,先创建类,再创建对象

创建类和对象

#创建类
#语法
	class 类的名字:
        #注释
        #类中的内容,描述属性和技能
        #描述属性用变量
        #描述行为用函数
        
#类的名字
	1.见名知意
    2.大驼峰(单词的首字母大写)
        
#创建类
	#一个类,使用类名+()可以创建多个不同的对象
class Student:
    pass

#创建对象(类名加括号)
print(Student)		#<class类 '__main__.Student'>
res = Student()
print(res)  		#<__main__.Student object对象 at 0x000001CBB06AFA90>
print(res.__class__.__name__)   #Student
print(res.__dict__)				#{}
        
#类中的属性可以被任何一个对象访问,所以类中存放的应该是对象的公共属性  
class Person:
    name = '张三丰'
    age = 2000
    sex = 'male'
res = Person()
print(res)
print(res.name)
print(res.age)
print(res.sex)

<__main__.Person object at 0x00000138EAA9A940>
张三丰
2000
male

#可以为对象单独设置属性	
	#属性的查找顺序为,先查找对象,再查找类
class Person:
    eat = '会吃饭'
    run = '会跑路'
zhangsan = Person()
zhangsan.eat = '不会吃饭'
lisi = Person()
lisi.run = '不会跑路'
print(zhangsan.eat)
print(zhangsan.run)
print(lisi.eat)
print(lisi.run)

不会吃饭
会跑路
会吃饭
不会跑路

#对象的精髓就在于将数据和处理数据的函数整合到一起,这样以来,当拿到一个对象,就同时拿到处理的数据和处理数据的函数
#在类和对象中,属性(数据)用变量表示,方法用函数表示
	#属性、方法也可以统一称为属性,分为数据属性、函数属性
    
class Student:
    school = '清华'
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def study(self):
        print('hello i am a student, my name is:%s'%self.school)

t1 = Student('syy',18)
print(type(t1.study))
print(type(Student.study))

<class 'method'>		#方法
<class 'function'>		#函数

属性的增删改查

#类
#增加类的属性
	新建一个类(覆盖)
    
#对象
#增加对象的属性
对象变量名.属性名称 = 属性值

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

#修改对象的属性
对象变量名.属性名称 = 新的属性值

#查看对象的属性(专属属性)
	#无论是查看类的属性还是查找对象的属性,都是使用__dict__
    #查看对象的类,使用__class__
class Person:
    eat = '会吃饭'
    run = '会跑路'
print(Person.__dict__)		#类的属性
res = Person()		
print(res)					#没有专属属性的对象
print(res.__dict__)
print(res.__class__)		#对象的类信息
zhangsan = Person()
zhangsan.eat = '不会吃饭'
print(zhangsan.__dict__)	#有专属属性的对象
print(zhangsan.__class__)		#对象的类信息

{'__module__模块名称': '__main__', 'eat': '会吃饭', 'run': '会跑路', '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__注释': None}
<__main__.Person object at 0x000001C11D14A9E8>
{}
<class '__main__.Person'>
{'eat': '不会吃饭'}
<class '__main__.Person'>

初始化对象的属性

#使用类产生对象1
class Teacher:
    school = '清华'

t1 = Teacher()
t1.name = 'tom'
t1.age = 19

t2 = Teacher()
t2.name = 'jerry'
t2.age = 20

t3 = Teacher()
t3.name = 'syy'
t3.age = 21

print(t1.name)
print(t2.name)
print(t3.name)

#使用类产生对象2,使用函数封装重复代码
class Teacher:
    school = '清华'

def init(obj,name,age):
    obj.name = name
    obj.age = age

t1 = Teacher()
t2 = Teacher()
t3 = Teacher()

init(t1,'tom',19)
init(t2,'jerry',19)
init(t3,'syy',19)

print(t1.name)
print(t2.name)
print(t3.name)
#使用类产生对象3,使用__init__方法
	#self就是生成的对应的对象
    #使用__init__定义的变量,就是对象的专属属性(__init__的作用就是给对象附初始值)
    #函数名必须是__init__,__init__函数不能使用return返回值
    #调用函数的时候,注意变量的个数要对应类体内的变量的个数(少一个),原因在于,调用函数的时候会将对象本身作为第一个参数
    #当需求是在创建对象时还需要做掉别的事,那就应该想到对象的初始化

class Teacher:
    school = '清华'
    def __init__(self):
        print(self)
t1 = Teacher()
print(t1)

<__main__.Teacher object at 0x000002C04478F908>
<__main__.Teacher object at 0x000002C04478F908>

class Teacher:
    school = '清华'
    def __init__(self,name,age):
        self.name = name
        self.age = age
t1 = Teacher('tom',19)
t2 = Teacher('jerry',20)
t3 = Teacher('syy',21)
print(t1,t2,t3)
print(t1.name,t2.name,t3.name)

<__main__.Teacher object at 0x0000022248F15A90> <__main__.Teacher object at 0x0000022248F15BA8> <__main__.Teacher object at 0x0000022248F159B0>
tom jerry syy
#使用locals()初始化对象
	#locals(),是一个字典,表示当前名称空间中所有的变量名

class Hero:
    def __init__(self,name,level,blood,attach,q_hurt,w_hurt,e_hurt):
        # self.name = name
        # self.level = level
        # self.blood = blood
        # self.attach = attach
        # self.q_hurt = q_hurt
        # self.w_hurt = w_hurt
        # self.e_hurt = e_hurt
        lcs = locals()
        lcs.pop('self')
        self.__dict__.update(lcs)
s1 = Hero(1,2,3,4,5,6,7)
print(s1.__dict__)

{'e_hurt': 7, 'w_hurt': 6, 'q_hurt': 5, 'attach': 4, 'blood': 3, 'level': 2, 'name': 1}

对象的绑定方法

#对象绑定方法
	#不使用装饰器的前提下,类中的方法(函数),都是对象绑定方法
	#使用类生成对象,通过对象可以调用别的方法(函数体代码)
    
    
#一个类里面可以有多个函数,但是一个类同时只能生成一个对象
	#只要一生成对象,类中的代码就会运行,但是不会运行除了__init__别的函数体代码
    #别的函数体代码只会在调用的时候运行
    
class Student:
    school = '清华'
    def __init__(self,name,age):
        self.name = name
        self.age = age
        print(self)
    def study(self):
        print('hello i am a student, my name is:%s'%self.name)

t1 = Student('syy',18)
t2 = Student('tom',19)
print(t1.study())		#不需要传参,自动将该对象当做第一个参数传入
print(t2.study())

<__main__.Student object at 0x000002842EECAA20>
<__main__.Student object at 0x000002842EEDE978>
hello i am a student, my name is:syy
None
hello i am a student, my name is:tom
None

#使用对象调用方法和使用类调用方法的行为不同
	#使用对象调用方法,有几个参数就传几个参数
    #使用类名调用方法,该方法就是一个普通函数
class Student:
    school = '清华'
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def study(self):
        print('hello i am a student, my name is:%s'%self.name)

t1 = Student('syy',18)
t1.study()						#使用对象调用方法
Student.study(t1)				#使用类名调用方法

hello i am a student, my name is:syy
hello i am a student, my name is:syy
    
#练习
	#写一个学生类,具备一个打招呼的技能,要能输出自己的名字信息
class Student:
    def __init__(self,name):
        self.name = name
    def say_hi(self):
        print('my name is %s'%self.name)
s1 = Student('syy')
s1.say_hi()

my name is syy

类的绑定方法

#
class Student:
    school = '清华'
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def study(self):
        print(self.school)

t1 = Student('syy',18)
print(Student.school)		#访问类
print(t1.school)			#访问对象	
Student.study(t1)			#访问类中的函数(需要传入一个对象)
t1.study()

#类的绑定方法
	#使用装饰器@classmethod装饰
    #不管是用类还是对象调用,都会自动传入类本身,作为第一个参数
class Student:
    school = '清华'
    def __init__(self,name,age):
        self.name = name
        self.age = age
    @classmethod
    def study(cls):
        print(cls.school)

t1 = Student('syy',18)
print(Student.school)
print(t1.school)
Student.study()			#访问类中的函数(不需要传参数)
t1.study()

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

非绑定方法

#非绑定方法或叫做静态方法
	#使用装饰器@staticmethod
	#即类既不绑定对象self,也不绑定类cls
    #该方法定义函数与类体外定义函数结果相同
    
class Student:
    school = '清华'
    def __init__(self,name,age):
        self.name = name
        self.age = age
    @staticmethod
    def study():
        print('hello')

t1 = Student('syy',18)
Student.study()			#使用类访问方法
t1.study()				#使用对象访问方法

#实例
	#为学生类添加一个save方法,一个get方法
    #save是将对象存储到文件中(对象绑定方法)
    #get是从你文件中获取对象(非绑定方法)
    
import pickle
class Student:
    def __init__(self,name):
        self.name = 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
s1 = Student('syy')
s1.save()
obj = Student.get('syy')
print(obj.name) 

#当类中的函数需要使用self或者__init__中定义的变量,那么这个函数就是对象绑定
#当类中的函数需要使用类中定义的变量,那么这个函数就是类绑定方法,可以使用装饰器classmethod修饰
#当类中的函数既不需要使用类中定义的变量,又不需要使用__init__中定义的变量,那么这个对象就是非绑定方法

实例 (对象交互练习)

参考网站

import random
import time

class Hero:
    def __init__(self,name,level,blood,att,Q_hurt,W_hurt,E_hurt):
        lcs = locals()
        lcs.pop('self')
        self.__dict__.update(lcs)
    def attack(self,enemy):
        enemy.blood -= self.att
        print('%s对%s释放了普通攻击,造成了%s的伤害,敌人剩余血量%s'%(self.name,enemy.name,self.att,enemy.blood))
        if enemy.blood <= 0:
            print('%s被%s使用普通攻击击杀!'%(enemy.name,self.name))
    def Q(self,enemy):
        enemy.blood -= self.Q_hurt
        print('%s对%s释放了Q技能,造成了%s的伤害,敌人剩余血量%s'%(self.name,enemy.name,self.Q_hurt,enemy.blood))
        if enemy.blood <= 0:
            print('%s被%s使用Q技能击杀!'%(enemy.name,self.name))
    def W(self,enemy):
        enemy.blood -= self.W_hurt
        print('%s对%s释放了W技能,造成了%s的伤害,敌人剩余血量%s'%(self.name,enemy.name,self.W_hurt,enemy.blood))
        if enemy.blood <= 0:
            print("%s被%s使用W技能击杀!" % (enemy.name, self.name))
    def E(self,enemy):
        enemy.blood -= self.E_hurt
        print('%s对%s释放了E技能,造成了%s的伤害,敌人剩余血量%s'%(self.name,enemy.name,self.E_hurt,enemy.blood))
        if enemy.blood <= 0:
            print('%s被%s使用E技能击杀!'%(enemy.name,self.name))

#定义英雄
h1 = Hero('亚索',level=1,blood=50000,att=500,Q_hurt=600,W_hurt=1000,E_hurt=2000)
h2 = Hero('妲己',level=2,blood=3000,att=600,Q_hurt=700,W_hurt=1000,E_hurt=2000)
h3 = Hero('鲁班',level=3,blood=3000,att=700,Q_hurt=800,W_hurt=1500,E_hurt=2000)
h4 = Hero('蔡文姬',level=4,blood=5000,att=100,Q_hurt=200,W_hurt=300,E_hurt=400)
# h1.attack(h2)
# h2.Q(h1)
# h2.W(h1)
# h2.E(h1)

while True:
    #把所有的攻击方法装到字典,为了随机取出一个
    funcs = {1:Hero.Q,2:Hero.W,3:Hero.E,4:Hero.attack}
    func_index = random.randint(1,len(funcs))
    func = funcs[func_index]
    #把所有英雄装到字典,为了随机取出一个
    heros = {1:h1,2:h2,3:h3,4:h4}
    hero_index = random.randint(1,len(heros))
    hero = heros[hero_index]
    #剩余的英雄们
    other_heros = {}
    new_index = 1
    for k,v in heros.items():
        if k != hero_index:
            other_heros[new_index] = v
            new_index +=1
    #从剩余的英雄中随机跳出一个英雄来挨打
    enemy_index = random.randint(1,len(other_heros))
    enemy = other_heros[enemy_index]
    #打他
    func(hero,enemy)
    if enemy.blood <=0:
        break
    time.sleep(1)

面向对象(OOP)的三大特征

继承

#什么是继承
	继承是一种关系,描述两个类之间的关系
    在程序中,继承描述的是类和类之间的关系(父类、基类、子类)
    在python中,一个子类可以同时继承多个父类,使用逗号分开

#为什么要使用继承
	继承的一方可以直接使用被继承一方已经有的东西
    继承的作用就是重用已经有的代码,提高重用性
    
#如何使用继承
#语法
class 类名称(父类的名称):
    pass

#例
class Base:
    desc = '这是一个基类'
    def show_info(self):
        print(self.desc)
    def make_money(self):
        print('一天赚它一个亿')
class Subclass(Base):
    pass
    # def make_money(self):
    #     print('一天赚它一百块')
obj = Subclass()
obj.make_money()
print(obj.desc)

一天赚它一个亿
这是一个基类

抽象与继承

#原始代码
class Teacher:
    def __init__(self,name,age,gender):
        self.name = name
        self.age = age
        self.gender = gender
    def say_hi(self):
        print('name:%s,age:%s,gender:%s'%(self.name,self.age,self.gender))
class Student:
    def __init__(self,name,age,gender):
        self.name = name
        self.age = age
        self.gender = gender
    def say_hi(self):
        print('name:%s,age:%s,gender:%s'%(self.name,self.age,self.gender))

t1 = Teacher('syy','male',20)
t1.say_hi()
stu1 = Student('dog','female',2)
stu1.say_hi()

#抽象
	#生活中,抽象是指不具体、不清晰、很模糊、看不懂,形容词
    #程序中,抽象是动词
    #将多个不同的子类中相同的部分进行抽取,形成一个新的类,这个过程叫抽象
    #先抽象再继承
    #继承一个已经存在的类,可以扩展或是修改原始的功能
    
class Person:
    def __init__(self,name,age,gender):
        self.name = name
        self.age = age
        self.gender = gender
    def say_hi(self):
        print('name:%s,age:%s,gender:%s'%(self.name,self.age,self.gender))
class Teacher(Person):
    def teacher(self):
        print('老师专属...')
class Student(Person):
    pass

t1 = Teacher('syy','male',20)
t1.say_hi()
stu1 = Student('dog','female',2)
stu1.say_hi()

属性查找顺序

#属性查找顺序
	对象 > 子类 > 父类
    
class A:
    a = 1
class B(A):
    b = 2
    def __init__(self,c):
        self.c = c
class C(B):
    pass
class D(C):
    pass
t = B(3)
print(t.b)		#2
print(t.a)		#1
print(t.c)		#3
print(D.mro())	#所有父类名,按继承顺序排列,[<class '__main__.D'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]

派生与覆盖

#派生
	当一个子类中出现了与父类中不同的内容时,这个子类就称之为派生类
    通常子类都会写一些新的代码,不可能和父类完全一样,所以,子类通常都是派生类
    
#覆盖
    覆盖也称之为overwrite
    当子类出现了与父类完全一致(名字一致)的属性和方法时,父类中的属性和方法被子类覆盖
    
#派生与覆盖
class Person:
    def say_hi(self):
        print('hello person')
class Student(Person):
    def say_hi(self):
        print('hello student')
stu = Student()
print(stu.say_hi())

#例
	实现一个可以限制元素类型的列表
	#子类中访问父类的内容使用关键字super
class Mylist(list):
    def __init__(self,element_type):
        #先继承父类,后自定义子类的初始化
        super().__init__()
        #element_type: 指定的数据类型
        self.element_type = element_type
    def append(self,object):
        #object: 要存储的元素
        if type(object) == self.element_type:
            #在这里访问父类的append函数
            super(Mylist,self).append(object)
        else:
            print('sorry,you element type not is %s'%self.element_type)
m = Mylist(str)
m.append('壹')
print(m[0])

super

参考网站

#python2中的supper
class Parent:
    test = 'abc'
    def say_something(self):
        print('anything')
class Sub(Parent):
    def show_info(self):
        print(super(Sub,self).test)
        super(Sub, self).say_something()
sub = Sub()
sub.show_info()

#python3中的supper
class Parent:
    test = 'abc'
    def say_something(self):
        print('anything')
class Sub(Parent):
    def show_info(self):
        print(super().test)
        super().say_something()
sub = Sub()
sub.show_info()

#使用类名称,与继承无关
class Parent:
    test = 'abc'
    def say_something(self):
        print('anything')
class Sub(Parent):
    def show_info(self):
        print(Parent.test)
        Parent.say_something(self)
sub = Sub()
sub.show_info()

#参考
class Animal:  				# 定义一个父类
    def __init__(self):  	# 父类的初始化
        self.name = 'animal'
        self.role = 'parent'
        print('I am father')
 
class Dog(Animal):				# 定一个继承Animal的子类
    def __init__(self):  		# 子类的初始化函数,此时会覆盖父类Animal类的初始化函数
        super(Dog, self).__init__()  # 在子类进行初始化时,也想继承父类的__init__()就通过super()实现,此时会定义self.name= 'animal'等
        print('I am son')
        self.name = 'dog'	  # 定义子类的name属性,并且会把刚才的self.name= 'animal'更新为'dog'
animal = Animal()			#I am father
xbai = Dog()				#I am son(没有继承父类的print?)
print(xbai.name)			#'dog'

#使用super继承
	#子类在初始化的时候,调用父类的初始化
class Teacher:
    def __init__(self,name,age,gender):
        self.name = name
        self.age = age
        self.gender = gender
    def say_hi(self):
        print('name: %s,age: %s,gender: %s'%(self.name,self.age,self.gender))
class Student(Teacher):
    def __init__(self,name,age,gender,number):
        super(Student, self).__init__(name,age,gender)
        self.number = number
    def say_hi(self):
        super().say_hi()
        print('number: %s'%self.number)
stu = Student('syy',18,'male',2333333)
stu.say_hi()

name: syy,age: 18,gender: male
number: 2333333
    
#继承方法
class Teacher:
    def __init__(self,name,age,gender):
        self.name = name
        self.age = age
        self.gender = gender
        self.aa()
    def say_hi(self):
        print('name: %s,age: %s,gender: %s'%(self.name,self.age,self.gender))
    def aa(self):
        print('my name si aa')
class Student(Teacher):
    def __init__(self,name,age,gender,number):
        super().__init__(name,age,gender)
        self.number = number
    def say_hi(self):
        super().say_hi()
        print('number: %s'%self.number)
stu = Student('syy',18,'male',2333333)
stu.say_hi()

#当继承一个现有的类,并且覆盖了父类的__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,age,phone):
        self.name = name
        self.age = age
        self.phone = phone
    def show_info(self):
        print('name: %s,age: %s'%(self.name,self.age))
phone1 = Phone(10000,'apple','red')
stu1 = Student('syy',18,phone1)
stu1.phone.call()

菱形继承

#python支持多继承
	存在类的继承顺序问题

#一般的继承顺序
	#按Sub的书写顺序继承
class A:
    pass
class B:
    pass
class C:
    pass
class Sub(A,B,C):
    pass
print(Sub.mro())

[<class '__main__.Sub'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>]

#python3中,任何类都是直接或间接的继承Object
class A:
    pass
print(A.mro())		#[<class '__main__.A'>, <class 'object'>]

#新式类与经典类
	#新式类
    	任何显示或隐式的继承自Object的类,就叫做新式类
        python3中,全部都是新式类
    #隐式类
    	没有继承Object的类,就叫做经典类,python2中才会存在经典类
        所以类的创建一般标识继承自object,这样代码既可以兼容python2有可以兼容python3
        
#菱形继承
	当一个类有多个父类,且多个父类有共同的基类,那么这个类的继承就叫做菱形继承
    
class A:
    j = 1
    pass
class B(A):
    # j = 2
    pass
class C(A):
    j = 3
    pass
class D(B,C):
    # j =4
    pass
test = D.j
print(test)		#3
    
#菱形继承中新式类与经典类的区别
	#新式类
    	有共同父类就广度(广度没有再深度),没有共同父类就深度找
    #经典类
    	有共同父类是深度(深度没有再广度),没有共同父类是且仅是深度找
class A:
    num = 1
    pass
class B():
    num = 2
    pass
class C(A):
    num = 3
    pass
class D(A):
    num = 4
    pass
class E(B):
    # num = 5
    pass
class F(C):
    num = 6
    pass
class G(D):
    num = 7
    pass
class H(E,F,G):
    # num = 8
    pass
print(H.num)	#2
print(H.mro())	#[<class '__main__.H'>, <class '__main__.E'>, <class '__main__.B'>, <class '__main__.F'>, <class '__main__.C'>, <class '__main__.G'>, <class '__main__.D'>, <class '__main__.A'>, <class 'object'>]

继承的使用

#原始数据
	程序员,姓名、性别、年龄、工资、编程技能
    项目经理,姓名、性别、年龄、工资、编程技能、奖金、管理技能
    
#分析关系
	初始化变量较多的时候,可以使用locals
	项目经理来自程序员,可以使用super继承,没有必要使用抽象
    无论是程序员还是项目经理,都要使用存和取,单独抽取代码更加简洁,有利于代码扩展

import pickle
import os
class Baseclass:
    '''
    将存储数据的操作单独抽取,这样可以降低耦合度,减少代码冗余
    '''
    def save(self):
        #判断类名对应的文件夹是否存在
        cls_name = self.__class__.__name__
        if not os.path.exists(cls_name):
            os.makedirs(cls_name)
        path = os.path.join(cls_name,self.name)
        with open(path,'wb') as f:
            pickle.dump(self,f)
    @classmethod
    def get_obj(cls,name):
        #拼接路径
        path = os.path.join(cls.__name__,name)
        if os.path.exists(path):
            with open(path,'rb') as f:
                return pickle.load(f)

class Coder(Baseclass):
    def __init__(self,name,age,gender,salary):
        self.name = name
        self.age = age
        self.gender = gender
        self.salary = salary
    def programming(self):
        print('程序员正在开发项目...')
class Manager(Coder):
    def __init__(self,name,age,gender,salary,bonus):
        super().__init__(name,age,gender,salary)
        self.bonus = bonus
    def manage(self):
        print('经理正在殴打程序员...')
if __name__ == '__main__':
    # cod1 = Coder('syy',18,'男神',10000)
    # cod1.programming()
    # cod1.save()
    # man1 = Manager('ji',88,'糟老头子',100,1)
    # man1.programming()
    # man1.save()

    cod1 = Coder.get_obj('syy')  #type: Coder
    cod1.programming()
    man1 = Manager.get_obj('ji')    #type: Manager
    man1.manage()
    
程序员正在开发项目...
经理正在殴打程序员...

封装

#什么是封装
	封装就是将复杂的细节隐藏到内部,对外提供简单的使用接口
    
#为什么要使用封装
	为了保证关键数据的安全性
    对外隐藏细节,隔离复杂度
    
#什么时候使用封装
	当有一些数据不希望外界直接修改
    当有一些数据不希望外界使用时
    
#怎么使用封装
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('111','syy',18)
p.id_number = '222'
p.show_id()				#222
print(p.id_number)		#222

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('111','syy',18)
p.__id_number = '222'
p.show_id()				#111
print(p.__id_number)	#222

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('111','syy',18)
p.id_number = '222'
p.show_id()				#111,此时外界无法修改类体(可以使用_Person__id_number修改)
print(p.id_number)		#222


#例
class PC:
    def __init__(self,price,kind,color):
        self.price = price
        self.kind = kind
        self.color = color
    def open(self):
        print('接通电源')
        self.__check_device()
        print('载入内核')
        print('初始化内核')
        self.__start_service()
        print('启动GUI')
        self.__login()
    def __check_device(self):
        print('硬件检测1.')
        print('硬件检测2..')
        print('硬件检测3...')
    def __start_service(self):
        print('启动服务1.')
        print('启动服务2..')
        print('启动服务3...')
    def __login(self):
        print('登录1.')
        print('登录2..')
        print('登录3...')
p = PC(10000,'苹果','白色')
p.open()
# p.login(),外界无法修改类体,无法使用类体内部的数据

#被封装的函数的特点
	1.外界不能直接访问
    2.内部依然可以使用函数名调用
    
#学习了封装之后就可以控制属性的权限
	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('当前缓冲大小为: %s'%self.__buffer_size)
            print('开始下载...')
        else:
            print('内存炸了!!!')
    #定义接口函数
    def set_buffer_size(self,size):
        #可以在接口函数中添加额外的逻辑
        if type(size) == int:
            print('缓冲大小修改成功,大小为: %s'%size)
            self.__buffer_size = size
        else:
            print('缓冲大小必须是整型!!!')
    # 通过函数访问内部封装的属性
    def get__buffer_size(self):
        return self.__buffer_size
#生成对象
d = Downloader('奥特曼全集','www.aoteman.com',1024*1024)
#下载
d.start_download()
#修改函数内部封装的属性
d.set_buffer_size(1024*512)
#继续下载
d.start_download() 

property、key.setter、key.deleter装饰器

#装饰器是用在函数上的
#property装饰器
	该装饰器为了解决封装的属性和方法的'访问'方法的不同
    使用了该装饰器,类中隐藏方法的'访问',直接使用点,不再使用括号
    
#key.setter装饰器
	该装饰器为了解决封装前后的属性'修改’的不同
    使用了该装饰器,类中隐藏属性的修改,直接使用点,不再使用括号
    
#key.deleter装饰器
	该装饰器为了解决不能删除封装的属性的问题,类似于触发器
    使用了该装饰器,类中隐藏属性的删除,可以直接删除,也可以设置触发条件
    
#原始代码
class A:
    def __init__(self,name,key):
        self.name = name
        self.__key = key
    def get_key(self):
        return self.__key
    def set_key(self,new_key):
        self.__key = new_key
a = A('syy',123)
print(a.name)			#访问属性不加括号
print(a.get_key())		#访问方法加括号

#使用了property后的代码
class A:
    def __init__(self,name,key):
        self.name = name
        self.__key = key
    @property
    def get_key(self):
        return self.__key
    def set_key(self,new_key):
        self.__key = new_key
a = A('syy',123)
print(a.name)			#访问属性不加括号
print(a.get_key)		#访问方法不再需要加括号,隐藏属性与普通属性访问方式相同
a.set_key(456)			#属性的修改需要加括号
print(a.get_key)

#使用property、key.setter、key.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):
        self.__key = new_key
    @key.deleter
    def key(self):
        print('不允许删除该属性...')
        #del self.__key		#可以在这里删除隐藏属性
a = A('syy',123)
print(a.key)
a.key = 456			#属性的修改不需要再使用括号,直接点,隐藏属性与普通属性修改方式相同
print(a.key)
del a.key			#隐藏属性不能直接删除,可以使用装饰器删除,隐藏属性与普通属性删除方式相同
print(a.key)

123
456
不允许删除该属性...
456

#注意
	key是property装饰器装饰的方法的名字,在使用setter和deleter时,装饰器的函数名要保持一致

封装的原理

#封装的原理
	#python中,类中的属性的封装非常简单,仅仅是把类体中的属性的'__变量名'改为'_类名__变量名',这样外界将不能直接使用变量名或者__变量名访问隐藏属性(但是外界可以使用_类名__变量名访问类的隐藏属性)
    #字符串的修改,对于python解释器的是很简单的
    #这种字符串的修改只会在加载类的时候,在类体中运行(属性隐藏和访问的时候,'添加都会运行')

class A:
    def __init__(self,key):
        self.__key = key
    @property
    def key(self):
        return self.__key
    @key.deleter
    def key(self):
        del self.__key
a = A(123)
print(a.key)
del a.key
print(a.key)	#AttributeError: 'A' object has no attribute '_A__key'

#
class A:
    def __init__(self,key):
        self.__key = key
    @property
    def key(self):
        return self.__key
    @key.deleter
    def key(self):
        del self.__key
a = A(123)
print(a.key)			#123
print(a.__dict__)		#{'_A__key': 123}
print(a._A__key)		#123

计算属性

#使用property装饰器,可以用来实现计算属性
	#计算属性指的是:属性的值不能直接获取,必须通过计算才能获取
    #这里的property装饰器,仅仅只是为了让方法的调用不加括号,与属性的调用相同而已
    
#例
	求正方形的面积
class Square:
    def __init__(self,width):
        self.width = width
    @property
    def area(self):
        return self.width * self.width
s = Square(10)
print(s.area)		#100
s.width = 20
print(s.area) 		#400

#例2
	求BMI,BMI = 体重/身高的平方
class Bmi:
    def __init__(self,height,weight):
        self.height = height
        self.weight = weight
    @property
    def bmi(self):
        return self.weight/self.height/self.height
b = Bmi(170,165)
print(b.bmi)		#0.005709342560553633

接口

#什么是接口
	接口是一组功能的集合,但是接口中只包含功能的名字,不包含具体的实现代码
    接口本质上是一套协议标准,遵循这个标准的对象就能被调用
    
#接口的作用
	为了提高代码的扩展性
    
#例
	例如电脑上的USB接口协议,只要你遵循该协议,相关的设备就能被电脑使用,不需要关心设备的种类
    #PC的代码一旦完成,后期无论什么样的设备,只要遵循了USB接口协议,都能被电脑调用
    #接口主要是方便了对象的使用者,降低了使用者的学习难度,因为使用者只要学习了一套使用方法,就可以操作各类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('鼠标不支持写入')
class Keyboard(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()
k = Keyboard()
pc(m)
pc(k)
    
鼠标开机
获取光标位置
鼠标不支持写入
鼠标关机
键盘开机
获取键盘字符
键盘写入灯光颜色
键盘关机
    
#问题
	如果子类没有按照你的协议来进行代码的编写,将导致代码无法运行

抽象类

#abc模块,是单词absract class的缩写,意为'抽象类' 
#抽象类
	一个类中没有包含函数体(或者经过装饰器装饰的函数体),那么这个类就是抽象类
    #作用
    	可以限制子类必须遵循父类中定义的抽象方法

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('鼠标不支持写入')

class Keyboard(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()
k = Keyboard()
pc(m)
pc(k)

鸭子类型

#python一般不会限制你必须怎么写,作为一个优秀的程序员,就应该遵守相关协议,所以有了鸭子类型的说法
#鸭子类型
	如果一个对象长得像鸭子,走路像鸭子,那他就是鸭子
    #由此类推,只要保证你的类,按照相关的协议编写,就可以达到提高扩展性的目的
    
class Mouse:
    def open(self):
        print('鼠标开机')
    def close(self):
        print('鼠标关机')
    def read(self):
        print('获取光标位置')
    def write(self):
        print('鼠标不支持写入')
class Keyboard:
    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()
k = Keyboard()
pc(m)
pc(k) 

多态性

#多态
	生活中,一种事物有多种不同的形态
	官网:多个对象可以响应同一个方法,产生不同的结果
    多态不是一种特殊的语法,而是一种状态、特性
    接口、抽象类、鸭子类型,都可以写出具备多态的代码,其中最简单的就是鸭子类型
    #优点
    	对于使用者而言,大大的降低了使用难度,例如USB接口程序就有多态性

#例1
class Ji:
    def bark(self):
        print('咯咯咯...')
    def spawn(self):
        print('下鸡蛋')
class Ya:
    def bark(self):
        print('嘎嘎嘎...')
    def spawn(self):
        print('下鸭蛋')
class E:
    def bark(self):
        print('eee...')
    def spawn(self):
        print('下鹅蛋')
j = Ji()
y = Ya()
e = E()
	#多个对象使用相同的方法,可以得到不同的结果
def manage(obj):
    obj.spawn()
manage(j)
manage(y)
manage(e)
    
下鸡蛋
下鸭蛋
下鹅蛋
    
#例2
a = 10
b = '10'
c = [10]
print(type(a))		#多个变量名使用相同的方法,得到不同的结果
print(type(b))
print(type(c))      

OOP相关的内置函数

#该内置函数无论在不在类中,都能执行
#代码1
def add_num(a,b):
    if type(a) == int and type(b) == int:
        return a+b
    return None
print(add_num(1,2))
print(add_num(1,'2'))

#isinstance()
	使用isinstance()函数,判断数据类型
    isinstance(对象,数据类型)
def add_num(a,b):
    if isinstance(a,int) and isinstance(b,int):
        return a+b
    return None
print(add_num(1,2))
print(add_num(1,'2'))

#issubclass()
	使用issubclass()函数,可以判断一个类是否为某个类的子类/子孙类
    issubclass(子类,父类)
class Animal:
    def eat(self):
        print('动物要吃东西...')
class Pig(Animal):
    def eat(self):
        print('猪要吃猪食')
class Tree:
    def light(self):
        print('植物进行光和作用')
def manage(obj):
    if issubclass(type(obj),Animal):
        obj.eat()
    else:
        print('不是动物!')
p = Pig()
t = Tree()
manage(p)
manage(t)

类中的魔法函数

#__str__
	只要是双下,就会在某个时候自动执行
    __str__会在对象被转换为字符串时执行,转换的结果就是这个函数的返回值

#打印对象,得到内存地址
class Person:
    pass
p = Person()
print(p)

<__main__.Person object at 0x0000028E1471FA20>

#打印对象,得到返回值
class Person:
    def __str__(self):
        return 'abc'
p = Person()
print(p) 		#abc
    
#python解释器中的内置变量、内置函数、内置类,在程序运行结束会自动清理,但是python解释器不会清理不属于解释器的资源,例如打开的文件等,这个时候就需要手动close关闭代码
	#__del__,吸构函数,删除对象/类,得到返回值 
	#__del__函数只会执行一次
    #执行时机:
    	手动删除对象时执行
        程序运行结束,就会执行类中的__del__函数
    #使用场景
    	当你的对象在使用过程中,打开了不属于python解释器的资源,例如文件、网络端口,在程序运行结束之后,就需要使用__del__函数来完成资源的释放工作
        
import time
class Person:
    def __del__(self):
        print('del run')
p = Person()
print(p)
# del p
time.sleep(2)
print('end')
    
<__main__.Person object at 0x0000025385DEF400>
end
del run
    
class Filetool:
    '''该类用于文件的读取操作'''
    def __init__(self,path):
        self.file = open(path,'rt',encoding='utf-8')
    def read(self):
        return self.file.read()
    #在这里可以确定生成的对象不再使用了,所以可以放心的关闭文件了
    def __del__(self):
        tool.file.close()
tool = Filetool(r'E:\python_test\a.txt')
print(tool.read())		#a.txt 
    
#__init__,初始化函数、构造函数
class Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age
p = Person('syy',18)
print(p.name)
    
#__call__函数,在调用对象的时候执行(对象加括号)
class A:
    def __call__(self, *args, **kwargs):
        print('call run')
        print(args)
        print(kwargs)
a = A()
a(1,'壹',z=233)
    
#python是一门动态的语言,可以在代码运行期间动态的修改对象的属性所占用的空间
	#如果代码运行内存不足,那么python解释器就会开启更大的内存区域,将原始的属性迁移过去
    #这里存在一个问题,如果python解释器开启内存太大,那么将会造成内存的浪费
    #解决方法是,在创建对象时,告诉python解释器要开启多大的内存,而不是让python解释器被动的开启内存
	#__slots__函数,该属性是一个类属性,用于优化对象内存,将原本不固定的属性数量变得固定,这样python解释器就不会为这个对象创建名称空间,所以也没了__dict__方法
    	#使用了该函数,对象将不能再添加、删除属性

#代码1
import sys
class Person:
    def __init__(self,name):
        self.name = name
p = Person('syy')
print(sys.getsizeof(p))		#56

#代码2
import sys
class Person:
    __slots__ = ['name']
    def __init__(self,name):
        self.name = name
p = Person('syy')
print(sys.getsizeof(p))		#48
# p.age = 18				#AttributeError: 'Person' object has no attribute 'age'
# print(p.__dict__)			#AttributeError: 'Person' object has no attribute 'age'

属性的getattr、setattr、delattr函数

#这几个函数体现了python解释器如何使用点来设置、访问、删除属性的
#使用点设置属性的时候,执行__setattr__函数
#使用点访问属性(属性不存在)的时候,执行___getattr__函数

class A:
    def __setattr__(self, key, value):
        print('__setattr__')
    def __getattr__(self, item):
        print('__getattr__')
    def __delattr__(self, item):
        print('__delattr__')
a = A()
a.name = 'syy'
print(a.name)

__setattr__
__getattr__
None

class A:
    def __setattr__(self, key, value):
        super().__setattr__(key,value)
        print('__setattr__')
    def __getattr__(self, item):
        print('__getattr__')
    def __delattr__(self, item):
        print('__delattr__')
a = A()
a.name = 'syy'			#通过点语法操作对象的属性(原理是使用__dict__)
print(a.name)			#syy,变量设置成功,所以可以打印出syy

__setattr__
syy

a = A()
a.__dict__['name'] = 'syy'		#通过__dict__设置属性
print(a.name)					#syy

class A:
    def __setattr__(self, key, value):
        self.__dict__[key] = value
        print('__setattr__')
    def __getattr__(self, item):
        print('__getattr__')
    def __delattr__(self, item):
        print('__delattr__')
a = A()
print(a.xxx)					#__getattr__

#删除属性的时候,执行__delattr__函数
class A:
    def __setattr__(self, key, value):
        self.__dict__[key] = value
        print('__setattr__')
    def __getattr__(self, item):
        print('__getattr__')
    def __delattr__(self, item):
        print('__delattr__')
a = A()
a.name = 'syy'
del a.name
print(a.name)		#删除不掉,原因是因为__delattr__对应的函数没有删除的操作

__setattr__
__delattr__
syy

class A:
    def __setattr__(self, key, value):
        self.__dict__[key] = value
        print('__setattr__')
    def __getattr__(self, item):
        print('__getattr__')
    def __delattr__(self, item):
        self.__dict__.pop(item)
        print('__delattr__')
a = A()
a.name = 'syy'
del a.name
print(a.name)		#删除成功

__setattr__
__delattr__
__getattr__
None

#访问属性的时候,无论有没有该属性都会执行__getattribute__函数,
class A:
    def __getattr__(self, item):
        print('__getattr__')
    def __getattribute__(self, item):
        print('__getattribute__')
a = A()
a.name = 'syy'
print(a.name)
print(a.xxx)

__getattribute__
None
__getattribute__
None

#__dict__方法的原理就是使用了__getattribute__函数
class A:
    def __getattr__(self, item):
        print('__getattr__')
    def __getattribute__(self, item):
        print('__getattribute__')
        return self.__dict__[item]
a = A()
a.name = 'syy'
print(a.name)
print(a.xxx)		#RecursionError: maximum recursion depth

#实际上python内部是先使用__getattribute__取值,如果取不到,再使用__getattr__
class A:
    def __getattr__(self, item):
        print('__getattr__')
    def __getattribute__(self, item):
        print('__getattribute__')
        return super(A, self).__getattribute__(item)
a = A()
a.name = 'syy'
print(a.name)
print(a.xxx)

__getattribute__
syy
__getattribute__
__getattr__
None

属性的setitem、getitem、delitem

#这几个函数体现了python解释器如何使用[]来设置、访问、删除属性的
#设置属性的时候执行__setitem__
class A:
    def __setitem__(self, key, value):
        print('__setitem__')
    def __getitem__(self, item):
        print('__getitem__')
    def __delitem__(self, key):
        print('__delitem__')
a = A()
a['name'] = 'syy'		#__setitem__
print(a.name)			#AttributeError: 属性不存在

#访问属性的时候执行__getitem__
class A:
    def __setitem__(self, key, value):
        print('__setitem__')
        self.__dict__[key] = value
    def __getitem__(self, item):
        print('__getitem__')
        return self.__dict__[item]
    def __delitem__(self, key):
        print('__delitem__')
        del self.__dict__[key]
a = A()
a['name'] = 'syy'
print(a['name'])

__setitem__
__getitem__
syy

#删除属性的时候执行__delitem__
class A:
    def __setitem__(self, key, value):
        print('__setitem__')
        self.__dict__[key] = value
    def __getitem__(self, item):
        print('__getitem__')
        return self.__dict__[item]
    def __delitem__(self, key):
        print('__delitem__')
        del self.__dict__[key]
a = A()
a['name'] = 'syy'
print(a['name'])
del a['name']
print(a['name'])		#KeyError: 'name'

__setitem__
__getitem__
syy
__delitem__
__getitem__

#例
	如何让一个对象既支持点语法取值,也支持括号取值
    #对象本身就支持括号[]设置、访问、删除值,所以只需要添加点语法即可

class Mydict(dict):
    def __setattr__(self, key, value):
        self[key] = value
    def __getattr__(self, key):
        return self.get(key)
    def __delattr__(self, item):
        del self[item]
m = Mydict()
m['name'] = 'syy'
print(m['name'])		#syy
del m['name']
# print(m['name'])
m.name = 'zyy'
print(m.name)			#zyy
del m.name
# print(m.name)

运算符重载

#当我们在使用某个符号时,python会为这个符号定义一个含义,同时调用对应的处理函数,当我们需要自定义对象的比较规则时,就可以在子类中覆盖 >、<、=、!=等方法
	#self和other指的是两个对象
    #类似于大于、小于,我们只需要实现一个即可,如果符号不同,python解释器会自动交换两个对象的位置

class Student:
    def __init__(self,name,age,height):
        self.name = name
        self.age = age
        self.height = height
    def __gt__(self, other):
        if self.height > other.height:
            return True
        else:
            return False
    def __lt__(self, other):
        if self.height < other.height:
            return True
        else:
            return False
    def __eq__(self, other):
        if self.name == other.name and self.age == other.age and self.height == other.height:
            return True
        else:
            return False
    def __ne__(self, other):
        if self.name == other.name or self.age == other.age or self.height == other.height:
            return True
        else:
            return False
s1 = Student('syy',18,180)
s2 = Student('zyy',80,140)
print(s1 > s2)
print(s1 < s2)
print(s1 == s2)
print(s1 != s2)

True
False
False
True

#代码简写
class Student:
    def __init__(self,name,age,height):
        self.name = name
        self.age = age
        self.height = height
    def __gt__(self, other):
        return self.height > other.height
    def __lt__(self, other):
        return self.height < other.height
    def __eq__(self, other):
        return self.name == other.name and self.age == other.age and self.height == other.height
    def __ne__(self, other):
        return self.name != other.name or self.age != other.age or self.height != other.height
s1 = Student('syy',18,180)
s2 = Student('zyy',80,140)
print(s1 > s2)
print(s1 < s2)
print(s1 == s2)
print(s1 != s2)

True
False
False
True

迭代器协议

#迭代器的判定方法
	1.内置有__iter__方法
	2.内置有__next__方法

#迭代器的作用
	迭代器就是一种取值的工具
    节省空间

#迭代器原理
class Myiter:
    def __init__(self,num):
        self.num = num
        self.count = 0
    def __iter__(self):
        return self
    def __next__(self):
        self.count += 1
        if self.count <= self.num:
            return '哈哈'
        else:
            raise StopIteration
for i in Myiter(10):
    print(i)

#生成器
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)

上下文管理

#上下文comtext
	指的是一段话的意义,要参考当前的场景,即参考上下文
	在python中,上下文可以理解为是一个代码区间、一个范围,例如with open,打开的文件尽在这个上下文中有效
	涉及到的两个方法:
		enter:
            表示进入上下文,进入某个场景
		exit:
            表示退出上下文,退出某个场景
        #使用open类的with,先执行__enter__函数,open内代码运行结束,最后执行__exit__函数
        
class Myopen:
    def __enter__(self):
        print('__enter__')
    def __exit__(self, exc_type, exc_val, exc_tb):
        print('__exit__')
        print(exc_type, exc_val, exc_tb)
with Myopen() as m:
    print('start')
    # '1' + 1			#open内代码执行的过程中,如果中途代码异常,则之后的代码不再执行,但是立即__exit__函数仍会执行,包含错误的类型、错误的信息、错误的追踪信息
    print('over')

__enter__
start
over
__exit__
None None None

class Myopen:
    def __init__(self,path):
        self.path = path
    def __enter__(self):
        self.file = open(self.path)
        return self
    def __exit__(self,exc_type, exc_val, exc_tb):
        self.file.close()
        return False        #通过返回的状态码判断代码运行的过程中,有诶有错误或者错误有没有被处理
with Myopen('a.txt') as m:
    print(m.file.read())

反射

#反射reflect
#什么是反射
	其实reflect的意思是反省、自省的意思
    反射指的是一个对象应该具备可以检测、修改、增加自身属性的能力
    反射就是通过字符串操作属性
    反射涉及到的四个内置函数(python解释器自带的),hasattr、getattr、setattr、delattr
    这几个函数都是针对于对象的,对应于对象属性是否存在、获取、设置、删除
    这几个函数实际上就是封装了__dict__等方法而已
    
#hasattr()函数
	#判断一个属性属不属于某个对象 
class Person:
    def __init__(self,name,age,gender):
        self.name = name
        self.age = age
        self.gender = gender
p = Person('syy',18,'male')	
print(hasattr(p,'name'))		#True
print(hasattr(p,'names'))   	#False
    
#getattr()函数
	#获取对象的属性,可以设置默认返回值(属性不存在的时候返回)
class Person:
    def __init__(self,name,age,gender):
        self.name = name
        self.age = age
        self.gender = gender
p = Person('syy',18,'male')
if hasattr(p,'name'):
    print(getattr(p,'name',None))
    
#setattr()函数
	#为对象添加新的属性
class Person:
    def __init__(self,name,age,gender):
        self.name = name
        self.age = age
        self.gender = gender
p = Person('syy',18,'male')
setattr(p,'id','123')
print(p.id)			#123
    
#delattr()函数
	#删除对象的属性
class Person:
    def __init__(self,name,age,gender):
        self.name = name
        self.age = age
        self.gender = gender
p = Person('syy',18,'male')
delattr(p,'name')
print(p.name)  			#AttributeError:   

反射的使用场景

	反射实际上就是对属性的增删改查,但是如果直接使用内置的__dict__来操作的话,会显得语法繁琐,不好理解,所以python封装了4个函数(getattr、setattr、delattr、getattribute)
    封装的另一个原因就是,如果对象不是我自己写的而是另一方提供的,那我就必须判断这个对象是否具备我需要的属性和方法
    反射是框架的基石,因为框架的设计者,不可能提前知道你的对象到底是怎么设计的,所以你提供给框架的对象,必须通过判断验证之后,才能正常的使用,而判断验证就是反射要做的事情,所以说反射就是对__dict__的操作进行封装
    面向对象不是只有python才有的概念,任何高级语言都有的
    
#需求
	要实现一个用于处理用户的终端指令的小框架
    所谓的框架,就是已经实现了最基础的构架,就是所有的项目都一样的部分
    
#framwork/myframwork.py 
import framework.plugins
def run(plugin):
    while True:
        cmd = input('请输入命令>>>: ').strip()
        if cmd == 'exit':
            break
        #判断对象是否具备处理这个指令的方法
        if hasattr(plugin,cmd):
            #去除对应的方法并执行
            func = getattr(plugin,cmd)
            func()
        else:
            print('不支持该指令,请重新输入...')
    print('bay bay ...')
wincmd = framework.plugins.Wincmd()
linuxcmd = framework.plugins.Linuxcmd()
run(wincmd)
run(linuxcmd) 
    
#framwork/plugins.py
class Wincmd:
    def cd(self):
        print('wincmd: 切换目录')
    def delete(self):
        print('wincmd: 删除')
    def dir(self):
        print('wincmd: 查看目录')

class Linuxcmd:
    def cd(self):
        print('linuxcmd: 切换目录')
    def rm(self):
        print('linuxcmd: 删除')
    def ls(self):
        print('linuxcmd: 查看目录')
        
#思考
	上述代码写死了必须使用某个类,这是不合理的,因为框架式的设计者无法提前知道对方的类在什么地方,以及类名叫什么
    所以我们应该为框架的使用者提供一个配置文件,要求框架的使用者将类的信息写入配置文件,然后框架自己去加载需要使用的模块

动态导入

#在框架设计中,我们不可能提前知道框架用户要提供的类的相关信息,所以需要动态导入
	#动态导入需要使用importlib模块

#framework/myframwork.py
import importlib
import framework.settings
def run(plugin):
    while True:
        cmd = input('请输入命令>>>: ').strip()
        if cmd == 'exit':
            break
        #判断对象是否具备处理这个指令的方法
        if hasattr(plugin,cmd):
            #取出对应的方法并执行
            func = getattr(plugin,cmd)
            func()
        else:
            print('不支持该指令,请重新输入...')
    print('bay bay ...')
#框架根据配置文件拿到类的路径
path = framework.settings.class_path
#框架从配置文件,单独拿出模块路径和类名称
module_path,class_name = path.rsplit('.',1)
mk = importlib.import_module(module_path)
cls = getattr(mk,class_name)
obj = cls()
run(obj)

#settings.py
    #settings为框架的配置文件
    #框架之外的部分就由自定义对象来完成,框架值提供固定的格式
    #作为框架的使用者,在配置文件中,手动指定要使用的类的路径和类名
class_path = 'libs.plugins.Wincmd'

#plugins.py
class Wincmd:
    def cd(self):
        print('wincmd: 切换目录')
    def delete(self):
        print('wincmd: 删除')
    def dir(self):
        print('wincmd: 查看目录')

class Linuxcmd:
    def cd(self):
        print('linuxcmd: 切换目录')
    def rm(self):
        print('linuxcmd: 删除')
    def ls(self):
        print('linuxcmd: 查看目录')

元类 metaclass

元类type与object的关系

#在python中,万物皆对象,当然类也是对象
	#对象是由类实例化产生的,所以类必然也是由另一个类实例化产生的
    
#什么是元类
	元类就是产生类的类
    所有类的元类默认都是type,type都是小写字母
    type是object的类型,同时,object又是type的超类
    	元类type -> object -> 类
    	元类type -> 类  
        class type(object):
            pass

class Person:
    pass
p = Person()
print(type(p))
print(type(Person))
print(type(type))
print(type(object))

<class '__main__.Person'>		对象p由Person类产生
<class 'type'>					对象Person由type类产生
<class 'type'>
<class 'type'>

class type1(object):
    pass
t = type1()
print(type(t))
print(type(type1))

<class '__main__.type1'>
<class 'type'>		#所有类的元类默认都是type

#一个类的三个基本组成
	1.类的名字,字符串类型
    2.类的父类们,是一个元组或列表
	3.类的的名称空间,字典类型
    #type类创建类与使用关键字class创建类等效(使用元类创建类的两种方式)
class type(object):
    def __init__(cls, what, bases=None, dict=None):
        pass
    
cls_obj = type('test', (), {})
print(cls_obj)
class test:
    pass
print(test)
class test(metaclass=type):
    pass
print(test)
    
<class '__main__.test'>		#cls_obj既是类又是对象
<class '__main__.test'>
<class '__main__.test'>
    
#为什么要使用元类
    高度'自定义'一个类,例如控制类的名字必须以大驼峰的方式书写
    只要是直接继承了type类,那么这个类就变成了一个元类,再利用覆盖可以做出'指定的元类',从而'限制类'
    因为不能修改python的源码,所以在使用元类的时候要自定义一个元类
    
#需求
	#如何用代码实现类名必须以大驼峰书写
#思考
	类也是对象,可以由元类创建
    类创建对象的时候执行类中的__init__方法,元类创建类的时候,执行元类中的__init__方法
    创建类的时候还有别的操作,想到__init__方法
    所以,只需要找到创建类的类(元类),再覆盖其中的__init__方法,就可以达到需求
#定义一个元类
class Mytype(type):
    def __init__(self,class_name,bases,dict):
        super().__init__(class_name,bases,dict)
        if not class_name.istitle():
            raise Exception('请书写大驼峰!')
#为pig类指定元类为Mytype
#方式1
# class Pig(metaclass=Mytype):
#     pass
#方式2
Mytype('Pig',(),{})

call方法的应用

#类实例化产生对象(类加括号)的时候,执行类中的__init__方法
#对象加括号执行的时候,调用类中的__call__方法
class Dog(type):
    def __call__(self,*args,**kwargs):
        print('call run')
d = Dog()	#类也是对象,1.执行类中的__init__2.执行元类的__call__方法
d()			#call run,1.执行类中的__call__

#类与元类中__init__,__call__的执行关系
class Mymete(type):
    def __init__(self,name,bases,dict):
        super().__init__(name,bases,dict)   #先覆盖,后导入
        print('元类init run')
    def __call__(self,*args,**kwargs):     #self为元类生成的类,*args为传入的位置参数,**kwargs为传入的关键字参数
        print('元类call run')
        # print(*args)
        # print(**kwargs)
        return super().__call__(*args,**kwargs)   #必须使用return,作用是创建对象,再执行子类的初始化
class Dog(metaclass=Mymete):        #此时执行元类的__init__
    def __init__(self,name):        #元类__call__有返回值super().__call__(*args,**kwargs),就会执行
        self.name = name
        print('init run')
    def __call__(self,*args,**kwargs):      #生成的对象家括号执行的时候执行
        print('call run')
d = Dog('syy')                      #此时执行元类的__call__,实例化产生对象就是执行元类中的__call__
print(d)
d()

元类init run
元类call run
init run
<__main__.Dog object at 0x00000212E9A4A908>
call run

#元类中的__call__方法
	当你调用类对象时,会自动执行元类中的__call__方法,并将这个类本身作为第一个参数传入,以及后面的一堆参数
	覆盖元类中的__call__之后,这个类就无法产生对象,必须调用super().__call__来完成对象的创建,并将其作为返回值
    当想要控制对象的创建过程,就覆盖父类(type)的__call__方法
    当想要控制类的创建过程,就覆盖父类(type)的__init__方法

#想要把对象的所有属性变成大写
class Mymete(type):
    def __call__(self, *args, **kwargs):
        new_args = []
        for i in args:
            new_args.append(i.upper())
        return super().__call__(*new_args,**kwargs)
class Person(metaclass=Mymete):
    def __init__(self,name):
        self.name = name
p = Person('syy')
print(p.name)		#SYY

#要求创建对象时,必须以关键字参数形式来传参
	#注意一旦覆盖了父类(type)的__call__,就必须调用返回父类(type)的__call__
class Mymete(type):
    def __call__(self, *args, **kwargs):
        if args:
            raise Exception('不好意思,不允许使用位置参数')
    #     return super().__call__(*args,**kwargs)		#1.创建对象,2.子类初始化
        obj = object.__new__(self)
        self.__init__(obj,*args,**kwargs)
        return obj
class Person(metaclass=Mymete):
    def __init__(self,name):
        self.name = name
p = Person(name='syy')
print(p.name)		#syy

#补充
	当你创建类对象时,会首先执行元类中的__new__方法,拿到一个空对象,然后自动调用元类__init__,然后对这个类进行初始化操作
    如果覆盖了该方法,则必须保证__new__方法必须有返回值且必须是类对象
    __new__方法和__init__方法,都可以实现控制类的创建过程,init更简单
    
class Mymete(type):
    def __new__(cls, *args, **kwargs):
        print(cls)      #元类自己
        print(args)     #创建类需要的几个参数,类名、基类、名称空间
        print(kwargs)   #空
        # return super().__call__(cls, *args, **kwargs)
        obj = type.__new__(cls,*args,**kwargs)
        return obj
    def __init__(self,a,b,c):
        super().__init__(a,b,c)
class A(metaclass=Mymete):
    pass
m = A()
print(m)

<class '__main__.Mymete'>
('A', (), {'__module__': '__main__', '__qualname__': 'A'})
{}
<__main__.A object at 0x000001F53C5AA940>
#习题
	定义一个元类,功能是获取类中所有的属性和方法,将名字存放到一个列表中,再将列表作为类的属性,使得类可以调用attrs,来获取自己的所有内容名称
    
class Mymeta(type):
    def __init__(self,name,bases,dict):
        super().__init__(name,bases,dict)
        attrs = [i for i in list(dict.keys()) if not i.startswith('__')]
        self.attrs = attrs
class A(metaclass=Mymeta):
    name = 'syy'
    def info(self):
        print('info')
    def say(self):
        print('say')
print(A.attrs)    		#['name', 'info', 'say']

单例设计模式

#模式
	固定的格式
    
#设计模式
	用于解决某种固定问题的套路
    
#单例
	一个类只产生一个对象
    为了节省空间,但一个类的所有对象的属性全部相同时,则没有必要创建多个类
    
#伪单例  
	#使用get_instance()方法,只会创建一个对象
    #但是使用'类()'仍然可以创建多个对象
class Person():
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def say_hi(self):
        print('hello %s'%self.name)
    #用于获取对象的方法
    @staticmethod
    def get_instance(name,age):
        #判断该类是否已经创建过对象
        if hasattr(Person,'obj'):
            return getattr(Person,'obj')
        obj = Person(name,age)
        Person.obj = obj
        print('new了一次')
        return obj
# p = Person()
p = Person.get_instance('syy',18)
p2 = Person.get_instance('syy',18)
p3 = Person.get_instance('syy',18)
    
    
#单例
class Mymeta(type):
    def __call__(self, *args, **kwargs):
        if hasattr(self,'obj'):
            return getattr(self,'obj')
        obj = super().__call__(*args,**kwargs)
        self.obj = obj
        print('new了一次')
        return obj
class Person(metaclass=Mymeta):
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def say_hi(self):
        print('hello %s'%self.name)
p = Person('syy',18)
p2 = Person('syy',18)
p3 = Person('syy',18)		#new了一次

冒泡排序

#冒泡排序
	从一个列表中,每次取出两个元素,比较大小并排序
    冒泡排序中,一圈中排序的次数 = 列表中元素的个数 - 1 - 再减去圈数的索引
    冒泡排序中,圈数 = 列表中元素的个数 - 1
    
#例
  #第一圈
	把列表[1,2,3,5],从大到小进行冒泡排序
  	#第一次排序,得到2的位置
    	[2,1,3,5]
    #第二次排序,得到3的位置
    	[2,3,1,5]
    #第二次排序,得到5的位置
    	[2,3,5,1]
  #第二圈
	#第一次排序,得到3的位置
    	[3,2,5,1]
    #第二次排序,得到5的位置
    	[3,5,2,1]
  #第三圈
	#第一次排序,得到5的位置
    	[5,3,2,1]

#代码实现
l = [1,2,3,5]
for i in range(len(l)-1):           #i 为圈数的索引(range出的)
    for j in range(len(l)-1-i):     #j 为循环的次数,即排序的次数(range出索引)
        if l[j] < l[j+1]:           #j 为排序的次数,range后可以作为冒泡排序的索引
            l[j],l[j+1] = l[j+1],l[j]
print(l)		#[5,3,2,1]
posted @ 2020-11-30 09:15  看萝卜在飘  阅读(136)  评论(0编辑  收藏  举报