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

感谢传至辛苦奉献的老师

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__)
# (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)

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中,对类属性存在一种向上查找机制

在这里插入图片描述

获取属性值:

  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. 返回这个对象

在这里插入图片描述

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模块的操作

文件操作

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

目录操作

序号方法名说明示例
01listdir目录列表os.listdir(目录名)
02mkdir创建目录os.mkdir(目录名)
03rmdir删除目录os.rmdir(目录名)
04getcwd获取当前目录os.getcwd()
05chdir修改工作目录os.chdir(目标目录)
06path.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("终端命令")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值