Python视频学习(二、Python面向对象)

感谢传至辛苦奉献的老师

目录

  • 0. `dir`函数查看对象的方法/属性
  • 1. OOP基本语法
      • 1. `self`参数
      • 2. `__init__`
      • 3. `__del__`
      • 4. `__str__`
  • 2. 面向对象封装
      • 1. 身份运算符`is`
      • 2. 私有属性和方法
  • 3. 继承
      • 1. 重写父类方法
      • 2. 父类的私有属性/方法
      • 3. 多继承
        • a. Python 对象的 `__mro__`
      • 4. 新式类和经典类
        • python2和python3中的类区别:
  • 4. 多态
  • 5. 类属性和类方法
    • 5.1 类是特殊的对象
    • 5.2 属性的获取机制
    • 5.3 类方法
    • 5.4 静态方法
  • 6. 单例
    • 6.1 使用 `__new__`方法
      • a. 对象创建过程
      • b. 重写`__new__`方法
        • `__new__`不返回对象的结果
        • `__new__`返回对象的结果
    • 6.2 设计单例
    • 6.3 只初始化一次
  • 7. 异常
    • 7.1 完整语法
    • 7.2 异常的传递
    • 7.3 抛出异常`raise`
  • 8. 模块和包
    • 8.1 模块导入
      • 1. import 导入
      • 2. 模块重命名
      • 3. from...import 导入
      • 4. 使用`as`来解决冲突
        • 5. from .. import *
    • 8.2 模块搜索顺序——sys.path, `__file__`
    • 8.3 模块的`__name__`
      • python文件结构:
    • 8.4 包(package)
      • 1. from.. import
      • 2. import
    • 8.5 发布自己的包
      • 1. 制作压缩包
      • 2. 安装模块
      • 3. 删除模块
      • 4. 使用pip安装第三方模块
  • 9. 文件
    • 9.1 访问方式
    • 9.2 OS模块的操作
      • 文件操作
      • 目录操作
  • 10. 编码
    • Python2中使用中文:
    • Python2中使用中文字符串遍历:
  • 11. `eval`

0. dir函数查看对象的方法/属性

1. OOP基本语法

面向对象三大特性

  1. 封装 根据 职责 将 属性 和 方法 封装 到一个抽象的 类 中
  2. 继承 实现代码的重用,相同的代码不需要重复的编写
  3. 多态 不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度
def Person(object):
	pass
  • 使用 print 输出 对象变量,默认情况下,是能够输出这个变量 引用的对象 是 由哪一个类创建的对象,以及 在内存中的地址(十六进制表示)

1. self参数

  • 在 类的外部,通过 变量名. 访问对象的 属性和方法
  • 在 类封装的方法中,通过 self. 访问对象的 属性和方法

由 哪一个对象 调用的方法,方法内的 self 就是 哪一个对象的引用

2. __init__

当使用 类名() 创建对象时,会 自动 执行以下操作:

  1. 为对象在内存中 分配空间 —— 创建对象
  2. 为对象的属性 设置初始值 —— 初始化方法(init)

3. __del__

  1. 当使用 类名() 创建对象时,为对象 分配完空间后,自动 调用 init 方法
  2. 当一个 对象被从内存中销毁 前,会 自动 调用 del 方法
class Cat(object):
    def __init__(self, new_name):
        self.name = new_name
        print("%s 来了" % self.name)

    def __del__(self):
        print("%s 去了" % self.name)

# tom 是一个全局变量
tom = Cat("Tom")
print(tom.name)

# del 关键字可以删除一个对象
del tom

print("-" * 50)

4. __str__

2. 面向对象封装

1. 身份运算符is

  • is 用于判断 两个变量 引用对象是否为同一个
  • == 用于判断 引用变量的值 是否相等

在 Python 中针对 None 比较时,建议使用 is 判断

2. 私有属性和方法

  1. 在属性/方法前加上一个_
  2. 在属性/方法前加上两个_, 这种方式实际是对 名称 做了一些特殊处理,使得外界无法访问到处理方式:在 名称 前面加上 _类名 => _类名__名称

3. 继承

class 类名(父类名):
    pass

1. 重写父类方法

# 方法1——推荐使用
super().父类方法()  
# 方法2
父类名.方法(self)

在Python中, super是一个特殊的类,super()就是创建这个类的对象,最常使用的场景就是重写父类方法的时候。在python2的早期没有super,所以只能使用上面的方法2来调用。

推荐使用方法一,因为如果父类名更改了,得重新修改子类代码

2. 父类的私有属性/方法

  • 子类对象 不能 在自己的方法内部,直接 访问 父类的 私有属性 或 私有方法
  • 子类对象 可以通过 父类 的 公有方法 间接 访问到 私有属性 或 私有方法
  • 私有属性、方法 是对象的隐私,不对外公开,外界 以及 子类 都不能直接访问

3. 多继承

class C(A, B):
	pass

在多继承的情况下,调用super().__init__(),只调用一个父类的初始化方法

class A(object):
	def __init__(self):
		print("A")

class B(object):
	def __init__(self):
		print("B")
class C(A, B):
	def __init__(self):
		super().__init__()
		print("C")

c = C()
# A
# C

提示:开发时,应该尽量避免这种容易产生混淆的情况! —— 如果 父类之间 存在 同名的属性或者方法,应该 尽量避免 使用多继承

a. Python 对象的 __mro__

Python 中针对 类 提供了一个 内置属性 mro 可以查看 方法 搜索顺序, MRO 是 method resolution order,主要用于 在多继承时判断 方法、属性 的调用 路径

print(类名.__mro__)
# (, , , )

4. 新式类和经典类

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

  • 新式类:以 object 为基类的类,推荐使用
  • 经典类:不以 object 为基类的类,不推荐使用
class A(object):
	"""新式类"""
	pass
	
class B:
	"""经典类,python2中才有"""
	pass

python2和python3中的类区别:

  • 在 Python 3.x 中定义类时,如果没有指定父类,会 默认使用 object 作为该类的 基类 —— Python 3.x 中定义的类都是 新式类
  • 在 Python 2.x 中定义类时,如果没有指定父类,则不会以 object 作为 基类

python2中如果不指定继承自object类,则是经典类,它里面没有关于object类的__XXX__方法

新式类 和 经典类 在多继承时 —— 会影响到方法的搜索顺序

class FatherA(object):
	def demo(self):
		print("A father")

class A(FatherA):
	pass

class FatherB(object):
	def demo(self):
		print("B father")

class B(FatherB):
	pass

class C(A, B):
	pass

c = C()
c.demo()   # A father
# 搜索顺序先搜索A,找不到再去搜索A的祖先,而不是直接搜索B

4. 多态

面向对象三大特性

封装 :根据 职责 将 属性 和 方法 封装 到一个抽象的 类 中
定义类的准则
继承: 实现代码的重用,相同的代码不需要重复的编写
设计类的技巧
子类针对自己特有的需求,编写特定的代码
多态 不同的 子类对象 调用相同的 父类方法,产生不同的执行结果
  1. 多态 可以 增加代码的灵活度
  1. 以 继承 和 重写父类方法 为前提
  1. 是调用方法的技巧,不会影响到类的内部设计

5. 类属性和类方法

通过类名()创建对象的时候,有2个步骤:

  1. 在内存中为对象 分配空间——使用__new__
  2. 调用初始化方法 __init__ 为 对象初始化

多个对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用(self)传递到方法内部

5.1 类是特殊的对象

在Python中,类是一个特殊的对象:class AAA:定义的类属于 类对象。在程序运行时,类 同样 会被加载到内存, 类对象 在内存中 只有一份。

除了封装 实例 的 属性 和 方法外,类对象 还可以拥有自己的 属性 和 方法

5.2 属性的获取机制

在Python中,对类属性存在一种向上查找机制

Python视频学习(二、Python面向对象)_第1张图片

获取属性值:

  1. 类名.类属性
  2. 对象.类属性 (不推荐)

注意使用对象.类属性赋值时的情况

5.3 类方法

在 类方法 内部可以直接访问类属性 或者调用其他的 类方法

@classmethod
def 类方法名(cls):
    pass

类名.类方法()

python会自动将类本身作为第一个参数,传递给cls

5.4 静态方法

在开发时,如果需要在 类 中封装一个方法,这个方法:

  • 不需要 访问 实例属性 或者调用 实例方法
  • 不需要 访问 类属性 或者调用 类方法
@staticmethod
def 静态方法名():
    pass

类名.方法名()

6. 单例

6.1 使用 __new__方法

__new__方法作用:

  1. 内存中为对象分配空间
  2. 返回对象引用

a. 对象创建过程

  1. 对象调用__new__这个静态方法,为对象分配空间,将这个引用返回
  2. 拿到这个对象引用,将其传递给__init__方法进行初始化
  3. 返回这个对象

Python视频学习(二、Python面向对象)_第2张图片

b. 重写__new__方法

调用语法为:

def __new__(cls, *args, **kwargs):

	return super().__new__(cls)   # 该方法为静态方法,要手动传递 cls

__new__不返回对象的结果

def Person(object):
	def __new__(cls, *args, **kwargs):
		print("创建")

	def __init__(self):
		print("初始化")

p = Person()   # 打印 “创建”
print(p)		# 打印 None
# 因为 __new__没有返回对象,所以没有调用__init__

__new__返回对象的结果

使用父类(object)自身的__new__方法来分配空间

def Person(object):
	def __new__(cls, *args, **kwargs):
		print("创建")
		# 分配空间
		instance = super().__new__(cls)   # 因为__new__是静态对象,所以cls要主动传递
		# 返回对象
		return instance
		

	def __init__(self):
		print("初始化")

p = Person()   # 打印 “创建” 、初始化
print(p)       # 打印地址

6.2 设计单例

class Person(object):
	singleton = None

	def __new__(cls, *args, **kwargs):
		if cls.singleton is None:
			cls.singleton = super().__new__(cls)
		return singleton

6.3 只初始化一次

虽然单例之后,对象的引用都是一个,但是这个引用每次都会在之后传递给__init__方法,这里就是使得__init__方法也只执行一次。

方法:使用flag

class Person(object):
	singleton = None
	init_flag = False
	
	def __new__(cls, *args, **kwargs):
		if cls.singleton is None:
			cls.singleton = super().__new__(cls)
		return singleton

	def __init__(self):
		if Person.init_flag:   # 已初始化,直接返回
			return
		print("初始化")
		Person.init_flag = True

注意,在Python中访问类属性的时候,记住 使用类名/cls.属性名,不要漏了,这样就变成了局部变量

7. 异常

程序开发时,很难将 所有的特殊情况 都处理的面面俱到,通过 异常捕获 可以针对突发事件做集中的处理,从而保证程序的 稳定性和健壮性

7.1 完整语法

try:
    # 尝试执行的代码
    pass
except 错误类型1:
    # 针对错误类型1,对应的代码处理
    pass
except 错误类型2:
    # 针对错误类型2,对应的代码处理
    pass
except (错误类型3, 错误类型4):
    # 针对错误类型3 和 4,对应的代码处理
    pass
except Exception as result:
    # 打印错误信息
    print(result)
else:
    # 没有异常才会执行的代码
    pass
finally:
    # 无论是否有异常,都会执行的代码
    print("无论是否有异常,都会执行的代码")

7.2 异常的传递

异常的传递 —— 当 函数/方法 执行 出现异常,会 将异常传递 给 函数/方法 的 调用一方(上抛),直到主程序

7.3 抛出异常raise

可以根据特定业务逻辑抛出自己的异常

  1. 创建Exception对象
  2. 使用raise抛出对象
raise Exception("这有问题")

# 捕获并且打印信息
except Exception as result:
	print(result):    # 这有问题

8. 模块和包

  • 每一个python文件就是一个模块,模块中的全局变量,函数,类可以被外部来使用。
  • 模块名也应该符合标识符命名规则

8.1 模块导入

1. import 导入

import 模块名1, 模块名2     # 可以,但是不符合标准

import 模块名1
import 模块名2 

2. 模块重命名

模块别名应该使用 大驼峰命名法

import 模块名1 as 模块别名

3. from…import 导入

如果希望 从某一个模块 中,导入 部分 工具,就可以使用 from … import 的方式

from 模块名1 import 工具名1,工具名2

注意使用from导入的,被导入的对象直接在主模块中存在; 而使用import引入的,成员还是在对应的模块中

4. 使用as来解决冲突

注意: 这种方式导入,如果存在同名成员,则后导入的会覆盖先导入的:

from module1 import test
from module2 import test

test()  # 执行模块2的

解决冲突:

from module1 import test
from module2 import test as test2

test()   # 执行模块1
test2()   #执行模块2

5. from … import *

8.2 模块搜索顺序——sys.path, __file__

会先在当前目录下查看,所以自己写的模块应该避免跟系统模块重名。

  1. sys.path会打印模块的搜索路径
  2. 很多模块有一个__file__,被引入后可以查看这个,它表示该模块的文件位置

8.3 模块的__name__

原则 —— 每一个文件都应该是可以被导入的

在导入文件时,文件中 所有没有任何缩进的代码 都会被执行一遍!

因为模块内部代码写的时候,可能还附带有测试代码,所以为了能够区分测试和使用,模块的__name__就派上用场了:

  1. __name__本身是字符串
  2. 如果 是被其他文件导入的,__name__ 就是 模块名
  3. 如果 是当前执行的程序 __name____main__

python文件结构:

# 导入模块
# 定义全局变量
# 定义类
# 定义函数

# 在代码的最下方
def main():
    # ...
    pass

# 根据 __name__ 判断是否执行下方代码
if __name__ == "__main__":
    main()

8.4 包(package)

  • 包 是一个 包含多个模块 的 特殊目录
  • 目录下有一个 特殊的文件 __init__.py
  • 包名的 命名方式 和变量名一致,小写字母 + _

可以从包导入模块:

mypack
-----module1
-----module2

1. from… import

from mypack import module1, module2
module1.func()
module2.func()

2. import

要现在__init__.py中添加能被外界访问的模块:

# __init__.py中,添加从 当前目录 导入 模块列表
from . import module1    # .代表当前文件夹
from . import module2

# 外部文件中:
import mypack
mypack.module1.func()
mypack.module2.func()

8.5 发布自己的包

1. 制作压缩包

1.制作发布压缩包
在和包相同文件夹下,创建 setup.py文件,里面输入这些代码:
from distutils.core import setup

setup(name="名称",  # 包名
      version="1.0",  # 版本
      description="描述",  # 描述信息
      long_description="长描述信息",  # 完整描述信息
      author="作者",  # 作者
      author_email="邮箱",  # 作者邮箱
      url="网址",  # 主页
      py_modules=["包名.模块名1",
                  "包名.模块名2"])

2.构建模块
$ python3 setup.py build
3.生成发布压缩包
$ python3 setup.py sdist
# 就会生成  .tar.gz文件,可以拷贝给别人使用

要制作哪个版本的模块,就使用哪个版本的解释器执行!

2. 安装模块

$ tar -zxvf hm_message-1.0.tar.gz 

# 进入这个压缩后的文件夹,运行:
$ sudo python3 setup.py install
# 模块就会被安装到 dist-packages

3. 删除模块

直接在dist-packages中删除对应的文件夹就可以了

4. 使用pip安装第三方模块

# 将模块安装到 Python 2.x 环境
$ sudo pip install pygame
$ sudo pip uninstall pygame

# 将模块安装到 Python 3.x 环境
$ sudo pip3 install pygame
$ sudo pip3 uninstall pygame

# Linux 下安装,是通过不同的名称来区分的
$ sudo apt install ipython
$ sudo apt install ipython3

9. 文件

9.1 访问方式

访问方式 说明
r 以只读方式打开文件。文件的指针将会放在文件的开头,这是默认模式。如果文件不存在,抛
w 以只写方式打开文件。如果文件存在会被覆盖。如果文件不存在,创建新文件
a 以追加方式打开文件。如果该文件已存在,文件指针将会放在文件的结尾。如果文件不存在,创建新文件进行写入
r+ 以读写方式打开文件。文件的指针将会放在文件的开头。如果文件不存在,抛出异常
w+ 以读写方式打开文件。如果文件存在会被覆盖。如果文件不存在,创建新文件
a+ 以读写方式打开文件。如果该文件已存在,文件指针将会放在文件的结尾。如果文件不存在,创建新文件进行写入
# 打开文件
file = open("README")

while True:
    # 读取一行内容
    text = file.readline()

    # 判断是否读到内容
    if not text:
        break

    # 每读取一行的末尾已经有了一个 `\n`
    print(text, end="")

# 关闭文件
file.close()

9.2 OS模块的操作

文件操作

序号 方法名 说明 示例
01 rename 重命名文件 os.rename(源文件名, 目标文件名)
02 remove 删除文件 os.remove(文件名)

目录操作

序号 方法名 说明 示例
01 listdir 目录列表 os.listdir(目录名)
02 mkdir 创建目录 os.mkdir(目录名)
03 rmdir 删除目录 os.rmdir(目录名)
04 getcwd 获取当前目录 os.getcwd()
05 chdir 修改工作目录 os.chdir(目标目录)
06 path.isdir 判断是否是文件 os.path.isdir(文件路径)

10. 编码

Python2中使用中文:

在文件开头加上下面这个,解释器就会以 utf-8来处理文件

# *-* coding:utf8 *-*
# 或者
# coding=utf8

Python2中使用中文字符串遍历:

在 Python 2.x 中,即使指定了文件使用 UTF-8 的编码格式,但是在遍历字符串时,仍然会 以字节为单位遍历 字符串。要能够 正确的遍历字符串,在定义字符串时,需要 在字符串的引号前,增加一个小写字母 u,告诉解释器这是一个 unicode 字符串(使用 UTF-8 编码格式的字符串)

# *-* coding:utf8 *-*

# 在字符串前,增加一个 `u` 表示这个字符串是一个 utf8 字符串
hello_str = u"你好世界"

print(hello_str)

for c in hello_str:
    print(c)

11. eval

将字符串当作代码操作,返回结果:

# 基本的数学计算
In [1]: eval("1 + 1")
Out[1]: 2

# 字符串重复
In [2]: eval("'*' * 10")
Out[2]: '**********'

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

# 将字符串转换成字典
In [4]: type(eval("{'name': 'xiaoming', 'age': 18}"))
Out[4]: dict

小心攻击:

__import__('os').system('ls')   

# 这条语句相当于
import os
os.system("终端命令")

你可能感兴趣的:(Python)