python二十七,二十八课---(面向对象动静态方法, 封装 继承 多态 ,经典类与新式类, 类的派生, 及名称空间知识,定义类时的注意事项)

昨日内容回顾

  • 管理员相关功能

    冻结账户
    1.用户必须登录而且必须是管理员
    	给每个用户字典再添加一个用于标识是否是管理员身份的键值对
    		is_admin:false
    2.如何判断用户是否登录并且是否是管理员
    	判断用户是否登录已经在之前的功能中编写了一个装饰器
     	用户登录成功之后获取用户的身份并在第一层字典中记录
        	is_login = {
                'username':'',
                'is_admin':False
            }
    	校验是否是管理员有很多种方式
        	直接在函数体代码中加if判断
        	将装饰器变成有参的版本(normal admin)
            	通过额外参数的不同在装饰器中校验完用户登录之后是否继续校验用户是否是管理员
    3.冻结账户
    	1.获取系统中除管理员意外的用户名单
     	2.管理员选择想要冻结的账户
     	3.获取该账户字典数据 修改用于标识是否冻结的键值对 is_lock
    	4.需要在项目登录功能处 添加用户是否已被冻结的校验 
    ps:基于上述模板可以自行扩展管理员其他功能
    
  • 学习建议

    1.学习方法
    	不要眼高手低 一定要动手敲
     	前两遍可以抄 抄的过程中思考每一步的含义并滤清思路
     	之后尝试着脱稿自己敲 遇到阻碍的部分 思考一下然后看一下 继续往后
     	直到有一遍完全不用看为止
    ps:只要肯砸时间IT大部分技术肯定能学会!!!
    

今日内容概要

  • 面向对象前戏之人狗大战
  • 面向对象编程思想
  • 面向对象重要理论
  • 面向对象代码实操
  • 对象的独有数据
  • 对象的独有方法

今日内容详细

人狗大战

# 编写代码简单的实现人打狗 狗咬人的小游戏(剧情需要)
"""推导步骤1:代码定义出人和狗"""
person1 = {
    'name': 'jason',
    'age': 18,
    'gender': 'male',
    'p_type': '猛男',
    'attack_val': 8000,
    'life_val': 99999999
}
person2 = {
    'name': 'kevin',
    'age': 28,
    'gender': 'female',
    'p_type': '淑女',
    'attack_val': 1,
    'life_val': 100
}
dog1 = {
    'name': '小黑',
    'd_type': '泰迪',
    'attack_val': 100,
    'life_val': 8000
}
dog2 = {
    'name': '小白',
    'd_type': '恶霸',
    'attack_val': 2,
    'life_val': 80000
}
# ps:如果想要定义出多个人和多条狗 上述的字典需要反复编写很多次
---------------------------------------------------------------------------
"""推导步骤2:将产生人和狗的字典封装成函数并封装人和狗的攻击函数"""
def create_person(name, age, gender, p_type, attack_val, life_val):
    person_dict = {
        'name': name,
        'age': age,
        'gender': gender,
        'p_type': p_type,
        'attack_val': attack_val,
        'life_val': life_val
    }
    return person_dict
# 把字典里面的人的属性全部改成参数的形式,这样传参只要不同生成的人字典就不同了!!
-------------------------------------------
def create_dog(name, d_type, attack_val, life_val):
    dog_dict = {
        'name': name,
        'd_type': d_type,
        'attack_val': attack_val,
        'life_val': life_val
    }
    return dog_dict
-------------------------------------------
这样想产生一个人,就只要调用函数,然后往括号里面传对应的实参就行了
p1 = create_person('jason', 18, 'male', '猛男', 8000, 99999999)
p2 = create_person('kevin', 28, 'female', '淑女', 100, 800)
d1 = create_dog('小黑', '恶霸', 800, 900000)
d2 = create_dog('小白', '泰迪', 100, 800000)
print(p1, p2)  # 打印两个人的字典
print(d1, d2)  # 打印两个狗的字典
-------------------------------------------

image
.

定义出简单的人打狗的动作函数: 
def person_attack(person_dict, dog_dict):
    print(f"人:{person_dict.get('name')}准备揍狗:{dog_dict.get('name')}")
	
    dog_dict['life_val'] -= person_dict.get('attack_val')
	
    print(f"人揍了狗一拳 狗掉血:{person_dict.get('attack_val')} 狗剩余血量:{dog_dict.get('life_val')}")
	
person_attack(p1, d1)  # 运行人打狗的函数 jason打小黑
-----------------------------------------------------------------------

image

定义出简单的狗咬人的动作函数:
def dog_attack(dog_dict, person_dict):
    print(f"狗:{dog_dict.get('name')}准备咬人:{person_dict.get('name')}")
	
    person_dict['life_val'] -= dog_dict.get('attack_val')
	
    print(f"狗咬了人一口 人掉血:{dog_dict.get('attack_val')} 人剩余血量:{person_dict.get('life_val')}")
	
dog_attack(d2, p2)  # 运行狗咬人的函数 小白咬kevin
----------------------------------------------------------------------------
![image](https://img2022.cnblogs.com/blog/2985186/202211/2985186-20221103193558821-277185326.png)
.

"""推导步骤3:人和狗的攻击混乱"""
person_attack(d1, p1)
# 形参就是一个变量名,第一个位置传d1,定义阶段的形参person_dict就会和d1绑定,并传到函数代码体里面去

dog_attack(p1, d2)   # 狗的也一样,调用函数的时候,实参传反了

归根结底是,人和狗的攻击函数,人可以调人的攻击函数,也能调狗的攻击函数。
在定义的时候,就没有限制(人只能调人的攻击函数,狗只能调狗的攻击函数),所以就容易乱。

image
.
.

面向对象核心思路前戏

"""推导步骤4:
如何实现只有人只能调用的人的攻击动作
狗只能调用狗的攻击动作
>>>:数据与功能的绑定    把人和人的攻击函数,狗和狗的攻击函数绑定到一起
"""
def get_person(name, age, gender, p_type, attack_val, life_val):
    # 产生人的函数(功能)
    def person_attack(person_dict, dog_dict):
        print(f"人:{person_dict.get('name')}准备揍狗:{dog_dict.get('name')}")
        dog_dict['life_val'] -= person_dict.get('attack_val')
        print(f"人揍了狗一拳 狗掉血:{person_dict.get('attack_val')} 狗剩余血量:{dog_dict.get('life_val')}")
    # 表示人的信息(数据)
    person_dict = {
        'name': name,
        'age': age,
        'gender': gender,
        'p_type': p_type,
        'attack_val': attack_val,
        'life_val': life_val,
        'person_attack': person_attack   # 最灵性的一步,字典里面多了一组键值对
    }
    return person_dict

person1 = get_person('jason', 18, 'male', '猛男', 8000, 99999999)
这样一变形,就只有自定义的 get_person(name, age, gender, p_type, attack_val, life_val): 函数所产生的人(人的字典)才可以调用人攻击狗的函数了!!!
----------------------------------------------------------------------

def get_dog(name, d_type, attack_val, life_val):
    def dog_attack(dog_dict, person_dict):
        print(f"狗:{dog_dict.get('name')}准备咬人:{person_dict.get('name')}")
        person_dict['life_val'] -= dog_dict.get('attack_val')
        print(f"狗咬了人一口 人掉血:{dog_dict.get('attack_val')} 人剩余血量:{person_dict.get('life_val')}")
    dog_dict = {
        'name': name,
        'd_type': d_type,
        'attack_val': attack_val,
        'life_val': life_val,
        'dog_attack': dog_attack   # 这也多加了一组键值对了
    }
    return dog_dict

dog1 = get_dog('小黑', '恶霸', 800, 900000)
-------------------------------------------------------------------
person1.get('person_attack')(person1, dog1)
# 这步是最骚的,在外部通过调用get_person函数,通过返回值拿到人的字典数据,人的字典里面的值里面又有攻击函数的函数名,这样通过键就能拿到攻击函数的函数名,再将人与狗的字典传到括号里面去
# 实现了  数据与功能的绑定

面向对象核心思想:数据与功能的绑定!!!!

编程思想

1.面向过程编程
	过程即流程 面向过程就是按照固定的流程解决问题
		eg:截止ATM为止 使用的几乎都是面向过程编程
			注册功能 登录功能 转账功能
		需要列举出每一步的流程 并且随着步骤的深入 问题的解决越来越简单
		ps:提出问题 然后制定出该问题的解决方案
-----------------------------------------------------------------
2.面向对象编程
	对象即容器,数据与功能的结合体  (python中一切皆对象)
		例如:python中的列表里既有数据,列表自身又有一定的自带功能
		例如:游戏人物就是用面向对象的思想编程出来的  亚索 劫 盲僧
		
面向对象编程:就是不需要提供解决问题的方案,只需要产生一些固定的对象就可以,
比如英雄联盟,只需要通过代码,写成亚索这个人,然后把这个人的一些基本信息,基本的功能都写好,然后就不关我的事了。
-----------------------------------------------------------------

面向对象编程有点类似于造物主的感觉 我们只需要造出一个个对象,给他们里面添加上一些属性和功能。

至于该对象将来会如何发展跟程序员没关系 也无法控制

用户选择了亚索就相当于选择了一个对象,这个对象里面有英雄的属性,英雄的技能,这些技能只有该对象可以调用

类似于程序员写游戏,只负责把每一个人物设计好,然后放到程序里面,供用户选择,用户选择了亚索以后,想怎么玩压缩,怎么出装备,怎么连招,不关我们的事。

上述两种编程思想没有优劣之分 需要结合实际需求而定
如果需求是注册 登录 人脸识别肯定面向过程更合适

如果需求是游戏人物肯定是面向对象更合适
实际编程两种思想是彼此交融的 只不过占比不同

拿到一个对象,数据也来了,功能也来了,就比较方便,也比较节省代码。

面向对象---------类与对象

对象:数据与功能的结合体---------对象才是核心

类:多个对象相同数据和功能的结合体-----类最本质的作用,主要就是为了节省代码!!!

"""
一个人-----对象
一群人------人类(所有人相同的特征)

一条狗------对象
一群狗------犬类(所有狗相同的特征)
"""
现实中一般是先有对象再有类

程序中如果想要产生对象 必须要先定义出类!!!

类与对象的创建 重要!!!!!!

面向对象并不是一门新的技术
但是为了很好的一眼区分开------------针对面向对象,设计了新的语法格式
python中一定要有类, 才能借助于类产生对象

1.类的语法结构

---------------------------------------------------------------------
1.类的语法结构
class 类名:                                        # 类名首字母要大写
	对象公共的数据
	对象公共的功能
----------------------------------------------------------------------
1.class是定义类的关键字
2.类名的命名与变量名几乎一致, 首字母推荐大写用于区分
3.数据:变量名与数据值的绑定
4.功能(方法)其实就是函数

.

2.类的定义与调用

类在定义阶段就会执行类体代码!!!!!!!!!
但是属于类的局部名称空间 外界无法直接调用类里面的名字!!!!!!

image
.

# 需求:清华大学学生选课系统
# 定义类
class Student:
    school_name = '清华大学'               # 对象公共的数据
    def choice_course(self):              # 对象公共的功能
        print('学生选课功能')
print(Student.__dict__)

查看类的名称空间 !!!!!!       固定格式 类名.__dict__
把名称空间里面所有的名字和名字对应的值,给用字典的形式展示出来!!  重要!!!
真是因为里面有__module__ 与 __dict__ 这些名字,
所以我们才可以用类名点的方式点出它们来!!!
------------------------------------------------------------------
类的__dict__属性和类对象的__dict__属性:
a、类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类__dict__里的
b、对象的__dict__中存储了一些self.xxx的一些东西
1) 内置的数据类型没有__dict__属性
2) 每个类有自己的__dict__属性,就算存着继承关系,父类的__dict 并不会影响子类的__dict__
3) 对象也有自己的__dict__属性, 存储self.xxx 信息,父子类对象公用__dict__
。

image
.
image

数据由数据名和数据值组成
功能(函数)由功能名和功能值(函数体代码)组成
数据和功能又可以统称为属性,所以数据名与功能名都可称为属性名
名字就是属性名

__dict__就是将对象的名称空间中数据与功能用实体化字典的形式表现出来,简单点来说就是名字和名字对应的值用字典的形式表现出现,名字与名字对应的值本身与字典没有半毛钱关系。
所以通过看字典里面数据,就知道对象名称空间有哪些数据与功能了!!!或者说有哪些名字和名字对应的值了!!!
__dict__是用来存储对象属性的一个字典,键是属性名,值是属性的值。
数据与功能有时候也统称为属性----属性名就是数据名或者功能名
所以通过字典的get方法get('school_name')就能拿到'school_name'对应的值了。
print(Student._ _dict_ _.get('school_name'))
print(Student._ _dict_ _.get('choice_course'))
借助于类的名字,借助于.__dict__方法,间接的去到名称空间里面,拿到'school_name'对应的值。同理,拿类里面的函数名也一样!!!!!!!

image

注意__dict__方法只是为了方便人看,才在字典里面,将每一个名称空间中的名字两边加了一个引号,实际在名称空间里的名字没有引号!!!!!

.
.

在面向对象中 类和对象访问数据或者功能 可以统一采用点的方式!!!!!

print(Student.school_name)    # 这样更简单一点,是被优化操作后的方法
# print(Student._ _dict_ _.get('school_name')) 和上面一个效果,就是麻烦一点
print(Student.choice_course)
对于拿类里面的名字两方法的效果是一致的
----------------------------------------------------------------------

.

类的调用>>>:产生对象

类名加括号就会产生对象!!!!!!!!!

并且每执行一次都会产生一个全新的对象!!!!!!!!!

obj1 = Student()       # 变量名obj1接收类名加括号之后的返回值(结果)
obj2 = Student()
obj3 = Student()
print(obj1, obj2, obj3)      # 3个对象是不一样的!!!

image
.

print(obj1.__dict__)
print(obj2.__dict__)
print(obj3.__dict__)

类实例化产生的对象,目前的名称空间里面还什么都没有!!!

只是说每个对象自己独有的名称空间什么都没有!!!

但是每个对象可以拿到类里面公共的数据和功能!!!

所以虽然显示的结果是个空,但是依然可以通过点的方式拿到类里面所有公共的数据与功能

所以说可以认为点的方式不仅具有拿对象自己名称空间的作用,还能拿类里面的名字的作用!!!所以对于对象来说点的方法,比.dict.get('')方法和.dict['']方法更强大。

.dict.get('')方法和.dict['']方法只能拿到对象自己名称空间里面的名字对应的值,点的方法还能拿到对象所属的类里面的公共的名字对应的值

image

image
.
image

print(obj1.school_name)       # 清华大学
print(obj2.school_name)       # 清华大学
print(obj3.school_name)       # 清华大学
Student.school_name = '家里蹲大学'    # 把类里面的变量名重新赋值为'家里蹲大学'
print(obj1.school_name)
print(obj2.school_name)
print(obj3.school_name)

类里面东西是公共的,一旦改了,所有的对象在点类里面的东西时,都会全部改掉。

image



对象独有的数据 重要!!!!!!

让类产生的多个对象具有各自独有的数据与功能!!!

class Student:
    school_name = '清华大学'        # 对象公共的数据
    def choice_course(self):       # 对象公共的功能
        print('学生选课功能')

obj1 = Student()       # 生成一个学生对象
obj2 = Student()       # 生成一个学生对象

推导流程1:------------每个对象手动添加独有的数据!!!!!!

'''推导流程1:每个对象手动添加独有的数据'''
print(obj1.__dict__) # 此时对象自己的名称空间为空,或者说存储对象属性的字典里面为空
                                  # 给对象obj1添加独有属性
obj1.__dict__['name'] = 'jason'   # 给存储对象属性的字典添加属性(添加键值对)
obj1.__dict__['age'] = 18
obj1.__dict__['hobby'] = 'study'

print(obj1.__dict__)    # 再次查看对象自己名称空间(存储对象属性的字典)
print(obj1.name)
print(obj1.age)
print(obj1.hobby)
---------------------------------------------------------------------
print(obj2.__dict__)
                                   # 给对象obj2添加独有属性
obj2.__dict__['name'] = 'kevin'    # 给存储对象属性的字典添加属性(添加键值对)
obj2.__dict__['age'] = 28
obj2.__dict__['hobby'] = 'music'

print(obj2.__dict__)
print(obj2.name)
print(obj2.age)
print(obj2.hobby)

image
.
image
.
image

对象通过调用 对象名._ dict _['属性名'] = 属性值 的方法,就可以给对象添加对象自己的独有属性了!!!
现在就可以通过这种给对象属性实例化的字典,添加键值对的方式给每一个学生对象添加属于他自己的独有属性了!!!
但是上面的方法有个问题,当我们需要创很多个对象,每个对象都有自己独有的属性时,上面的代码3行就需要大量重复!!!

.
.

推导流程2----------------将添加对象独有数据的代码封装成函数!!!!!!

这步推导巨重要!!!!!!

'''推导流程2:将添加对象独有数据的代码封装成函数'''
def init(obj, name1, age1, hobby1):
     obj.__dict__['name'] = name1
     obj.__dict__['age'] = age1
     obj.__dict__['hobby'] = hobby1
stu1 = Student()
stu2 = Student()
init(stu1, 'jason', 18, 'music')
init(stu2, 'kevin', 29, 'read')

print(stu1.__dict__)
print(stu2.__dict__)

这样一来,当类产生两个学生对象后,
只需要调用:添加对象独有数据的函数代码 init() 把对象和属性值往括号里面一传
每个对象就有了各自独有的属性了!!!



推导流程3--------------给学生对象添加独有数据的函数只有学生对象有资格调用

init 是initial的缩写 开始的,最初的adj 英 /iˈniʃəl/

推导流程3:给学生对象添加独有数据的函数只有学生对象有资格调用
思路:把这个往对象里面添加独有功能的函数,放到类体的代码里面去,这样就只有学生对象可以调用给对象添加独有功能的函数了!!!

class Student:
     school_name = '清华大学'       # 对象公共的数据
    def init(obj, name2, age2, hobby2):     # 专门给学生添加独有数据的函数
         obj.__dict__['name'] = name2
         obj.__dict__['age'] = age2
         obj.__dict__['hobby'] = hobby2
     def choice_course(self):           # 对象公共的功能
         print('学生选课功能')

stu1 = Student()         # 先生成一个学生对象
Student.init(stu1, 'jason', 18, 'music')
# 通过类名点的方式拿到类里面的init函数名,再加括号,再传参运行init函数!!!
stu2 = Student()
Student.init(stu2, 'kevin', 29, 'read')

print(stu1.__dict__, stu2.__dict__)

这样就实现了只有类产生的学生对象才能调用该给对象添加属性的函数的效果了。

image

推导步骤4----------------init方法变形 最后一步!!!

'''推导步骤4:init方法变形'''
class Student:
     school_name = '清华大学'       # 对象公共的数据
# 专门给学生添加独有数据的功能,  类产生对象的过程中自动触发,运行该函数体代码!!
     def __init__(obj, name8, age8, hobby8):
         obj.__dict__['name'] = name8
         obj.__dict__['age'] = age8
         obj.__dict__['hobby'] = hobby8

     def choice_course(self):        # 对象公共的功能
         print('学生选课功能')

 stu1 = Student('jason', 18, 'read')
 print(stu1.__dict__)
 print(stu1.name)
 print(stu1.school_name)
 
原来是要先生成对象,再通过类名点的方式拿到,给对象添加属性的函数的函数名,再加括号与传参,运行该函数。
现在将原来的函数名换成__init__后,就可以在类产生对象的同时,
直接运行该函数代码!!!!!!就更方便了!!!!!!
这 __init__ 是python解释器的的固定语法!!!
作用是:当类名加括号产生一个空对象,然后将空对象与括号里面的参数一起最为参数传到__init__函数体代码里面去!!!

再补充一句:类中定义的双下方法,会在特定的时间自动触发!!!

比如双下init方法在类名加括号,产生空对象后,后就会自动触发运行!!!

image

推导步骤5-----------------------变量名修改 obj改为self

'''推导步骤5:变量名修改'''
class Student:
    school_name = '清华大学'           # 对象公共的数据
    # 专门给学生添加独有数据的功能  类产生对象的过程中自动触发
    def __init__(self, name9, age9, hobby9):
        self.__dict__['name'] = name9
        self.__dict__['age'] = age9
        self.__dict__['hobby'] = hobby9

    def choice_course(self):       # 对象公共的功能
        print('学生选课功能')

stu1 = Student('jason', 18, 'read')
print(stu1.name)
print(stu1.school_name)

推导步骤6-----------------------.dict['属性名'] = 属性值 换掉!!

类和对象访问名字对应的值,可以统一采用点名字的方式!!!

'''推导步骤5:变量名修改'''
class Student:
    school_name = '清华大学'           # 对象公共的数据
    # 专门给学生添加独有数据的功能  类产生对象的过程中自动触发
    def __init__(self, name88, age88, hobby88):
        self.name = name88    # 等于self.__dict__['name'] = name88
        self.age = age88
        self.hobby = hobby88

    def choice_course(self):       # 对象公共的功能
        print('学生选课功能')

stu1 = Student('jason', 18, 'read')
print(stu1.name)
print(stu1.school_name)

针对对象的独有数据的代码就定型了。
也就是将来在写一个类的时候,如果想要一个类产生的对象,既有公共的数据和功能,又有独有的数据,那么就在类里面提前写好def__init__(self,属性名1,属性名2...)的函数代码,比如:
def __init__(self, name8, age8, hobby8):
        self.student_name = name8
        self.student_age = age8
        self.student_hobby = hobby8
-----------------------------------------------------------
将来拿对象独有数据的时候,对象名点属性名就行了。
stu1 = Student('jason', 18, 'read')
print(stu1.student_name)

.
.
.

对象独有的功能!!! 重要!!!!!!!!!!!!!

class Student:
    school_name = '清华大学'       # 对象公共的数据
    # 专门给学生添加独有数据的功能  类产生对象的过程中自动触发
    def __init__(self, name, age, hobby):
        self.name = name  # self.__dict__['name'] = name
        self.age = age
        self.hobby = hobby

    def choice_course(self):    # 对象公共的功能
        print('学生选课功能')

stu1 = Student('jason', 18, 'music')
stu2 = Student('kevin', 28, 'read')

1.直接在全局定义功能---------该函数就不是学生对象独有的了

# 1.直接在全局定义功能  该函数就不是学生对象独有的了
def eat():
     print('吃东西')
----------------------------
stu1.eat = eat
print(stu1.__dict__)
stu1.eat()

image
.
.

2.只能将函数放在类中 但是类中的函数又是类产生的所有对象所公共拥有的

定义在类中的功能 默认就是绑定给对象使用的 谁来调谁就是主人公!!!

Student.choice_course(123)      # 类调用类里面的方法,需要自己传参数
------------------------------------------
stu1.choice_course()       #  等同于 choice_course(stu1)
当类的对象在调用类里面的方法的时候,会自动将对象当做方法的第一个参数,传给这个方法。
这就是固定规律,固定的底层原理。没有为什么。
------------------------------------------

image

class Student:
    school_name = '清华大学'       # 对象公共的数据
    # 专门给学生添加独有数据的功能  类产生对象的过程中自动触发
    def __init__(self, name, age, hobby):
        self.name = name  # self.__dict__['name'] = name
        self.age = age
        self.hobby = hobby

    def choice_course(self):      # 即算对象公共的功能,也算对象独有的功能
        print(f'学生{self.name}正在选课')

stu1 = Student('jason', 18, 'music')
stu2 = Student('kevin', 28, 'read')

stu1.choice_course()      # 对象stu1,去调用类里面公共的功能
stu2.choice_course()      # 对象stu2,去调用类里面公共的功能

谁调用谁就是主人翁!!!
-------------------------------------------------------------

image
.
.
.
.

对象修改数据值!!!!!!!!!

stu1.name = 'tony'    # 当点的名字已经存在的情况下,则修改对应的值!!!

# 对象新增数据值
stu1.pwd = 123        # 当点的名字不存在的情况下,则新增数据
print(stu1.__dict__)

# 对象删除数据值
del stu1.name       # 对象的name属性名与对应属性值就删除了,很少用

image

image
.
.

总结

学完本课,当我定义好类,类里面也有了可以给对象添加独有数据的函数,公共的函数,公共数据。
那我以后我们想要产生一个对象时,就只需要先通过类名加括号,并在括号里面传对象独有的数据值后,再通过对象名点的方式,拿类体里面的公共函数的函数名,再加括号运行函数,并将对象自身当作第一个参数给传到函数里面去!!!
类里面的方法虽然是公共的方法,但是由于哪个对象调用该公共方法,都要先把对象自身最为第一个参数给传到公共的方法里,所以公共的方法也相当于对象独有的方法。
底层原理是:
类里面的方法在对象调用的时候,都是将类里面的方法复制一份给对象用,所以不同对象在调公共的方法时,产生的数据内存地址是不一样的!!!!!!,所以类里面的方法称为绑定方法,谁调用,就就是主人公



昨日内容回顾

  • 人狗大战
  1.直接使用字典表示人和狗
  	p1 = {}
  	p2 = {}
  	d1 = {}
  	d2 = {}
  2.封装产生人和狗的函数
  	def create_person():pass
  	def create_dog():pass
  3.封装人和狗的攻击动作
  	def person_attack(person_dict, dog_dict):pass
  	def dog_attack(dog_dict, person_dict):pass
  4.针对人和狗的攻击动作只能针对性调用
  	人只能调用人的攻击动作 狗只能调用狗的攻击动作
  5.将数据与功能绑定到一起
  	def get_person():
          def person_attack():pass
          return {'person_attack':person_attack}
  	def get_dog():
          def dog_attack():pass
          return {'dog_attack':dog_attack}
  6.面向对象核心思想:数据与功能的绑定
  • 编程思想
  面向过程编程
	针对具体的问题给出具体的解决流程 
		eg:注册功能 登录功能 转账功能

  面向对象编程
	没有具体的流程 我们只负责造物
     	eg:游戏人物

  """两种思想不是独立的 是相互融合的"""
  • 类与对象的概念

    类
    	多个对象相同数据和功能的结合体
    对象
    	数据与功能的结合体
    
  • 类与对象的代码实操

    class Student:
        pass
    obj = Student()
    1.如何查看类或者对象名称空间中可以使用的名字
    	类/对象.__dict__
    2.面向对象访问名字统一采用句点符
    	类/对象.名字
    3.类名加括号一定会产生一个新的对象
    	 obj1 = Student()
    	 obj2 = Student()
        obj3 = Student()
    
  • 对象的独有数据

    1.直接利用对象点名字的方式 
    	obj1 = Student()
    	obj1.__dict__['name'] = 'jason'
     	obj1.age = 18
    	obj2 = Student()
    	obj2.__dict__['name'] = 'kevin'
     	obj2.age = 29
    2.将添加对象独有数据的代码封装成函数
    	def init(obj,name,age):
            obj.name = name  # obj.__dict__['name'] = name
            obj.age = age  # obj.__dict__['age'] = age
    3.将上述方法绑定给特定类的对象
    	class Student:
            def init(obj,name,age):
            	obj.name = name  # obj.__dict__['name'] = name
            	obj.age = age  # obj.__dict__['age'] = age
    4.自动触发的方法
    	class Student:
            def __init__(self,name,age):
            	self.name = name  # obj.__dict__['name'] = name
            	self.age = age  # obj.__dict__['age'] = age
    5.类中如果有双下init方法 意味着类加括号需要传参数
    	obj = Student('jason', 18)
    
  • 对象的独有方法

    1.也是直接在全局定义函数 然后给对象
    	def func():pass
       	obj = Student()
    	obj.func = func
    2.面向对象的思想 放到类中
    3.对象的绑定方法
    	类中定义的方法既可以说是对象公共的方法 也可以是对象独有的方法
    	哪个对象来调用 谁就是主人公
    

今日内容概要

  • 动静态方法

  • 面向对象之继承理论

  • 继承基本操作

  • 对象查找名字的顺序(非常重要)

    1.不继承
    2.单继承
    3.多继承
    
  • 继承本质

  • 基于继承的派生方法(重要)

今日内容详细

动静态方法------在类中定义的函数有多种特性

1.绑定给对象的方法


class Student:
    school_name = '摆烂大学'
    # 1.类中直接定义函数 默认绑定给对象 类调用有几个参数传几个 对象调用第一个参数就是对象自身
	def func1(self):
		print('看谁最能摆烂 真的好棒棒!!!')

obj = Student()
obj.func1()
Student.func1(123)

image
.
.
.
.

2.绑定给类的方法------在源码里面会经常看到!!!!!!


# 被@classmethod修饰的函数 默认绑定给类!!!!!
# 类调用第一个参数就是类自身!!!!!!
# 对象也可以调用,并且会自动将产生该对象的类当做第一个参数传入!!!!!!
class Student:
    school_name = '摆烂大学'
	def func1(self):
		print('看谁最能摆烂 真的好棒棒!!!')
	@classmethod
	def func2(cls):      # cls就是类class的简写
		print('嘿嘿嘿 猜猜我是干嘛滴', cls)

obj = Student()
Student.func2()    # fun2(Student)
obj.func2()        # func2(Student)

.
.
.
.

3.普普通通的函数 ,叫静态方法

无论是类还是对象调用,都必须自己手动传参!!!!!!


class Student:
	school_name = '摆烂大学'
	@staticmethod
	def func3(a):
		print('哈哈哈 猜猜我又是什么', a)

Student.func3(123)    # 不传参就报错
obj.func3(321)

类产生对象,对象有资格到类里面究竟找任何它想要找的东西的!!!

类也好对象也好,其实就是一些名字的集合体,里面既有数据又有功能,通过点的方式就能拿到想要的名字对应的值。

image
.
.
.

面向对象三大特性


封装 继承 多态
1.三者中继承最为核心(实操最多 体验最强)
2.封装和多态略微抽象

.
.
.
.
.
.
.

面向对象之继承的概念


1.继承的含义
	在编程世界中继承表示:类与类之间资源的从属关系
		eg:类A继承类B
2.继承的目的
	在编程世界中类A继承类B,就拥有了类B中所有的数据和方法使用权限!!!!!!
3.继承的实操

class Father:
	money = 666666666
	def play(self):
		print('再喊一声,带你会所嗨皮')

class Son(Father):
	pass
print(son.__dict__)     # 现在名称空间为空
print(son.money)     # 可以拿到父类的里面的名字money对应的值。
print(son.play)    #  可以拿到父类的里面,函数名play对应的代码
-------------------------

obj = son()
print(obj.__dict__)    # son类为空,它产生的对象名称空间更为空
print(obj.money)   # 子类产生的对象,还是可以拿到父类里面的名字对应的值
obj.play() # 子类产生的对象,还是可以拿到父类里面,函数名play对应的代码

.
.
.
.

类的继承就相当于让对象拥有的多个可以去查找数据和方法的地方!!!!!!

1.在定义类的时候类名后面可以加括号填写其他类名 意味着继承其他类

2.在python支持多继承 括号内填写多个类名彼此逗号隔开即可


class Son(F1, F2, F3):
	pass

image
.
image
.
image
.
.


1.继承其他类的类	Son
		我们称之为子类、派生类
2.被继承的类  Father F1 F2 F3
	我们称之为父类、基类、超类
ps:我们最常用的就是子类和父类

.
.
.

继承的本质

类与父类本质都是为了节省代码!!!!!!

比如老师类与学生类里面都有相同的定义对象独有属性的代码
image
.
把相同的代码直接抽出来,放在新建的poeple类里面,老师与学生类直接继承people类就行了
image
.
这样,子类通过类名加括号的方式正常传参,就也能触发父类里面的__init__的方法了
image
.
.
.
.


"""
对象:数据与功能的结合体
类(子类):多个对象相同数据和功能的结合体
父类:多个类(子类)相同数据和功能结合体
"""
继承本质应该分为两部分
	抽象:将多个类相同的东西抽出去形成一个新的类
	继承:多个子类继承刚刚抽取出来的新的类
	

.
.
.
.
.

名字的查找顺序

1.不继承情况下名字的查找顺序


1.不继承情况下名字的查找顺序
	class C1:
    name = 'jason'

    def func(self):
        print('from func')

obj = C1()
print(C1.name)    # 类肯定找的自己的

------------------------------------

obj.name = '你迷了吗'  # 由于对象原本没有name属性 该语法会在对象名称空间中创建一个新的'键值对'
print(obj.__dict__)
print(obj.name)  # 你迷了吗
print(C1.name)



类里面的name,只有通过类名.name= 的方式能改,类里面的name,通过对象名点的方式
obj.name= 是改不了的!!!obj.name只有资格访问,obj.name='你迷了吗'的意思是
在对象名称空间中创建一个新的属性名name指向了'你迷了吗'

image
.
.
.
.
.
.

对象查找名字的顺序(没有继承的情况下)


对象查找名字的顺序
1.先从自己的名称空间中查找!!!
2.自己没有再去产生该对象的类中查找!!!
3.如果类中也没有 那么直接报错!!!
对象自身 >>>  产生对象的类

.
.
.
.
.
.

2.单继承情况下名字的查找顺序 重要!!!


2.单继承情况下名字的查找顺序
class F1:
     name = 'jason'
class S1(F1):
     name = 'kevin'

obj = S1()
obj.name = 'oscar'    # 给对象添加独有的属性
print(obj.name)


image
.
image
.
.
.
.
.

结论:对象自身>>>产生对象的类>>>父类


class F3:
     name = 'jerry'
     pass

class F2(F3):
     name = 'tony'
     pass

class F1(F2):
     name = 'jason'
     pass

class S1(F1):
     name = 'kevin'
     pass
obj1 = S1()
obj1.name = '嘿嘿嘿'
print(obj1.name)

当前层找不到,就一层一层的往上找就行了!!!
image
.
.
.
.
.
.
.

面试题--------重要!!!


class A1:
	def func1(self):
		print('from A1 func1')

	def func2(self):                  # 1     此处的self指代的是obj!!!
		print('from A1 func2')        # 2
		self.func1()                  # 3

class B1(A1):
	def func1(self):                  # 4
		print('from B1 func1')        # 5

obj = B1()
obj.func2()

from A1 func2
from B1 func1

.
.
.
.

补充

类的继承,对象在点一个函数名字的时候,不管这个对象在哪,谁能调到这个函数名,
就会把这个对象当作第一个参数传进来,无论这个方法是类里面的还是父类里面的,
还是父父类里面的,都叫绑定给对象的方法。哪个对象点的,哪个对象就作为self给传进去。
image
.
image
.
.

强调:对象点名字 永远从对象自身开始一步步往外查找!!!!

以后在看到self.名字的时候 一定要搞清楚self指代的是哪个对象

.
.
.
.
.
.

3.多继承情况下名字的查找顺序

	菱形继承
		广度优先(最后才会找闭环的定点)
	非菱形继承
		深度优先(从左往右每条道走完为止)
	ps:mro()方法可以直接获取名字的查找顺序
-------------------------------------------------------
'''
    对象自身  >>>  产生对象的类  >>> 多个父类时(从左往右依次查找!!)
'''
class F1:
    name = 'jason'
    pass

class F2:
    name = 'oscar'
    pass

class F3:
    name = 'jerry'
    pass

class S1(F1, F2, F3):
    name = '嘿嘿嘿'
    pass
obj = S1()
obj.name = '想干饭'
print(obj.name)

image
.
image
.
image
.
.

class G:
    name = 'from G'
    pass

class A:
    name = 'from A'
    pass

class B:
    name = 'from B'
    pass

class C:
    name = 'from C'
    pass

class D(A):
    name = 'from D'
    pass

class E(B):
    name = 'from E'
    pass

class F(C):
    name = 'from F'
    pass

class S1(D, E, F):
    pass

obj = S1()
print(S1.name)
print(S1.mro())  # S1.mro()这个方法的作用是,能够给返回类里面涉及名字查找的时候,名字顺序!!!
非菱形继承
	深度优先(从左往右每条道走完为止)!!!

image
.
当类A,B,C都继承类G的时候,就形成闭环,变成菱形继承
菱形继承
广度优先(最后才会找闭环的定点)!!!
image
.
image
.
.
.
.
.
.

经典类与新式类


object源码里面定义了很多双下的方法,有作为一个类所必须具备的全部条件,
类如果不继承object类,就连作为类的基本条件都没有了。

object不参与菱形非菱形继承的闭环操作,可以认为是一个虚的父类。


"""
经典类:不继承object或者其子类的类
新式类:继承object或者其子类的类

在python2中有经典类和新式类
在python3中只有新式类(所有类默认都继承object)
"""
class Student(object):pass

ps:以后我们在定义类的时候 如果没有其他明确的父类 也可能习惯写object兼容python2

.
.
.
.
.
.
.

派生方法

子类基于父类某个方法做了扩展


class Person:
    def __init__(self, name1, age1, gender1):
        self.name = name1
        self.age = age1
        self.gender = gender1
-----------------------------------------------------

class Student(Person):
    def __init__(self, name1, age1, gender1, sid1):  # sid1是加的参数
        super().__init__(name1, age1, gender1)   # super()子类调用父类的方法
        self.sid = sid1
---------------------------------------------

class Teacher(Person):
    def __init__(self, name2, age2, gender2, level2):
        super().__init__(name2, age2, gender2)
        self.level = level2

------------------------------------------

stu1 = Student('jason', 18, 'male', 666)
print(stu1.__dict__)
tea1 = Teacher('tony', 28, 'female', 99)
print(tea1.__dict__)

.
.
.

派生实战代码:

class MyList(list):
   pass
obj = MyList()
print(obj, type(obj))
------------------------------
obj.append(111)
obj.append(222)
obj.append(333)
print(obj)
------------------------------

现在要求append方法不能往对象obj里面追加jason,怎么办?
要基于append方法之上做一些额外的限制,
class MyList(list):
    def append(self, values):      # 在子类里面再定义一个append方法
        if values == 'jason':
            print('jason不能尾部追加')
            return
        super().append(values)     # 子类调用父类的append方法,if不走就走它
obj.append(111)
obj.append(222)
obj.append(333)
obj.append('jason')             # 现在想往里面加也加不进去了!!!
print(obj)

image
.
image
.
.
.
.
.
.

名称空间与作用域

什么是名称空间


回忆:我们定义变量时,发生了什么
name = 'leethon'
"""
赋值符号右边,产生了一个数据值'leethon',我们在内存中划定一个区域,将其存进去
内存空间很大,数据值的位置需要被记录,所以记录在了变量名name中!!!!!!
"""

提问:变量名以及数据值的位置存在什么地方呢?
实际上,变量名及其绑定的数据值的位置也存在内存中,它们被集中放在内存中方便寻找的某一处,这个地方就是名称空间。

名称空间不仅会存储变量名,还会存储函数名等所有名字。

.
.
.
.
.
.

三大名称空间介绍


内置名称空间
解释器级别的名称空间,存了所有内置函数和关键字的名字如:print、len、def

全局名称空间
文件级别的名称空间,当运行py文件时,会产生一个全局名称空间,存储文件中定义的变量名、函数名还有类名(之后会讲)。

局部名称空间
函数级别的名称空间,在调用某个函数时,会产生一个局部名称空间,存储函数中定义的变量名、函数名等。

.
.
.
.

名称空间存活周期和作用域


存活周期
内置名称空间:python解释器启动则创建,关闭则销毁。
全局名称空间:py文件执行则创建,运行结束则销毁。
局部名称空间:函数体代码运行创建,函数体代码结束则销毁。

作用域
内置名称空间:只要是python程序,其中的名字都可以使用。
全局名称空间:只要是当前的py文件,其中的名字在定义完就可以使用。
局部名称空间:当前函数体代码内有效。

.
.
.
.
.
.
.
.

名字的查找顺序


有时候,我们可能在函数内定义了某个变量名而在全局也定义了这个变量名,但我们在全局调用某个函数时,
它内部的变量名我们并不能考虑的到,所以规则上,函数体内的局部变量名和全局中的全局变量名不应该互相影响。
所以,python规定了名称空间的作用域,而我们在使用变量时,也要搞清楚变量名在哪个名称空间中。

局部名称空间内使用名字:
局部名称空间 ---> 全局名称空间 ---> 内置名称空间

全局名称空间内使用名字:
全局名称空间 ---> 内置名称空间

ps:所以我们在变量名命名时,会规定变量名不能使用系统提供给我们的关键字,
因为会优先使用局部和变量的名称而使关键字失去原本的功能。

.
image
当在局部作用域中使用名字,会按照从低到高的名称空间级别去找。
.
.
.
.
.


两个局部名称空间是独立的,不会使用对方空间中的名字,如果没在局部找到则到全局名称空间找,如果全局没找到就会去内置名称空间找。
# 例子2
x = 1
def func1():
    x=2
    def func2():
        # x = 3
        print(x)

    func2()
func1()
![image](https://img2022.cnblogs.com/blog/2985186/202211/2985186-20221107170501812-121103056.png)
名字print的查找过程:

func2局部名称空间 ---> func1局部名称空间 ---> 全局名称空间---> 内置名称空间

.
.
.
.
.
.
.
.

定义类时的注意事项


def test1(b=[]):
    # b = []
    l = b
    l.append(1)
    return l


print(test1())  # [1]
print(test1())  # [1, 1]
print(test1())  # [1, 1, 1]
# 可以看到虽然在函数定义阶段,位置行参b设置了默认值,为空列表
# 但是每次调用test1函数的时候,不是重新再让位置行参b为空列表,而是继续用的同一个列表!!!
# 为了避免这种坑,不要在函数的定义阶段,给位置形参设置默认值的时候,设置的是一个可变的数据类型!!!
# 可以把设置默认值的操作写到函数里面去!!!

------------------------------------------------------
------------------------------------------------------
------------------------------------------------------

# 同理在类的定义阶段也是一样,不要这样写
# 函数定义阶段,位置形参设置的默认值的数据类型,不要设置可变类型!!!!!!
class Test:
    def __init__(self, a=[1, ]):
        # a = []
        self.l = a
        self.l.append(1)


t1 = Test()
print(t1.l)  # [1, 1]

t2 = Test()
print(t2.l)  # [1, 1, 1]



.
.
.
.

posted @ 2022-11-02 22:45  tengyifan  阅读(44)  评论(0)    收藏  举报