Python入门(三)·面向对象·异常·模块·文件操作·序列化


这部分内容承接 Linux入门python入门(一)
Python入门(三)的内容,是我在python入门学习的第四篇笔记。
主要记录面向对象等方面的内容。

面向对象

面向对象技术简介

类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
方法:类中定义的函数。
类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
局部变量:定义在方法中的变量,只作用于当前实例的类。
实例变量:在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
实例化:创建一个类的实例,类的具体对象。
对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法
和其它编程语言相比,Python 在尽可能不增加新的语法和语义的情况下加入了类机制。
Python中的类提供了面向对象编程的所有基本功能:类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法,方法中可以调用基类中的同名方法

对象可以包含任意数量和类型的数据。

类是模板,对象是根据这个模板创建出来的。
类只能有一个,对象可以有多个,不同对象的属性可能不同。
类中定义了什么属性和方法,对象就有什么属性和方法,不可能多也不可能少。

类的设计·类的定义、属性、方法

类对象的内置操作函数

dir()内置函数
给该函数传入标识符/数据,就可以看到针对这一对象所有的属性和方法。
isinstance(obj, class)
用于判断对象是否属于某一类
hasattr(obj, attribute) getattr(obj, attribute) setattr(obj, attribute)
判断对象是否有某个属性,获取某个属性,设置某个属性

以__name__这种下划线作为开头和结尾的是python给对象提供的内置方法和属性。

类的定义、实例化、单继承、多继承、super函数

'''
	类的定义
	类的名使用大驼峰命名法,
	即每个单词的首字母大写,单词之间没有下划线
'''
# 定义一个类
class ClassName(BaseClassName):
	''' 括号内的基类名表示类继承的基类 
	子类(派生类 DerivedClassName)会继承父类
	(基类 BaseClassName)的属性和方法。
	BaseClassName(实例中的基类名)必须与派生类定义在一个作用域内。
	除了类,还可以用表达式,基类定义在另一个模块中时这一点非常有用: 
	class DerivedClassName(modname.BaseClassName):

	pythoN还有限地支持多继承形式,用逗号表达式给出多个基类名即可
	需要注意圆括号中父类的顺序,若是父类中有相同的方法名,
	而在子类使用时未指定,python从左至右搜索
	即方法在子类中未找到时,从左到右查找父类中是否包含方法。

	如果需要通过子类实例或子类调用父类方法就用super函数
	super() 函数是用于调用父类(超类)的一个方法。
	super() 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。
	'''
	pass 
	'''类名的确定:分析整个业务流程出现的名词通常就可以作为类名'''
	def __init__(self, arg1, arg2, ...):
		super().BaseClassName.__init__
		pass
		'''
		类有一个名为 __init__() 的特殊方法(构造方法)
		该方法在类实例化时会自动调用
		可以不重写这一方法,也可以用它来初始化实例变量
		
		类的方法定义和函数定义时的区别是:
		其必须传入第一个参数是其类的实例本身,一般用self表示。
		'''
	
# 实例化这个类
x = ClassName()
''' 实例化类是创建一个'''

类属性和方法

类的私有属性
__private_attrs:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时 self.__private_attrs。
类的方法
在类的内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self,且为第一个参数,self 代表的是类的实例。self 的名字并不是规定死的,也可以使用 this,但是最好还是按照约定使用 self。
类的私有方法
__private_method:两个下划线开头,声明该方法为私有方法,只能在类的内部调用 ,不能在类的外部调用。self.__private_methods。

类的专有方法:
这些方法只是设置了接口,还是需要自己实现,用于定制类

__init__ : 构造函数,在生成对象时调用
__del__ : 析构函数,释放对象时使用
__str__ : 返回结果必须是字符串,重写该方法可以改变print(实例)的输出
__repr__ : 打印,转换
__setitem__ : 按照索引赋值
__getitem__: 按照索引获取值
__delitem__:按照索引删除值
__getattr__:在没有找到指定的属性时会到该方法下搜索
__len__: 获得长度
__cmp__: 比较运算
__call__: 函数调用
__add__: 加运算
__sub__: 减运算
__mul__: 乘运算
__truediv__: 除运算
__mod__: 求余运算
__pow__: 乘方
__iter__:返回一个可迭代对象

运算符重载
Python同样支持运算符重载,我们可以对类的专有方法进行重载

封装

封装是面向对象编程的一大特性。
面向对象编程的第一步就是将属性和方法封装到一个类中
外界使用类创建对象,用对象调用方法
对象方法的细节都被封装在类中

多态

面向对象的三大特性:封装、多态、继承
多态:不同的子类对象调用相同的父类方法,产生不同的结果。

  1. 多态可以增加代码的灵活度
  2. 以继承和重写父类方法为前提
  3. 是调用方法的技巧,不会影响类的内部设计

类属性和实例属性

类属性就是给类对象定义的属性
通常用来记录与这个类相关的特征
类属性不会用来记录具体对象的特征

类属性定义在类方法外部,能被类名和实例名访问。
实例属性如果和类属性同名,会遮蔽掉类属性,实例属性被删除后,遮蔽解除。

类方法和静态方法

在类方法内部可以访问类对象或其他类方法
类方法需要用修饰器 @classmethod 来标识,告诉解释器这是一个类方法,类方法的第一个参数应该是 cls

  1. 由哪一类调用的类方法,cls就是哪一类的引用。
  2. 这个参数和实例方法的self参数类似
  3. 也可以使用其他标识符,但一半用cls表示
    通过 classname. 调用类方法时,不需要给出cls参数。
    在类方法内部,可以通过cls.访问类属性和其他类方法。

静态方法
如果需要在类内封装一个方法,这个方法及不需要访问实例属性也不需要访问类属性,既不需要调用实例方法叶也不需要调用类方法,这个方法就可以成为静态方法。
用修饰器 **@staticmethod**修饰
通过 classname. 调用静态方法

杂项

身份运算符 is 和 is not

is是判断两个标识符是不是引用同一个对象
类似id(x) == id(y)

MRO 查看方法搜索顺序

在多继承时可以通过python给类内置的属性__mro__查看方法搜索顺序。

私有属性和私有方法

在属性和方法名前增加2个下划线。
私有属性在外界不能被直接访问
要在外界调用私有属性和方法可以用
_ClassName__AttributeName()
即在方法前添加下划线和类名

类是一个特殊的对象

python运行时类同样会被加载到内存中
类对象在内存中只有一份,一个类可以创建多个实例对象
除了封装实例的属性和方法外,类对象还可以拥有自己的属性和方法。

单例设计模式

设计模式是前人工作总结的经验和方法,通常是对某一类问题的成熟解决方案。
单例设计模式:让类创建的对象在系统中只有唯一的一个实例。

'''__new__方法
__new__方法是object基类提供的内置静态方法,
在使用类名创建对象时,解释器会调用该方法为对象分配内存空间
该方法的作用
1.为对象分配内存空间
2.返回对象的引用
解释器获得该引用后传递给__init__参数

重写__new__方法时一定要
	return super().__new__(cls)
	
'''

面向对象高级编程

使用__slots__

python可以动态地给实例或类绑定属性和方法

def dynaticMethod(sefl, *arg):
	pass
	return 

class A(baseClass):
	pass
a = A()

from types import MethodType
# 给实例添加方法
a.dynaticMethod = MethodType(dynaticMethod, a)
# 然后就可以调用该方法
a.dynaticMethod()
# 但给实例绑定的方法只能在该实例使用

# 如果要在其他实例也使用,应该绑定到类方法上
A.dynaticMethod = dynaticMethod

但是如果我们需要限制实例的属性和方法添加呢?为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性
使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的
除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__

class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

使用@property

有没有既能检查参数,又可以用类似属性这样简单的方式来访问类的变量呢?
Python内置的@property装饰器就是负责把一个方法变成属性调用的
对一个属性名,常用一个getitem()和setitem()方法来访问和修改它

class Student(object):
	def __init__(self, score):
		self._score = score
	def score_get(self):
		return self._score
	def score_set(self, score):
		self._score = score

使用@property后可以简化为

class Student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

其中装饰器@score.setter是由@property产生的,用于装饰修改属性的方法,如果不使用该装饰器,则该属性变为只读的
要注意,由于@property装饰的方法可以被当作属性直接访问,要避免它指向的属性与该方法同名,否则会造成递归错误

多重继承

通过多重继承,一个子类就可以同时获得多个父类的所有功能。

使用枚举类

from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
# 这样我们就获得了Month类型的枚举类,可以直接使用Month.Jan来引用一个常量,或者枚举它的所有成员:
for name, member in Month.__members__.items():
    print(name, '=>', member, ',', member.value)
# value属性则是自动赋给成员的int常量,默认从1开始计数。

# 如果需要更精确地控制枚举类型,可以从Enum派生出自定义类:
@unique #该装饰器检查是否有重复值
class Gender(Enum):
	female = 0
	male = 1

使用type和元类metaclass 动态创建类

type()函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过type()函数创建出Hello类,而无需通过class Hello(object)…的定义:

要创建一个class对象,type()函数依次传入3个参数:

  1. class的名称;
  2. 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
  3. class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。

通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。

正常情况下,我们都用class Xxx…来定义类,但是,type()函数也允许我们动态创建出类来,也就是说,动态语言本身支持运行期动态创建类,这和静态语言有非常大的不同,要在静态语言运行期创建类,必须构造源代码字符串再调用编译器,或者借助一些工具生成字节码实现,本质上都是动态编译,会非常复杂。

>>> def fn(self, name='world'): # 先定义函数
...     print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class

metaclass,直译为元类,简单的解释就是:

当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。

但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。
元类的使用感觉不太常用,先不写

异常

异常的概念

程序运行时,解释器遇到一个错误,会停止程序并提示信息。
程序停止执行并给出信息这个动作,成为抛出(raise)异常
通过捕获异常可以对突发事件进行集中处理,保证程序的稳定性和健壮性。

Python的错误其实也是class,所有的错误类型都继承自BaseException,所以在使用except时需要注意的是,它不但捕获该类型的错误,还把其子类也“一网打尽”。

常见的错误类型和继承关系

https://docs.python.org/3/library/exceptions.html#exception-hierarchy

捕获异常

  1. 简单的捕获异常语法
try:
	尝试执行的代码
except:
	出错处理的代码
''' 注意,出错处理后程序不会停止而是会继续执行 '''
  1. 错误类型捕获
try:
	pass
except error1:
	# 针对错误1的处理
	pass
except (error2, error3)# 针对错误2、错误3的处理
	pass
except Exception as Result:
	print("未知错误 %s " % Result)
  1. 异常捕获完整语法
try:
	pass
except error1:
	# 针对错误1的处理
	pass
except (error2, error3)# 针对错误2、错误3的处理
	pass
except Exception as Result:
	print("未知错误 %s " % Result)
else:
	# 没有异常出现才会执行的代码
	pass
finally:
	# 无论是否有异常都会执行的代码
	pass

异常的传递

当方法/函数调用出现异常,会将异常传递给调用该方法函数的一方。
并逐级回传,直到传递到主程序,如果主程序仍然没有异常处理,程序才会被终止。

这样,我们根据异常的传递性,只需要在主程序中增加异常捕获语句,可以保证代码的简洁性。

记录异常(错误) logging模块

Python内置的logging模块可以非常容易地记录错误信息:
程序打印完错误信息后会继续执行,并正常退出:

# err_logging.py

import logging

def foo(s):
    return 10 / int(s)

def bar(s):
    return foo(s) * 2

def main():
    try:
        bar('0')
    except Exception as e:
        logging.exception(e)

main()
print('END')

抛出异常

如果要抛出错误,首先根据需要,可以定义一个错误的class,选择好继承关系,然后,用raise语句抛出一个错误的实例

raise语句如果不带参数,就会把当前错误原样抛出。此外,在except中raise一个Error,还可以把一种类型的错误转化成另一种类型:

# err_raise.py
class FooError(ValueError):
    pass

def foo(s):
    n = int(s)
    if n==0:
        raise FooError('invalid value: %s' % s)
    return 10 / n

foo('0')

调试

print查看

断言 assert

def foo(s):
    n = int(s)
    assert n != 0, 'n is zero!'
    return 10 / n

def main():
    foo('0')

assert的意思是,表达式n != 0应该是True,否则,根据程序运行的逻辑,后面的代码肯定会出错。如果断言失败,assert语句本身就会抛出AssertionError

程序中如果到处充斥着assert,和print()相比也好不到哪去。不过,启动Python解释器时可以用-O参数来关闭assert:(断言的开关“-O”是英文大写字母O,不是数字0。)

logging记录

把print()替换为logging是第3种方式,和assert比,logging不会抛出错误,而且可以输出到文件:

import logging
logging.basicConfig(level=logging.INFO)

s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)

logging允许你指定记录信息的级别,有debug,info,warning,error等几个级别,当我们指定level=INFO时,logging.debug就不起作用了。同理,指定level=WARNING后,debug和info就不起作用了。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。

pdb模块调试

第4种方式是启动Python的调试器pdb,让程序以单步方式运行,可以随时查看运行状态。我们先准备好程序
在运行文件时带上-m pdb即可

python -m pdb test.py

l # 查看代码
n # 单步执行
p variable # 查看变量值
q # quit
c # continue untill trace point

也可以在代码中设置断点pdb.set_trace()来设置断点
程序会自动在pdb.set_trace()暂停并进入pdb调试环境,可以用命令p查看变量,或者用命令c继续运行:

import pdb
pdb.set_trace()
# 程序会自动在pdb.set_trace()暂停并进入pdb调试环境,可以用命令p查看变量,或者用命令c继续运行:

测试

单元测试模块 unittest

https://www.liaoxuefeng.com/wiki/1016959663602400/1017604210683936
感觉暂时用不上,先不写。

文档测试(单文件测试)

当我们编写注释时,如果写上这样的注释:

# mydict2.py
class Dict(dict):
    '''
    Simple dict but also support access as x.y style.

    >>> d1 = Dict()
    >>> d1['x'] = 100
    >>> d1.x
    100
    >>> d1.y = 200
    >>> d1['y']
    200
    >>> d2 = Dict(a=1, b=2, c='3')
    >>> d2.c
    '3'
    >>> d2['empty']
    Traceback (most recent call last):
        ...
    KeyError: 'empty'
    >>> d2.empty
    Traceback (most recent call last):
        ...
    AttributeError: 'Dict' object has no attribute 'empty'
    '''
    def __init__(self, **kw):
        super(Dict, self).__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Dict' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

if __name__=='__main__':
    import doctest
    doctest.testmod()

运行该文件时,会把注释中的测试代码提取出来运行一遍,如果正常运行就无影响,否则会报错。

模块

模块搜索顺序:
在当前目录下搜索指定名称的模块文件
如果在当前目录下没找到,会回到系统的工具包目录下找。

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

模块开发原则

每一个文件都应该是可以被导入的。
一个独立的py文件就是一个模块。
在导入文件时,所有没有任何缩进的代码都会被执行一遍

内置属性 name

__name__属性可以做到,测试模块的代码只在测试时执行,在被导入不会被执行
如果是被其他文件导入的,__name__就是模块名
如果是当前执行的文件,name__就是__main

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

# 在代码的最下方
def main():
	#...
	pass
if __name__ == "__main__":
	main()	

发布模块

  1. 创建setup.py
from distutils.core import setup
setup(name = "包名",
version = "1.0",
description = "描述信息",
long_description = "",
author = "",
author_email = "",
url = "",
py_modules = ["包名.模块名1",
	"包名.模块名2"]
)
  1. 构建发布模块
    在终端执行指令
# 构建模块
python3 setup.py build
# 发布模块
python3 setup.py sdist


# 安装模块
tar -zxvf  包名.tar.gz
sudo python3 包名.py install

# 删除模块
cd usr/local/lib/python3.x/dist-packages/
sudo rm -r 包名*

使用 pip 安装第三方模块

# 如果想把包安装到Python3环境
sudo pip3 install package_name
sudo pip3 uninstall package_name
# 如果想把pip安装到python2环境
sudo pip install package_name
sudo pip uninstall package_name

包是一个包含多个模块的特殊目录
目录下有一个特殊的文件__init__.py
包名的命名方式和变量名命名方式一样,小写字母 + 下划线_
使用 import 包名 可以一次导入包中所有模块

__init.py

# 要在外界使用包中的模块,
# 需要在__init.py指定对外界提供的模块列表
from . import module1
from . import module2
...

文件操作(IO控制)

文件的概念

  1. 文件的概念和作用
  2. 文件的存储方式
    二进制数据

文件的基本操作

文件操作流程

  1. 打开文件
  2. 读写文件
  3. 关闭文件

操作文件的函数/方法

函数/方法说明
open打开文件并返回文件操作对象
read将文件内容读取到内存
write将指定内容写入到文件
close关闭文件

以上是最基本的四个方法,还有许多的文件对象方法:
在这里插入图片描述
在这里插入图片描述

文件打开的方式

open 函数默认以只读方式打开文件,并返回文件对象

# 常用形式
f = open("文件名", "打开方式")
# 完整格式
open(file, mode='r', buffering=-1, 
	encoding=None, errors=None, newline=None, 
	closefd=True, opener=None)
'''
file: 必需,文件路径(相对或者绝对路径)。
mode: 可选,文件打开模式
buffering: 设置缓冲
encoding: 一般使用utf8
errors: 报错级别
newline: 区分换行符
closefd: 传入的file参数类型
opener: 设置自定义开启器,开启器的返回值必须是一个打开的文件描述符。
'''

打开方式mode的参数有
在这里插入图片描述
在这里插入图片描述

文件/目录常用管理操作

在终端/文件浏览器上可以执行常规的文件/目录管理操作,如创建、重命名、删除、改变路径等。
在python中可以通过导入os模块实现这些功能。

文件操作
方法名说明示例
rename重命名os.rename(源文件名, 目标文件名)
remove删除文件os.remove(目标文件)
目录操作
方法名说明示例
listdir目录列表os.listdir(目录名)
mkdir创建目录os.mkdir(目录名)
rmdir删除目录os.rmdir(目录名)
getcwd获取当前目录os.getcwd()
chdir修改工作目录os.chdir(目标目录)
path.isdir判断是否是文件os.path.isdir(文件路径)
文件路径支持相对路径和绝对路径

文本文件的编码格式

文本文件存储的是基于字符编码的数据,常见的有ascii编码和unicode编码等
python2.x默认使用ascii编码。
python3.x默认使用UTF-8编码

如果要 在python2.x中使用中文输出,需要把编码格式改成utf-8
具体地是在代码最开始加上一行注释。并且在字符串引号前加u

# *-* coding:utf8 *-*
u"这是一个中文字符串"
ASCII编码

有256个字符,给字符在内存中占据1个字节的空间。

UTF-8编码

使用1-6个字节来表示一个UTF-8字符,几乎涵盖世界上所有地区的文字
大多数汉字会用3个字节表示
UTF-8是unicode的一种编码格式

StringIO和BytesIO

很多时候,数据读写不一定是文件,也可以在内存中读写。

StringIO顾名思义就是在内存中读写str。

要把str写入StringIO,我们需要先创建一个StringIO,然后,像文件一样写入即可:

>>> from io import StringIO
>>> f = StringIO()
>>> f.write('hello')
5
>>> f.write(' ')
1
>>> f.write('world!')
6
>>> print(f.getvalue())
hello world!

>>> from io import StringIO
>>> f = StringIO('Hello!\nHi!\nGoodbye!')
>>> while True:
...     s = f.readline()
...     if s == '':
...         break
...     print(s.strip())
...
Hello!
Hi!
Goodbye!

StringIO操作的只能是str,如果要操作二进制数据,就需要使用BytesIO。

BytesIO实现了在内存中读写bytes,我们创建一个BytesIO,然后写入一些bytes:

序列化·pickle模块

我们把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling,在其他语言中也被称之为serialization,marshalling,flattening等等,都是一个意思。

序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。

反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。
Python提供了pickle模块来实现序列化

>>> import pickle
>>> d = dict(name='Bob', age=20, score=88)
>>> pickle.dumps(d)
b'\x80\x03}q\x00(X\x03\x00\x00\x00ageq\x01K\x14X\x05\x00\x00\x00scoreq\x02KXX\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00Bobq\x04u.'

>>> f = open('dump.txt', 'wb')
>>> pickle.dump(d, f)
>>> f.close()

当我们要把对象从磁盘读到内存时,可以先把内容读到一个bytes,然后用pickle.loads()方法反序列化出对象,也可以直接用pickle.load()方法从一个file-like Object中直接反序列化出对象。我们打开另一个Python命令行来反序列化刚才保存的对象:

>>> f = open('dump.txt', 'rb')
>>> d = pickle.load(f)
>>> f.close()
>>> d
{'age': 20, 'score': 88, 'name': 'Bob'}

eval()函数

将字符串当成有效表达式来求值并返回结果

要注意的是,不要用eval函数直接转换终端输入,否则用户可以通过os模块的函数调用做一些非法操作。
比如

eval('__import__(os).system("rm -rf")')

会导致当前目录下文件被强制删除

#------------------------------------------------------------------------------#
更新于2021-6-2

#------------------------------------------------------------------------------#
更新于2023-1-4,更新了面向对象高级编程部分、异常部分、测试部分的内容,参考廖雪峰前辈的网站增加了许多内容。

说明:本文内容参考了一些视频和博客,虽然是我自己进行了整理和编辑后的内容,但如果您(原视频或博客作者)认为我的行为侵犯了您的版权,请私信联系我,核实身份后我会删除文章。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值