笔记day03



上周内容回顾

  • 函数

  • 算法

  • 迭代器生成器

  • 异常处理

本周内容概要

  • 作业讲解

  • 模块

  • 面向对象

本周内容详细

作业讲解

"""
讲师:JasonJi   外号:鸡哥
邮箱:18817628568@163.com
微信:15618087189、18817628568(备注来源)
"""

# 2.利用while与异常捕获 实现for循环的迭代取值的功能
# info = {'name': 'jason', 'age': 18, 'pwd': 123}
"""
for循环底层原理
  1.将in后面的数据自动调用双下iter方法转为迭代器
  2.再让迭代器反复调用双下next方法取值
  3.一旦取值完毕报错 自动处理并结束循环
"""
# res = info.__iter__() # 可以简写为iter(info)
# print(res.__next__()) # 可以简写为next(info)
# res = info.__iter__()
# while True:
#     try:
#         print(res.__next__())
#     except StopIteration:
#         break

# 3.编写生成器模拟出range方法所有的功能
"""
range()在python3中是一个迭代器
  range(10)           0 9
  range(1,10)         1 9
  range(1,10,2)       1等差2
生成器的本质其实还是迭代器 只不过迭代器是系统自动提供的而生成器需要我们自己编写
自定义迭代器肯定要想到的关键字yield

编程技巧:如果相似的功能需求有多个那么可以先写其中的一个 然后基于这一个扩展即可
"""
# 3.1.先写两个参数的版本
# def my_range(start_num, end_num):
#     while start_num < end_num:
#         yield start_num
#         start_num += 1
# 3.2.思考如果就传一个参数
# def my_range(start_num, end_num=0):
#     if not end_num:
#         end_num = start_num
#         start_num = 0
#     while start_num < end_num:
#         yield start_num
#         start_num += 1
# 3.3.传三个参数
# def my_range(start_num, end_num=0, step=1):
#     if not end_num:
#         end_num = start_num
#         start_num = 0
#     while start_num < end_num:
#         yield start_num
#         start_num += step


# 4.编写一个用户认证装饰器
# register login transfer withdraw
# 基本要求
#   执行每个函数的时候必须先校验身份 eg: jason 123
#   拔高练习(有点难度)
#   执行被装饰的函数 只要有一次认证成功 那么后续的校验都通过
#   提示:全局变量 记录当前用户是否认证

# 定义一个存储用户是否登录的数据字典
is_login = {
   'username':''
}

def login_auth(func_name):
   def inner(*args, **kwargs):
       # 先判断全局字典中username键是否有值 如果有说明用户已经登录成功 无需反复校验
       if is_login.get('username'):
           res = func_name(*args, **kwargs)
           return res
       # 执行被装饰函数之前可以做的操作
       username = input('username>>>:').strip()
       password = input('password>>>:').strip()
       if username == 'jason' and password == '123':
           # 记录用户登录成功的状态
           is_login['username'] = username
           res = func_name(*args, **kwargs)
           # 执行被装饰函数之后可以做的操作
           return res
       else:
           print('用户名或密码错误 没有资格执行当前函数')
   return inner

@login_auth
def register():
   print('注册功能')
@login_auth
def login():
   print('登录功能')
@login_auth
def transfer():
   print('转账功能')
@login_auth
def withdraw():
   print('提现功能')

register()
login()
transfer()
withdraw()

模块简介

"""
python屈辱史
python刚开始出来的时候被其他编程程序员瞧不起
ps:太简单 写代码都是调用模块(调包侠 贬义词)
随着业务的扩展其他程序员也需要使用python写代码
写完之后发现python真香 贼好用(调包侠 褒义词)
为什么python很牛逼
python之所以牛逼就是因为支持python的模块非常的多 非常的全 非常的猛
作为一名python程序员
将来接收到某个业务需求的时候 不要上来就想着自己写 先看看有没有相应的模块已经实现
"""

1.如何理解模块
模块可以看成是一系列功能的结合体
  使用模块就相当于拥有了这结合体内的所有功能
ps:使用模块编程就相当于站在巨人的肩膀上
2.模块的分类
1.内置模块
  解释器自带的 直接就可以使用的模块
      eg:
               import time
               time.sleep(3)
2.自定义模块
  自己写的模块
      eg:
            注册功能 登录功能
3.第三方模块
  别人写的模块 存在于网络上 使用之前需要提前下载
      eg:
图形识别 图形可视化 语音识别
3.模块的表现形式
1.py文件(py文件也可以称之为是模块文件)
2.含有多个py文件的文件夹(按照模块功能的不同划分不同的文件夹存储)
3.已被编译为共享库或DLL的c或C++扩展(了解)
4.使用C编写并链接到python解释器的内置模块(了解)

导入模块的两种句式

"""
补充说明
以后真正的项目中 所有的py文件名称都是英文
没有所谓的编号和中文
eg:
错误的 01.变量的使用
正确的 test01.py
学习模块的时候 模块文件的名称就得用英文

py文件被当做模块导入的时候不需要考虑后缀
"""
导入模块的句式1 import句式
import md
   """
  import md
  执行文件是   02 模块的导入句式.py
  被导入文件是 md.py

  1.会产生执行文件的名称空间
  2.产生被导入文件的名称空间并运行该文件内所有的代码 存储名字
  3.在执行文件中会获取一个模块的名字 通过该名字点的方式就可以使用到被导入文件名称空间中的名字

  补充说明
      同一个程序反复导入相同的模块 导入语句只会执行一次
          import md   有效
          import md   无效
          import md   无效
  """
   # money = 10
   # print(md.money)
   # print(money)


   # def read1():
   #     print('我是执行文件里面的read函数')
   # md.read2()

   # money = 1000
   # md.change()
   # print(money)
   # print(md.money)

导入模块的句式2 from...import...句式
# from md import money # 指名道姓的导入
   # print(money) # 999
   # money = '嘿嘿嘿'
   # print(money) # 嘿嘿嘿
   # print(read1)
   # print(md.read1())

   from md import money, read1
   read1()
   """
  1.创建执行文件的名称空间
  2.创建被导入文件的名称空间
  3.执行被导入文件中的代码 将产生的名字存储到被导入文件的名称空间中
  4.在执行文件中获取到指定的名字 指向被导入文件的名称空间
  """

两种导入句式的优缺点

import md
优点:通过md点的方式可以使用到模块内所有的名字 并且不会冲突
缺点:md什么都可以点 有时候并不想让所有的名字都能被使用
from md import money, read1
优点:指名道姓的使用指定的名字 并且不需要加模块名前缀
缺点:名字及其容易产生冲突(绑定关系被修改)

补充知识

1.起别名
情况1:多个模块文件名相同(多个人写)
       from md import money as md_my
       from md1 import money as md1_my
       print(md_my)
       print(md1_my)
情况2:原有的模块文件名复杂
      import mdddddddddd as md
       
2.导入多个名字
import time, sys, os
上述导入方式建议多个模块功能相似才能适应 不相似尽量分开导入
import time
import os
import sys
   
from md import money, read1, read2
上述导入方式是推荐使用的 因为多个名字出自于一个模块文件
   
3.全导入
需求:需要使用模块名称空间中很多名字 并且只能使用from...import句式
from md import *  # *表示所有
ps:针对*号的导入还可以控制名字的数量
在模块文件中可以使用__all__ = [字符串的名字]控制*能够获取的名字

循环导入问题

如何理解循环导入
循环导入就是两个文件彼此导彼此
循环导入容易出现报错现象
使用彼此的名字可能是在没有准备好的情况下就使用了
如何解决循环导入保存现象
彼此在使用彼此名字之前 先准备好
 
"""循环导入将来尽量避免出现!!! 如果真的避免不了 就想办法让所有的名字在使用之前提前准备好"""

判断文件类型

学习完模块之后 以后我们的程序运行起来可能涉及到的文件就不止一个

所有的py文件中都自带一个__name__内置名
当py文件是执行文件的时候 __name__的结果是__main__
当py文件是被导入文件的时候 __name__的结果是模块名(文件名)
   
__name__主要用于开发模块的作者测试自己的代码使用
if __name__ == '__main__':
  当文件是执行文件的时候才会执行if的子代码
       
上述判断一般只出现整个程序的启动文件中
       
ps:在pycharm中可以直接编写main按tab键自动补全

模块的查找顺序

"""
1.先去内存中查找
2.再去内置中查找
3.再去sys.path中查找(程序系统环境变量) 下面详细的讲解
"""
1.导入一个文件 然后在导入过程中删除该文件 发现还可以使用
import md
   import time

   time.sleep(15)
   print(md.money)
2.创建一个跟内置模块名相同的文件名
# import time
   # print(time.time())
   from time import name
   print(name)
ps:创建模块文件的时候尽量不要与内置模块名冲突
3.导入模块的时候一定要知道谁是执行文件
所有的路径都是参照执行文件来的
import sys
   sys.path.append(r'D:\pythonProject\day22\xxx')
   import mdd
   print(mdd.name)
"""
1.通用的方式
sys.path.append(目标文件所在的路径)
2.利用from...import句式
起始位置一定是执行文件所在的路径
from xxx import mdd
"""

常见模块

直接利用句式导入即可
time,datetime 时间相关模块
 os,sys 操作系统、python解释器相关
 json 序列化模块
 hashlib 加密模块
 random 随机数模块
 logging 日志模块
 re 正则模块

第三方模块 需要先下载后使用
pip install 模块名==版本号
1.默认的下载地址是国外的 速度有点慢
   2.可以切换下载的地址提升速度
  pip install 模块名 -i 源地址
  pycharm永久切换
     解释器配置文件永久修改
3.下载第三方模块有三大类错误
  1.含有关键字timeout
    说明网络波动太大 多尝试几次或者切换稳定的网络
     2.pip版本过低
    报错信息中会提供更新pip的命令 拷贝执行即可
     3.需要特定的环境
    拷贝报错信息至百度查找即可

面向对象前戏

人狗大战
# 1.推导步骤1:想办法描述出人和狗>>>:字典
# p1 = {
#     'name': 'jason',
#     't_type': '猛男',
#     'attack_val': 800,
#     'life_val': 2000
# }
# d1 = {
#     'name': '小黑',
#     't_type': '田园犬',
#     'attack_val': 200,
#     'life_val': 8000
# }
# 2.推导步骤2:将表示人和狗的代码封装成函数 避免重复编写>>>:函数
# def get_person(name, p_type, attack_val, life_val):
#     '''专门产生人'''
#     person = {
#         'name': name,
#         'p_type': p_type,
#         'attack_val': attack_val,
#         'life_val': life_val
#     }
#     return person


# def get_dog(name, d_type, attack_val, life_val):
#     '''专门产生狗'''
#     dog = {
#         'name': name,
#         'd_type': d_type,
#         'attack_val': attack_val,
#         'life_val': life_val
#     }
#     return dog


# 3.定义人和狗彼此攻击的功能
# def person_attack(person_dict, dog_dict):
#     '''人打狗'''
#     print('当前狗的基本信息:%s' % dog_dict)
#     dog_dict['life_val'] -= person_dict.get('attack_val') # 简单模拟
#     print(
#         f"人:{person_dict.get('name')} 打了狗:{dog_dict.get('name')} 掉血:{person_dict.get('attack_val')} 狗剩余血量为:{dog_dict.get('life_val')}")


# def dog_attack(dog_dict, person_dict):
#     '''狗咬人'''
#     print('当前人的基本信息:%s' % person_dict)
#     person_dict['life_val'] -= dog_dict.get('attack_val') # 简单模拟
#     print(
#         f"狗:{dog_dict.get('name')} 咬了人:{person_dict.get('name')} 掉血:{dog_dict.get('attack_val')} 人剩余血量为:{person_dict.get('life_val')}")


# 4.人狗大战
# p1 = get_person('jason', '猛男', 8000, 10000)
# p2 = get_person('kevin', '菜鸡', 100, 1000)
# d1 = get_dog('小黑', '恶霸犬', 8888, 100000)
# d2 = get_dog('小黄', '泰迪犬', 100, 500)
# person_attack(p1, d1) # 人打狗
# print(d1)
# dog_attack(d2, p2) # 狗咬人
# print(p2)

# 5.核心矛盾(乱套)
# person_attack(d1, p1) # 狗调用了人的攻击方法
# dog_attack(p1, d2) # 人调用了狗的攻击方法

# 6.数据与功能相互绑定>>>:人打狗的动作只能人来调 ...
def get_person(name, p_type, attack_val, life_val):
   '''专门产生人'''

   # 人的功能
   def person_attack(person_dict, dog_dict):
       '''人打狗'''
       print('当前狗的基本信息:%s' % dog_dict)
       dog_dict['life_val'] -= person_dict.get('attack_val')  # 简单模拟
       print(
           f"人:{person_dict.get('name')} 打了狗:{dog_dict.get('name')} 掉血:{person_dict.get('attack_val')} 狗剩余血量为:{dog_dict.get('life_val')}")

   # 人的数据
   person = {
       'name': name,
       'p_type': p_type,
       'attack_val': attack_val,
       'life_val': life_val,
       'attack': person_attack
  }
   return person


def get_dog(name, d_type, attack_val, life_val):
   '''专门产生狗'''
   # 狗的功能
   def dog_attack(dog_dict, person_dict):
       '''狗咬人'''
       print('当前人的基本信息:%s' % person_dict)
       person_dict['life_val'] -= dog_dict.get('attack_val')  # 简单模拟
       print(
           f"狗:{dog_dict.get('name')} 咬了人:{person_dict.get('name')} 掉血:{dog_dict.get('attack_val')} 人剩余血量为:{person_dict.get('life_val')}")

   # 狗的数据
   dog = {
       'name': name,
       'd_type': d_type,
       'attack_val': attack_val,
       'life_val': life_val,
       'attack': dog_attack
  }
   return dog


p1 = get_person('jason', '猛男', 8000, 10000)
p2 = get_person('kevin', '菜鸡', 100, 1000)
d1 = get_dog('小黑', '恶霸犬', 8888, 100000)
d2 = get_dog('小黄', '泰迪犬', 100, 500)
p1.get('attack')(p1, d2)
d2.get('attack')(d2, p2)
"""
我们的目的是想让数据和功能有关系
"""

总结
面向对象其实就是上述推导过程中的思路>>>:将数据和功能绑定到一起

总结

将人的数据跟人的功能绑定到一起
只有人可以调用人的功能
将狗的数据跟狗的功能绑定到一起
只有狗可以调用狗的功能
我们将数据与功能绑定到一起的操作起名为:'面向对象编程'

本质:将特定的数据与特定的功能绑定到一起 将来只能彼此相互使用

编程思想

面向过程编程
截止昨天 我们所编写的代码都是面向过程编程
过程其实就是流程 面向过程编程其实就是在执行一系列的流程
eg: 注册功能   登录功能   冻结账户  ...
   就是按照指定的步骤依次执行 最终就可以得到想要的结果
   
面向对象编程
核心就是'对象'二字
  对象其实就是一个容器 里面将数据和功能绑定到了一起
eg: 游戏人物 ...
   只负责创造出该人物以及该人物具备的功能 至于后续战绩如何无人知晓
"""
面向过程编程相当于让你给出一个问题的具体解决方案
面向对象编程相当于让你创造出一些事物之后不用你管
"""

上述两种编程思想没有优劣之分 仅仅是使用场景不同
甚至很多时候是两者混合使用

对象与类的概念

对象:数据与功能的结合体
类:多个对象相同的数据和功能的结合体
"""
类比学习法
一个人 对象
多个人 人类

一条狗 对象
多条狗 犬类
"""
类主要用于记录多个对象相同的数据和功能
对象则用于记录多个对象不同的数据和功能
ps:在面向对象编程中 类仅仅是用于节省代码 对象才是核心

对象与类的创建

在现实生活中理论是应该先有一个个的个体(对象)再有一个个的群体(类)
在编程世界中必须要先有类才能产生对象


面向对象编程本质就是将数据和功能绑定到一起 但是为了突出面向对象编程的形式
python特地开发了一套语法专门用于面向对象编程的操作


创建类的完整语法
class People:
   # 学生对象公共的数据
   # 学生对象公共的方法
   
1.class是定义类的关键字
2.People是类的名字
类名的命名跟变量名一致 并且推荐首字母大写(为了更好的区分)
3.类体代码
公共的数据\公共的方法
ps:类体代码在类定义阶段就会执行!!!
   

# 查看名称空间的方法
# print(Student.__dict__) # 使用该方法查看名称空间 可以看成是一个字典
# print(Student.__dict__['school']) # 使用字典的取值方式获取名字
# print(Student.__dict__.get('choice_course')) # 使用字典的取值方式获取名字
'''在面向对象编程中 想要获取名称空间中的名字 可以采用句点符'''
# print(Student.school)
# print(Student.choice_course)
'''类实例化产生对象>>>: 类名加括号'''
stu1 = Student()
stu2 = Student()
print(stu1.school)
print(stu2.school)
# print(stu1) # <__main__.Student object at 0x000001D923B04A60>
# print(stu2) # <__main__.Student object at 0x0000025E8A48F130>
# print(stu1.__dict__, stu2.__dict__) # {} {}
# print(stu1.school)
# print(stu2.school)
# print(stu1.choice_course)
# print(stu2.choice_course)
Student.school = '北京大学'  # 修改school键对应的值
print(stu1.school)
print(stu2.school)
"""
我们习惯将类或者对象句点符后面的东西称为属性名或者方法名
"""

对象独有的数据

# 学生类
# class Student:
#     # 学生对象公共的数据
#     school = '清华大学'
#
#     # 学生对象公共的方法
#     def choice_course(self):
#         print('正在选课')

'''推导思路1: 直接利用__dict__方法朝字典添加键值对'''
# obj1 = Student()
# obj1.__dict__['name'] = 'jason' # 等价于 obj1.name = 'jason'
# obj1.__dict__['age'] = 18 # 等价于 obj1.age = 18
# obj1.__dict__['gender'] = 'male' # ...
# print(obj1.name)
# print(obj1.age)
# print(obj1.gender)
# print(obj1.school)
# obj2 = Student()
# obj2.__dict__['name'] = 'kevin'
# obj2.__dict__['age'] = 28
# obj2.__dict__['gender'] = 'female'
# print(obj2.name)
# print(obj2.age)
# print(obj2.gender)
# print(obj2.school)
'''推导思路2: 将添加独有数据的代码封装成函数'''
# def init(obj,name,age,gender):
#     obj.__dict__['name'] = name
#     obj.__dict__['age'] = age
#     obj.__dict__['gender'] = gender
# stu1 = Student()
# stu2 = Student()
# init(stu1,'jason',18,'male')
# init(stu2, 'kevin',28,'female')
# print(stu1.__dict__)
# print(stu2.__dict__)
'''推导思路3: init函数是专用给学生对象创建独有的数据 其他对象不能调用>>>:面向对象思想   将数据和功能整合到一起
将函数封装到学生类中 这样只有学生类产生的对象才有资格访问
'''


class Student:
   """
  1.先产生一个空对象
  2.自动调用类里面的__init__方法 将产生的空对象当成第一个参数传入
  3.将产生的对象返回出去
  """
   def __init__(self, name, age, gender):
       self.name = name  # obj.__dict__['name'] = name
       self.age = age  # obj.__dict__['age'] = age
       self.gender = gender  # obj.__dict__['gender'] = gender
       # 左右两边的名字虽然一样 但是意思不一样 左边的其实是字典的键 右边的其实是实参

   # 学生对象公共的数据
   school = '清华大学'

   # 学生对象公共的方法
   def choice_course(self):
       print('正在选课')


# stu1 = Student()
# print(stu1.__dict__)
# Student.init(stu1, 'jason', 18, 'male')
# print(stu1.__dict__)
# print(stu1.name)

stu1 = Student('jason', 18, 'male')
print(stu1)
stu2 = Student('kevin', 28, 'female')
print(stu2)

对象独有的功能

class Person:
   h_type = '人类'

   def __init__(self, name):  # 让对象拥有独有的数据
       self.name = name
   # 定义在类中的函数 我们称之为方法
   def eat(self):  # 是多个对象公共的方法 也算多个对象独有的方法 对象来调用就会将对象当做第一个参数传入
       print('%s正在干饭'%self.name)

   def others(self,a,b):
       print('others哈哈哈')

'''
针对对象独有的方法 我们无法真正实现
  1.如果在全局则不是独有的
  2.如果在类中则是公共的
python解释器针对上述问题添加了一个非常牛的特性
  定义在类中的函数默认是绑定给对象的(相当于是对象独有的方法)
'''
# p1 = Person('jason')
# p1.eat() # eat(p1)
# p2 = Person('kevin')
# p2.eat() # eat(p2)
# 如何理解绑定二字
# p3 = Person('oscar')
# Person.eat(p3)

p1 = Person('jason')
p1.others(1, 2)
Person.others(p1,1,2)

动静态方法

专门针对在类体代码中编写的函数
1.绑定给对象的方法
直接在类体代码中编写即可
  对象调用会自动将对象当做第一个参数传入
类调用则有几个形参就传几个实参
2.绑定给类的方法
3.静态方法(普普通通的函数)

class Student:
   school = '清华大学'

   # 绑定给对象的方法
   def run(self):  # self用于接收对象
       print('老六赶紧跑!!!', self)

   @classmethod  # 绑定给类的方法
   def eat(cls):  # cls用于接收类
       print('老六你可真有才', cls)

   @staticmethod  # 静态方法
   def sleep(a, b):  # 无论谁来调用都必须按照普普通通的函数传参方式
       print('老六快去睡觉吧')


stu1 = Student()
# 调用绑定给类的方法
# Student.eat() # 类调用会自动将类当做第一个参数传入   eat(Student)
# stu1.eat() # 对象调用会自动将产生该对象的类当做第一个参数传入 eat(Student)
# 调用静态方法
# Student.sleep(1,2)
stu1.sleep(1, 2)

面向对象三大特性之继承

"""
面向对象三大特性分别是
继承、封装、多态
"""
1.继承的含义
在现实生活中继承其实就是用来描述人与人之间资源的关系
eg:儿子继承父亲的财产(拥有了父亲所有的资源)
在编程世界里继承其实就是用来描述类与类之间数据的关系
  eg:类A继承类B(拥有了类B里面所有的数据和功能)
2.继承的目的
现实生活中继承就是想占有别人的财产
eg:亲身父亲 干爹 干妈 富婆
编程世界里继承就是为了节省代码编写
  eg:可以继承一个类 也可以继承多个类
3.继承的操作
class 类名(类名):
       pass
1.定义类的时候在类名后加括号
2.括号内填写你需要继承的类名
3.括号内可以填写多个父类 逗号隔开即可
"""
我们将被继承的类称之为: 父类或基类或超类
我们将继承类的类称之为: 子类或派生类
ps:平时最常用的就是父类和子类
"""
class MyClass(F1,F2,F3):
   pass
ps:目前掌握从左到右查找每个父类中的属性即可

继承的本质

抽象:将多个类共同的数据或功能抽取出来形成一个基类
继承:从上往下白嫖各个基类里面的资源
"""
对象:数据和功能的结合体
类:多个对象相同的数据和功能的结合体
父类:多个类相同的数据和功能的结合体
ps:类和父类最主要的功能其实就是节省代码
"""
一定要掌握继承的本质 这样以后你才会在代码中自己定义出子类父类

名字的查找顺序

1.不继承的情况下名字的查找顺序
先从对象自身查找 没有的话 再去产生该对象的类中查找
       class Student:
           school = '清华大学'
           def choice_course(self):
               print('正在选课')
       stu1 = Student()
       print(stu1.school)  # 对象查找school 自身名称空间没有 所以查找的是类的 清华大学
       stu1.school = '北京大学'  # 在自身的名称空间中产生了新的school
       """对象点名字并写了赋值符号和数据值 那么操作的肯定是自己的名称空间"""
       print(stu1.school)  # 北京大学
       print(Student.school)  # 清华大学
对象 >>>

2.单继承的情况下名字的查找顺序
先从对象自身查找 然后是产生该对象的类 然后是一个个父类
   class A:
       # name = 'from A'
       pass
   class B(A):
       # name = 'from B'
       pass
   class C(B):
       # name = 'from C'
       pass
   class MyClass(C):
       # name = 'from MyClass'
       pass
   obj = MyClass()
   # obj.name = '我很困!!!'
   print(obj.name)
对象 >>> >>> 父类...
class A1:
   def func1(self):
       print('from A1 func1')
   def func2(self):
       print('from A1 func2')
       self.func1()  # obj.func1()
   class MyClass(A1):
       def func1(self):
           print('from MyClass func1')
   obj = MyClass()
   obj.func2()
'''只要涉及到对象查找名字 几乎要回到最开始的位置依次查找'''
3.多继承的情况下名字的查找顺序
非菱形继承(最后不会归总到一个我们自定义类上)
  深度优先(每个分支都走到底 再切换)
菱形继承(最后归总到一个我们自定义类上)
  广度优先(前面几个分支都不会走最后一个类 最后一个分支才会走)
ps:结合群内截图理解即可
也可以使用类点mro()方法查看该类产生的对象名字的查找顺序
######################################################
主要涉及到对象查找名字 那么几乎都是
对象自身   类   父类
######################################################

经典类与新式类

经典类
不继承object或其子类的类(什么都不继承)
新式类
继承了object或其子类的类
"""
在python3中所有的类默认都会继承object
也就意味着python3里面只有新式类
在python2中有经典类和新式类
由于经典类没有核心的功能 所以到了python3直接砍掉了

以后我们在定义类的时候 如果没有想要继承的父类 一般推荐以下写法
  class MyClass(object):
      pass
目的是为了兼容python2
"""
以后写代码针对object无需关心 知道它的存在即可

派生方法

子类中定义类与父类一模一样的方法并且扩展了该功能>>>:派生

面向对象三大特性之封装

封装其实就是将数据或者功能隐藏起来(包起来 装起来)
隐藏的目的不是让用户无法使用 而是给这些隐藏的数据开设特定的接口 让用户使用接口才可以去使用 我们在接口中添加一些额外的操作


1.在类定义阶段使用双下划线开头的名字 都是隐藏的属性
后续类和对象都无法直接获取
2.在python中不会真正的限制任何代码
隐藏的属性如果真的需要访问 也可以 只不过需要做变形处理
  __变量名 _类名__变量名
ps:既然隐藏了 就不改使用变形之后的名字去访问 这样就失去了隐藏的意义
   
class Student(object):
   __school = '清华大学'
   def __init__(self, name, age):
       self.__name = name
       self.__age = age
   # 专门开设一个访问学生数据的通道(接口)
   def check_info(self):
       print("""
      学生姓名:%s
      学生年龄:%s
      """ % (self.__name, self.__age))
   # 专门开设一个修改学生数据的通道(接口)
   def set_info(self,name,age):
       if len(name) == 0:
           print('用户名不能为空')
           return
       if not isinstance(age,int):
           print('年龄必须是数字')
           return
       self.__name = name
       self.__age = age

stu1 = Student('jason', 18)
stu1.set_info('','我很大')
"""
我们编写python很多时候都是大家墨守成规的东西 不需要真正的限制
class A:
_school = '清华大学'
def _choice_course(self):
pass
"""

property伪装属性

可以简单的理解为 将方法伪装成数据
obj.name   # 数据只需要点名字
obj.func()  # 方法至少还要加括号
伪装之后可以将func方法伪装成数据 obj.func

扩展了解
体质指数(BMI)=体重(kg)÷身高^2(m)


# class Person:
#     def __init__(self, name, weight, height):
#         self.name = name
#         self.weight = weight
#         self.height = height
#
#     @property
#     def BMI(self):
#         return self.weight / (self.height ** 2)


# p1 = Person('jason', 78, 1.83)
# res = p1.BMI()
# print(res)
# p2 = Person('悍匪', 72, 1.73)
# res = p2.BMI()
# print(res)
"""BMI虽然需要计算获得 但是更像是人的数据"""


# p1 = Person('jason', 78, 1.83)
# print(p1.BMI)
# print(p1.name)


class Foo:
   def __init__(self, val):
       self.__NAME = val  # 将属性隐藏起来

   @property
   def name(self):
       return self.__NAME

   @name.setter
   def name(self, value):
       if not isinstance(value, str):  # 在设定值之前进行类型检查
           raise TypeError('%s must be str' % value)
       self.__NAME = value  # 通过类型检查后,将值value存放到真实的位置self.__NAME

   @name.deleter
   def name(self):
       raise PermissionError('Can not delete')


obj = Foo('jason')
# print(obj.name)
# obj.name = 666
# print(obj.name)
del obj.name

面向对象三大特性之多态

多态:一种事物的多种形态
水:液态 气态 固态
动物:
       
class Animal(object):
   def spark(self):
       pass


class Cat(Animal):
   def spark(self):
       print('喵喵喵')


class Dog(Animal):
   def spark(self):
       print('汪汪汪')


class Pig(Animal):
   def spark(self):
       print('哼哼哼')


# c1 = Cat()
# d1 = Dog()
# p1 = Pig()
# c1.miao()
# d1.wang()
# p1.heng()
"""
一种事物有多种形态 但是相同的功能应该有相同的名字
这样的话 以后我无论拿到哪个具体的动物 都不需要管到底是谁 直接调用相同的功能即可
  无论你是鸡 鸭 猫 狗 猪 只要你想叫 你就调固定的叫的功能
"""
# c1.spark()
# d1.spark()
# p1.spark()

"""
其实上述多态的概念 我们很早之前就已经解除过
"""
# l1 = [11, 22, 33, 44]
# d1 = {'name': 'jason', 'pwd': 123, 'hobby': 'raed'}
# t1 = (11, 22, 33, 44)
# print(len(l1))
# print(len(d1))
# print(len(t1))


"""
python也提供了一种强制性的操作(了解即可) 应该是自觉遵守
"""
# import abc
# # 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化
# class Animal(metaclass=abc.ABCMeta):
#     @abc.abstractmethod # 该装饰器限制子类必须定义有一个名为talk的方法
#     def talk(self): # 抽象方法中无需实现具体的功能
#         pass
# class Person(Animal): # 但凡继承Animal的子类都必须遵循Animal规定的标准
#     def talk(self):
#         pass
#     def run(self):
#         pass
# obj = Person()

"""
鸭子类型
  只要你长得像鸭子 走路像鸭子 说话像鸭子 那么你就是鸭子
"""
# class Teacher:
#     def run(self):pass
#     def eat(self):pass
# class Student:
#     def run(self):pass
#     def eat(self):pass


"""
操作系统
  linux系统:一切皆文件
      只要你能读数据 能写数据 那么你就是文件
          内存
          硬盘
      class Txt: #Txt类有两个与文件类型同名的方法,即read和write
          def read(self):
              pass
          def write(self):
              pass
       
      class Disk: #Disk类也有两个与文件类型同名的方法:read和write
          def read(self):
              pass
          def write(self):
              pass
       
      class Memory: #Memory类也有两个与文件类型同名的方法:read和write
          def read(self):
              pass
          def write(self):
              pass
  python:一切皆对象
      只要你有数据 有功能 那么你就是对象
          文件名         文件对象
          模块名         模块对象      
"""

面向对象之反射

反射:通过字符串来操作对象的数据或方法
   
反射主要就四个方法
hasattr():判断对象是否含有某个字符串对应的属性
getattr():获取对象字符串对应的属性
setattr():根据字符串给对象设置属性
delattr():根据字符串给对象删除属性
       
class Student:
   school = '清华大学'

   def choice_course(self):
       print('选课')
stu = Student()
# 需求:判断用户提供的名字在不在对象可以使用的范围内
# 方式1:利用异常处理(过于繁琐)
# try:
#     if stu.school:
#         print(f"True{stu.school}")
# except Exception:
#     print("没有属性")
"""
变量名school 与字符串school 区别大不大
  stu.school
  stu.'school'
两者虽然只差了引号 但是本质是完全不一样的
"""
# 方式2:获取用户输入的名字 然后判断该名字对象有没有
# while True:
#     target_name = input('请输入您想要核查的名字>>>:').strip()
#     '''上面的异常更加不好实现 需要用反射'''
#     # print(hasattr(stu, target_name))
#     # print(getattr(stu, target_name))
#     if hasattr(stu, target_name):
#         # print(getattr(stu, target_name))
#         res = getattr(stu, target_name)
#         if callable(res):
#             print('拿到的名字是一个函数', res())
#         else:
#             print('拿到的名字是一个数据', res)
#     else:
#         print('不好意思 您想要查找的名字 对象没有')
print(stu.__dict__)
stu.name = 'jason'
stu.age = 18
print(stu.__dict__)
setattr(stu,'gender','male')
setattr(stu,'hobby','read')
print(stu.__dict__)
del stu.name
print(stu.__dict__)
delattr(stu, 'age')
print(stu.__dict__)

"""
以后只要在需求中看到了关键字
....对象....字符串
那么肯定需要使用反射
"""

反射实战案例

class FtpServer:
   def serve_forever(self):
       while True:
           inp = input('input your cmd>>: ').strip()
           cmd, file = inp.split()
           if hasattr(self, cmd):  # 根据用户输入的cmd,判断对象self有无对应的方法属性
               func = getattr(self, cmd)  # 根据字符串cmd,获取对象self对应的方法属性
               func(file)
   def get(self, file):
       print('Downloading %s...' % file)

   def put(self, file):
       print('Uploading %s...' % file)
obj = FtpServer()
obj.serve_forever()

面向对象魔法方法

魔法方法其实就是类中定义的双下方法
之所以会叫魔法方法原因是这些方法都是到达某个条件自动触发 无需调用
eg: __init__方法在给对象设置独有数据的时候自动触发(实例化)

下列讲解的魔法方法都必须明确的知道的触发的条件
class MyClass(object):
   def __init__(self, name):
       """实例化对象的时候自动触发"""
       # print('__init__方法')
       # pass
       self.name = name
   def __str__(self):
       """
      对象被执行打印操作的时候会自动触发
          该方法必须返回一个字符串
          返回什么字符串打印对象之后就展示什么字符串
      """
       # print('__str__方法')
       # print('这是类:%s 产生的一个对象')
       # return '对象:%s'%self
       return '对象:%s'%self.name
   def __call__(self, *args, **kwargs):
       """对象加括号调用 自动触发该方法"""
       print('__call__方法')
       print(args)
       print(kwargs)
   def __getattr__(self, item):
       """当对象获取一个不存在的属性名 自动触发
          该方法返回什么 对象获取不存在的属性名就会得到什么
          形参item就是对象想要获取的不存在的属性名
      """
       print('__getattr__', item)
       return '您想要获取的属性名:%s不存在'%item
   def __setattr__(self, key, value):
       """对象操作属性值的时候自动触发>>>: 对象.属性名=属性值"""
       # print("__setattr__")
       # print(key)
       # print(value)
       super().__setattr__(key, value)
   def __del__(self):
       """对象在被删除(主动 被动)的时候自动触发"""
       # print('__del__')
       pass
   def __getattribute__(self, item):
       """对象获取属性的时候自动触发 无论这个属性存不存在
          当类中既有__getattr__又有__getattribute__的时候 只会走后者
      """
       # print('__getattribute__')
       # return super(MyClass, self).__getattribute__(item) 复杂写法
       return super().__getattribute__(item)  # 简便写法
   def __enter__(self):
       """对象被with语法执行的时候自动触发 该方法返回什么 as关键字后面的变量名就能得到什么"""
       print('__enter__')
   def __exit__(self, exc_type, exc_val, exc_tb):
       """对象被with语法执行并运行完with子代码之后 自动触发"""
       print('__exit__')

魔法方法笔试题

"""补全以下代码 执行之后不报错"""
class Context:
   def __enter__(self):
       return self
   def __exit__(self, exc_type, exc_val, exc_tb):
       pass
   def do_something(self):
       pass
with Context() as f:
   f.do_something()

元类简介

# s1 = '哈哈哈 今天下午终于可以敲代码了!!!'
# l2 = [60, 80, 100, 120, 150, 200]
# d = {'name': '死给我看', 'age': 18}
# print(type(s1)) # <class 'str'>
# print(type(l2)) # <class 'list'>
# print(type(d)) # <class 'dict'>
"""
基础阶段我们使用type来查找数据的数据类型
但是学了面向对象之后 发现查看的不是数据类型 而是数据所属的类

我们定义的数据类型 其实本质还是通过各个类产生了对象
  class str:
      pass
  h = 'hello' str('hello')

我们也可以理解为type用于查看产生当前对象的类是谁
"""
class MyClass:
   pass
obj = MyClass()
print(type(obj))  # 查看产生对象obj的类:<class '__main__.MyClass'>
print(type(MyClass))  # 查看产生对象MyClass的类:<class 'type'>
"""
通过上述推导 得出结论 自定义的类都是由type类产生的
我们将产生类的类称之为 '元类'
"""

产生类的两种方式

1.class关键字
class MyClass:
       pass

2.利用元类type
type(类名,类的父类,类的名称空间)

"""
学习元类其实就是掌握了类的产生过程 我们就可以在类的产生过程中高度定制化类的行为
eg:
类名必须首字母大写
上述需求就需要使用元类来控制类的产生过程 在过程中校验
"""

元类基本使用

class MyMetaClass(type):
   pass
"""只有继承了type的类才可以称之为是元类"""
class MyClass(metaclass=MyMetaClass):
   pass
"""如果想要切换产生类的元类不能使用继承 必须使用关键字metaclass声明"""

'''
思考
类中的__init__用于实例化对象
元类中__init__用于实例化类
'''

class MyMetaClass(type):
   def __init__(self,what, bases=None, dict=None):
       # print('别晕')
       # print('what', what) 类名
       # print('bases', bases) 类的父类
       # print('dict', dict) 类的名称空间
       if not what.istitle():
           # print('首字母必须大写 你会不会写python 面向对象学过吗 lowB')
           raise Exception('首字母必须大写 你会不会写python 面向对象学过吗 lowB')
       super().__init__(what, bases, dict)
"""只有继承了type的类才可以称之为是元类"""


# class Myclass(metaclass=MyMetaClass):
#     pass


"""如果想要切换产生类的元类不能使用继承 必须使用关键字metaclass声明"""
class aaa(metaclass=MyMetaClass):
   pass

元类进阶

"""元类不单单可以控制类的产生过程 其实也可以控制对象的!!!"""
1.对象加括号执行产生该对象类里面的双下call
2.类加括号执行产生该类的元类里面的双下call
class MyMetaClass(type):
   def __call__(self, *args, **kwargs):
       print('__call__')
       if args:
           raise Exception('必须用关键字参数传参')
       super().__call__(*args, **kwargs)


class MyClass(metaclass=MyMetaClass):
   def __init__(self, name, age):
       self.name = name
       self.age = age
       print('__init__')


# 需求:实例化对象 所有的参数都必须采用关键字参数的形式
obj = MyClass('jason', 18)
# obj = MyClass(name='jason', age=18)


总结
"""
如果我们想高度定制对象的产生过程
可以操作元类里面的__call__
如果我们想高度定制类的产生过程
可以操作元类里面的__init__
"""

作业

1.自行下载好第三方模块
openpyxl、requests、pandas
 尝试着使用上述模块及内置模块re爬取链家二手房数据并持久化到表格中
 https://sh.lianjia.com/ershoufang/
 基础要求:单页
 拔高要求:多页
2.使用random模块编写一个可以产生任意位数随机验证码的代码
eg: 假设是五位 每一位都可以是大写字母或小写字母或数字
3.查阅json模块的主要功能并完成下列需求
import datetime
 import json
 d = {
      't1': datetime.datetime.today(),
      't2': datetime.date.today()
}
 如何序列化上述的字典,有几种方式 如果采用面向对象的派生如何编写
4.配置文件信息过滤
配置文件中一般配置名称都是纯大写 如何写代码将一个py文件中所有的大写配置打印或者保存起来忽略掉其他非纯大写的配置
eg:  HOST = '127.0.0.1' 有效配置
        port = 3306 无效配置
 ps:一切皆对象 dir()可以获取对象可以调用的名字
5.编写一个类模拟操作系统终端的命令
eg: dir  ls  ipconfig
 如何获取用户输入的命令并判断类中是否含有该命令功能 如果有如何自动执行
6.补全下列代码使其不报错
 class Context:
     pass
 with Context() as f:
     f.do_something()
7.购物车大作业
直接参考b站讲解视频(课上不讲解)
8.ATM大作业
直接参考b站讲解视频(课上不讲解)
https://space.bilibili.com/481479179/?spm_id_from=333.999.0.0
posted @ 2022-08-27 17:31  呼长喜  阅读(12)  评论(0编辑  收藏  举报