Python 进阶 - 面向对象

Python 面向对象

面向过程

  • 把完成某个需求的所有步骤,从头到尾逐步实现
  • 根据开发需求,将某些功能独立的代码封装成一个又一个函数
  • 最后完成的代码,就是顺序地调用不同的函数

面向过程特点:

  1. 注重步骤过程,不注重职责份工
  2. 如果需求复杂,代码很复杂
  3. 开发复杂项目,没有固定套路,开发难度大

面向对象特点

  1. 注重对象和职责,不同的对象承担不同的职责
  2. 更加适合对复杂需求变化,时专门应对复杂项目开发,提供的固定套路
  3. 需要在面向过程基础上,再学习一些面向对象语法

类,对象

  • 类是图纸,是模板
  • 对象是根据图纸制作的飞机

设计类满足的三要素:

  1. 类名
  2. 属性
  3. 方法

基础语法

目标

  • dir 内置函数
  • 定义简单的类(只包含方法)
  • 方法中的 self 参数
  • 初始化方法
  • 内置方法和属性

01 dir 内置函数

  • 提示: __方法名__格式的方法是 Python 提供的 内置方法/属性,.
方法名 类型 作用
__new__ 方法 创建对象时,会被自动调用
__init__ 方法 对象被初始化时,会被调用
__del__ 方法 对象被从内存中销毁前,会被调用
__str__ 方法 返回对象的描述信息, print 函数输出使用

例子

# coding=utf-8

class Demo:
    
	# 如果存在 __new__ 则接下来的函数都不会进行.
    def __init__(self,name):
        self.name = name
        print('init---%s :'% name)

    def __del__(self):
        print('del---')

    def __str__(self):
        return '123'

hehe = Demo('guagua')
print(hehe)
del hehe
"""
打印结果:
init---guagua :
123
del---
"""

1) __str__ 方法

  • 在 Python 中,使用 print 输出 对象变量,默认情况下,会输出这个变量 引用的对象由哪一个类创建的对象,以及在内存中的地址(十六进制表示)
  • 如果在开发中,希望使用 print 输出 对象变量 时,能够打印 自定义的内容,就可以利用 __str__ 这个内置方法了
  • __str__ 必须返回一个字符串

私有属性和私有方法

应用场景及定义方式

  • 私有属性就是对象不希望公开的属性
  • 私有方法就是对象不希望公开的方法

定义方式:

  • 在定义属性或方法时,在 属性名或者方法名前 增加 两个下划线,定义的就是 私有 属性或者方法
class Women:
    def __init__(self,name):
        self.name = name
        self.__age = 18 #私有属性
    
    #私有方法
    def __get_age():
        pass
    
	def secret(self):
        print('年龄是%s' % self.__age)
        
xiaofang = Women('小芳')
print(xiaofang.age)# 私有属性不可外部直接调用,报错
xiaofang.__get_age()# 私有方法不可外部直接调用,报错
xiaofang.secret()#

伪私有属性和私有方法

Python 中,并没有 真正意义私有,以上讲的私有其实都是伪私有

  • 在给属性、方法命名时,实际时对名称左了一些特殊处理,使得,外界无法访问到
  • 处理方式:在名称前面加上 _类名 => _类名__名称
  • 处理之后使得能够直接调用私有方法

提示:在日常开发中,不要使用这种方式去访问 定义好的 私有的属性和方法

经过改造的上面的例子:

print(xiaofang._Women__age)# 私有被直接调用
xiaofang.Women.__get_age()# 私有被直接调用

引用概念的强调

  • Python 使用类创建对象之后,tom 变量中记录的是 对象在内存中的地址。
  • 也就是 tom 变量引用了 新建的对象
  • 使用 print 输出对象变量,默认情况下,是能够输出这个变量 引用的对象 是 由哪一个类创建的对象,以及在内存中的地址(十六进制表示)

提示:在计算机中,通常使用 十六进制 表示 内存地址

  • %d 可以以 10进制 输出数字
  • %x 可以以 16进制 输出数字

<__main__.Cat object at 0xi9asdiaxxxxx>

tom = Cat()
addr = id(tom)
print("%x" % addr)
print("%d" % addr)

继承

  • 单继承
  • 多继承
  • 面向对象三大特性
    1. 封装 根据 职责 将 属性 和 方法 封装 到一个抽象的 类 中
    2. 继承 实现代码的重用,相同的代码不需要重复的编写
    3. 多态 不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活性

单继承

继承的概念、语法和特点

继承的概念:子类 拥有 父类 的所有 方法 和 属性

class 类名(父类名):
	...
  • 子类 是 父类的 继承类
  • 子类 是 父类的 派生类
  • 父类 是 子类的 基类
  • 子类 从 父类 派生
方法的重写
  • 当父类 的 方法实现不能满足子类需求时,可以对方法 进行重写 override

多继承

  • 子类 可以拥有 多个父类,并且具有 所有父类 的属性 和方法
class 子类名(父类名1,父类名2...)
	...

注意:

开发时候,应尽量避免不同父类 中存在 同名的方法,容易产生混淆。如果父类之间存在同名,尽量避免使用多继承。

Python 中的 MRO -- 发给发搜索顺序

  • Python 中针对 类提供了一个内置属性 __mro__ 可以查看 方法 搜索顺序
  • MRO 是 method resolution order ,主要用于 在多继承时判断 方法、属性的调用 路径
print(C.__mro__)
输出结果:
(<class '__main__.C'>,<class '__main__.A'>,<class '__main__.B'>,<class 'object'>)
  • 在搜索方法时,是按照 __mro__ 的输出结果从左至右的顺序查找的
  • 如果在当前类中 找到方法,就直接执行,不再搜索
  • 如果没有找到,就查找下一个类中是否有对应的方法,如果找到,就直接执行,不再搜索
  • 如故宫找到最后一个类,还没有找到方法,程序报错

新式类与旧式

object 是 Python 为所有对象提供的基类,提供有一些内置的属性和方法,可以使用 dir 函数查看

多态

  • 不同的 子类对象调用相同的父类方法,产生不同的执行结果
  • 增加代码灵活度
  • 继承 和 重写父类方法为前提
  • 时调用方法的技巧,不会影响到类的内部设计

类--特殊对象

  • 程序运行时,类 同样会被加载到内存
  • 在 Python 中,类 是一个图书的对象 -- 类对象
  • 在程序运行时,类对象(模板)在内存中 只有一份,一个类可以创建多个对象示例
  • 模板 创建 实例对象,类对象只有一个,实例对象可以创建多个

类属性和实例属性

  • 类属性就是给 类对象中 定义的熟悉感
  • 通常记录 与这个类相关的特征
  • 类属性 不会用于记录 具体对象的特征

属性的获取机制

  • 属性的获取存在一个向上查找机制

访问类属性有两种

  1. 类名.类属性
  2. 对象.类属性(不推荐)
class Tool(object):
	count = 0
    
    def __init__(self,name):
        self.name = name
        Tool.count += 1 #类属性
        
tool1 = Tool("a")
tool2 = Tool("b")
tool3 = Tool("c")

tool3.count == 99 #对象属性 赋值
print(tool3.count)# 打印99
print(Tool.count) # 打印3 tool1,tool2,tool3,
# 因为类声明,Python 解释器会为其在内存开辟空间,它也是个对象。--类对象。

类方法 和 静态方法

  • 针对 类对象 定义的方法
  • 类方法需要用 修饰器 @classmethod 来标识,告诉解释器这是一个类方法
  • 类方法的第一个参数 应该是 cls
    • 哪个类 调用的方法,方法内的 cls 就是 哪一个类的引用
    • 这个参数和 实例方法 的第一个参数是 self 类似
    • 使用其他名称也可以,不过习惯使用 cls
  • 通过 类名 调用 类方法,调用方法时,不需要传递 cls 参数
  • 在方法内部
    • 可以通过 cls,访问类的属性
    • 也可以通过 cls,调用其他的类方法
class Tool(object):
	count = 0
    
    @classmethod
    def show_tool_count(cls):
        print(cls.count)
    
    def __init__(self,name):
        self.name = name
        Tool.count += 1 #类属性
        
tool1 = Tool("a")
print(Tool.show_tool_count) # 打印 1
# 因为类声明,Python 解释器会为其在内存开辟空间,它也是个对象。--类对象。

静态方法

  • 在开发时,如果需要在 类 中封装一个方法,这个方法:
    • 既不需要访问实例属性,调用实例方法
    • 也不需要访问类属性,类方法
@staticmethod
def 静态方法名():
    pass
  • 静态方法 需要使用装饰器 @staticmethod 来标识,告诉解释器这是一个静态方法
  • 通过类名.直接调用静态方法,不需要创建 实例对象

案例:

  1. 设计一个 Game 类
  2. 属性
    • 定义一个类属性 top_score 记录游戏的 历史最高分
    • 定义一个实例属性 player_name 记录 当前游戏的玩家姓名
  3. 方法:
    • 静态方法 show_help 显示游戏帮助信息
    • 类方法 show_top_score 显示历史最高分
      • 类方法内部调用类属性 cls.top_score
    • 实例方法 start_game 开始当前玩家的游戏
      • self.player_name
    • __init__ 类设置名称:self.player_name = player_name

小结:

  • 实例方法 内部需要访问 实例属性
    • 实例方法 内部可以使用 类名.访问类属性
  • 类方法 内部只需要访问类属性
  • 静态方法 内部,不需要访问,实例属性类属性

单例

单例设计模式

  • 目的:为了让 创建的对象,在系统中 只有 唯一的一个实例
  • 每一次执行类名() 返回的对象,内存地址是相同的

__new__ 方法(内部方法)

  • 使用 类名() 创建对象时,Python 的解释器 首先 会调用 __new__ 方法为对象 分配空间
  • __new__ 是一个 由 object 基类提供的 内置的静态方法,主要作用有两个:
    • 在内存中为对象 分配空间
    • 返回 对象的引用

重写 __new__ 方法的代码非常固定

  • 重写 __new__ 一定要 return super().__new__(cls)
  • 否则 Python 的解释器 得不到 分配了空间的对象引用,就不会调用对象的初始化方法
  • 注意:__new__ 是一个静态方法,在调用时需要主动传递 cls 参数

重写例子

class MusicPlayer(object):
    
    def __new__(cls,*args,**kwargs):
        # 1.创建对象时,new 方法会被自动调用
        # 2.为对象分配空间
        instance = super().__new__(cls)
        # 3.返回对象的引用
        return instance
    
    def __init(self):
        print('初始化播放器')
        
player = MusicPlayer()
print(player)        

Python 中的单例

class MusicPlayer(object):
    #记录第一个被创建对象的引用
    instance = None
    #记录是否执行过初始化动作
    init_flag = False
    
    def __new__(cls,*args,**kwargs):
        if cls.instance is None:
        	cls.instance = super().__new__(cls)
        return cls.instance
    
    def __init__(self):
        # 1. 判断是否执行过初始化动作
        if MusicPlayer.init_flag:
            return
        # 2. 如果没有执行过开始初始化
        print("初始化播放器")
        # 3.修改类属性的标记
        MusicPlayer.init_flag = True
  
player1 = MusicPlayer()
player2 = MusicPlayer()

异常

  • 异常的概念
  • 捕获异常
  • 异常的传递
  • 自定义异常

01 异常的概念

  • 通过异常捕获 可以针对突发事件做集中的处理,从而保证程序的稳定性和健壮性

02 捕获异常

基本格式

try:
    尝试执行代码
except:
    出现错误处理

错误类型捕获:

try:
    pass
except 错误类型1:
    pass
except (错误类型2,错误类型3):
    pass
except Exception as result:
    print(位置错误)
  • 当Python 解释器 抛出异常时,最后一行错误信息的第一个单词,就是错误类型
  • 如果希望程序 无论出现任何错误,都不会因为 Python 解释器 抛出异常而被终止,可以再增加一个except (即最后一个Exception)

异常捕获完整语法

try:
    # 尝试执行的代码
except 错1:
    pass
except 错2:
    pass
except (错3,错4):
    pass
except Exception as result:
    print(result)
else:
    # 没有异常才会执行的代码
    pass
finally:
    print("无论是否有异常都会执行")

03 异常的传递

  • 异常的传递 -- 当 函数/方法 执行 出现异常,会 将异常传递 给 函数/方法 的调用以方
  • 如果 传递到主程序,仍然没有异常处理,程序才会被终止

提示:

  • 在开发中,可以在主函数中增加异常捕获
  • 而在主函数中调用的其他函数,只要出现异常都会传递到主函数进行异常捕获

04 抛出 raise 异常

  • 开发时,如果满足 特定业务需求时,希望抛出异常,可以:
    1. 创建一个 Exception 对象
    2. 使用 raise 关键字抛出
    3. 但不进行捕获由其他函数捕获异常
def input_password():
    
    pwd = input("请输入密码:")
    if len(pwd)>=8:
        returen pwd
    raise Exception("密码长度不够")#主动抛出异常
    
try:
    print(inptu_password())
except Exception as result:
    print(result)

模块

模块时 Python 程序架构的一个核心概念

  • 每一个扩展名 py 结尾的 Python 源码文件 都是一个模块
  • 模块名 同样时一个标识符,需要符合命名规则
  • 在模块中定义的 全局变量、函数、类 都是提供给外界直接使用的工具
  • 模块就好比是工具包。

01 导入模块的方式

1) import 导入

import 模块1,模块2 #不推荐使用,需要单独行导入
# 推荐
import 模块1
import 模块2

模块别名:需要符合大驼峰命名

import 模块名1 as 模块别名

2) 局部导入

  • 如果希望从某一个模块中,导入部分工具,就可以使用 from ... import 的方式
  • 导入后 不需要通过 模块名
  • 可以直接使用 模块提供的工具--全局变量、函数、类

注意:

  • 如果两个模块,存在同名函数,那么后导入的函数会覆盖先导入的。
  • 新增别名,可避免

导入全部工具模块

from 模块名1 import *

3) 模块的搜索顺序

  • 当前目录搜索指定模块,如果有就直接导入
  • 如果没有,再搜索系统目录

Python 中每一个模块都有一个内置属性 __file__ 可以查看模块完整路径

4) 原则

__name__

  • 如果直接执行模块__name__ (即不是外部使用该模块),永远是执行__main__
  • 是一个 Python 的内置熟属性,记录着一个字符串
  • 如果 是被其他文件导入的__name__ 就是模块名

所以:

if __name__ = "__main__":
    main() # 进行测试

包 (Package)

  • 包即目录,是一个包含多个模块的特殊目录
  • 目录下有一个特殊文件 __init__.py
    • __init__.py文件中 设置 from . import ....
    • 导入模块可直接导入模块

发布模块 (重要)

01 制作发布压缩包步骤

以 hm_message 为例:

1) 创建setup.py

from distutils.core import setup

setup(name="hm_message",# 包名
     version="1.0", #版本 
     description="描述信息", 
     long_description="完整的描述信息",
     author="作者",
     url="主页",
     py_modules=["hm_message.send_message",
                "hm_message.receive_message"])
     )

可以参阅官方网站:

https://docs.python.org/2/distutils/apiref.html

2) 构建模块

python3 setup.py build

3) 生成发布压缩包

python3 setup.py sdist

生成 hm_message-1.0.tar.gz

02 安装模块

tar -zxvf xxxxxx.tar.gz
sudo python3 setup.py install

03 卸载模块

直接到安装目录把模块目录删除即可

04 pip 安装 pygame 游戏开发模块

sudo apt install ipython3 # Linux 下安装 iPython
sudo pip install pygame

eval 函数

eval() 函数十分强大 -- 将字符串 当成 有效的表达式 来求值 并 返回计算结果

# 基本的数学计算
eval("1 + 1") # 结果: 2

# 字符串重复
eval("*" * 10)
输出 :**********

# 字符串转换成列表
type(eval("[1,2,3,4,5]"))
输出: list

# 将字符串转换成字典
type(eval("{'name':'xiaoming','age':18}"))
输出 dict

安全 - 不要滥用 eval (重要)

开发时千万不要使用 eval 直接转换 input 的结果

__import__('os').system('ls') # 会有安全问题,可以看到你的东西
等价于:
import os
os.system("终端命令")

直接操作文件有很大的安全问题

posted @ 2019-09-02 15:31  呱呱禅  阅读(182)  评论(0编辑  收藏  举报