4.1 函数
函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。函数能提高应用的模块性,和代码的重复利用率。Python 提供了许多内建函数,比如 print()。也可以自己创建函数,这被叫做用户自定义函数。
4.1.2 定义函数
定义一个由自定义函数,需要遵循以下规则:
- 函数代码块以def关键词开头,后接函数标识符名称和圆括号()。
- 任何传入参数和自变量必须放在圆括号中间。圆括号之间可以用于定义参数。
- 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
- 函数内容以冒号起始,并且缩进。
- return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的 return 相当于返回 None。
语法
def functionname( parameters ): "函数_文档字符串" function_suite return [expression]
4.1.2 函数调用
定义一个函数只给了函数一个名称,指定了函数里包含的参数,和代码块结构。这个函数的基本结构完成以后,可以通过另一个函数调用执行,也可以直接从 Python 提示符执行。
# 定义函数def printme(str): print(str)# 调用函数printme("我要调用用户自定义函数!")
4.1.3 参数传递
在 python 中,类型属于对象,变量是没有类型的:
以上代码中,[1,2,3] 是 List 类型,"Python" 是 String 类型,而变量 a 是没有类型,她仅仅是一个对象的引用(一个指针),可以是 List 类型对象,也可以指向 String 类型对象。
可更改(mutable)与不可更改(immutable)对象
在 python 中,strings,tuples 和 numbers 是不可更改的对象,而 list,dict 等则是可以修改的对象。
- 不可变类型:变量赋值a=5后再赋值a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变a的值,相当于新生成了a。
- 可变类型:变量赋值la=[1,2,3,4]后再赋值la[2]=5则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了。
python 函数的参数传递:
- 不可变类型:类似 c++ 的值传递,如 整数、字符串、元组。如 fun(a),传递的只是 a 的值,没有影响 a 对象本身。比如在 fun(a) 内部修改 a 的值,只是修改另一个复制的对象,不会影响 a 本身。
- 可变类型:类似 c++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后 fun 外部的 la 也会受影响
python 中一切都是对象,严格意义我们不能说值传递还是引用传递,应该说传不可变对象和传可变对象。
4.1.4 参数类型
必备参数
必备参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样。
# 必备参数函数def printme(str): print(str)# 调用 printme 函数,不传参数会报错printme()
关键字参数
关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。使用关键字参数允许函数调用时参数的顺序与声明时不一致,Python 解释器能够用参数名匹配参数值。
# 关键字参数函数def printinfo( name, age ): print(f"Name: {name}") print(f"Age: {age}")# 调用 printinfo 函数printinfo( age=23, name="张三" )
默认参数
调用函数时,默认参数的值如果没有传入,则被认为是默认值。
# 默认值参数函数def printinfo( name, age=35): print(f"Name: {name}") print(f"Age: {age}")# 调用 printinfo 函数printinfo( age=23, name="张三" )printinfo( name="李四" )
不定长参数
可能需要一个函数能处理比当初声明时更多的参数。这些参数叫做不定长参数,和其他类型的参数不同,不定长参数声明时不会命名。基本语法如下:
def functionname([formal_args,] *var_args_tuple ): function_suite return [expression]
加了星号*的变量名会存放所有未命名的变量参数。不定长参数实例如下:
# 不定长参数函数def printinfo( arg1, *vartuple ): print(f"输出: {arg1}") for var in vartuple: print(var)# 调用 printinfo 函数printinfo( 10 )printinfo( 70, 60, 50 )
4.1.5 匿名函数
python 使用 lambda 来创建匿名函数。
- lambda 只是一个表达式,函数体比 def 简单很多。
- lambda 的主体是一个表达式,而不是一个代码块。仅仅能在 lambda 表达式中封装有限的逻辑进去。
- lambda 函数拥有自己的命名空间,且不能访问自有参数列表之外或全局命名空间里的参数。
- 虽然 lambda 函数看起来只能写一行,却不等同于 C 或 C++ 的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。
lambda
lambda函数的语法只包含一个语句,如下:
lambda [arg1 [,arg2,.....argn]]:expression
示例如下:
# 传递参数 arg1,arg2 执行 arg1 + arg2sum = lambda arg1, arg2: arg1 + arg2
4.1.6 变量作用域
在 Python 程序中,变量的作用域主要由其定义位置决定。变量可以有全局作用域和局部作用域两种。全局变量:定义在函数外部的变量拥有全局作用域,可以在整个文件内使用。全局变量的作用域跨越函数边界,,所有函数都可以访问全局变量。例如:
a = 1 # 全局变量 adef foo(): print(a) # 可以访问全局变量 a
如果在函数内部定义了一个与全局变量同名的变量,则该函数内部的变量会遮盖全局变量,函数内部使用的是局部变量。
a = 1 def foo(): a = 2 # 没有声明 global,此处创建局部变量 a print(a) # 2 print(a) # 1,不影响全局变量 a
若在函数内修改全局变量,需要使用 global 关键字进行声明,否则会在函数内创建一个同名的局部变量。
a = 1 def foo(): global a # 声明 a 为全局变量 a = 2 print(a) # 2print(a) # 2,foo 函数修改了全局变量 a
局部变量:定义在函数内部的变量拥有局部作用域,只能在函数内部使用。局部变量的作用域只在函数内,外部函数无法访问局部变量:
def foo(): a = 1 # 局部变量 aprint(a) # 错误,a 是局部变量,只能在函数内使用
变量的作用域由变量的定义位置决定。如果在函数内部定义一个变量,它只能在函数内部使用;如果在函数外部定义一个变量,它可以在整个模块中使用。
4.2 模块
Python 模块(Module),是一个 Python 文件,以 .py 结尾,包含了 Python 对象定义和Python 语句。模块能定义函数,类和变量,模块里也能包含可执行的代码。
4.2.1 import 语句
模块定义好后,可以使用 import 语句来引入模块,语法如下:
import module1[, module2[,... moduleN]]
比如要引用模块 math,就可以在文件顶端用import math来引入。在调用 math 模块中的函数时,必须模块名.函数名的方式引用。
当解释器遇到 import 语句,如果模块在当前的搜索路径就会被导入。搜索路径是一个解释器会先进行搜索的所有目录的列表。一个模块只会被导入一次,不管执行了多少次 import。
当导入一个模块,Python 解析器对模块位置的搜索顺序是:
- 当前目录
- 如果不在当前目录,Python 则搜索在 shell 变量 PYTHONPATH 下的每个目录。
- 如果都找不到,Python会察看默认路径。UNIX下,默认路径一般为 /usr/local/lib/python/。
模块搜索路径存储在 system 模块的 sys.path 变量中。变量里包含当前目录,PYTHONPATH 和由安装过程决定的默认目录。
4.2.2 from…import 语句
Python 的 from 语句可以从模块中导入一个指定的部分到当前命名空间中。语法如下:
from modname import name1[, name2[, ... nameN]]
这个声明不会把整个模块导入到当前的命名空间中,它只会将模块里的指定部分单独引入到执行这个声明的模块的全局符号表。
把一个模块的所有内容全都导入到当前的命名空间也是可行的,只需使用如下声明:
import 导入模块,每次使用模块中的函数都要是定是哪个模块。
from…import * 导入模块,每次使用模块中的函数,直接使用函数就可以了;因为已经知道该函数是那个模块中的了。但是导入模块时,会跳过私有属性;
4.2.3 标准库
以下是一些 Python3 标准库中的模块:
标准库名称 | 描述 |
math | 提供了各种数学运算函数,如三角函数、对数、指数等。 |
os | 提供了访问操作系统功能的接口,例如文件操作、目录操作、环境变量等。 |
datetime | 用于处理日期和时间。可以创建日期时间对象、进行日期时间运算和格式化输出。 |
random | 用于生成伪随机数。可以生成随机整数、随机浮点数、随机序列等。 |
json | 用于解析和生成 JSON 格式的数据。可以将 Python 对象转换为 JSON 字符串,或将 JSON 字符串转换为 Python 对象。 |
re | 提供了正则表达式的功能,用于字符串的模式匹配和搜索。可以进行强大的字符串匹配和搜索操作。 |
urllib | 用于进行 URL 请求和处理。包括发送 HTTP 请求、下载网页内容、解析 URL 等功能。 |
argparse | 用于解析命令行参数。可以方便地处理命令行参数,并生成帮助信息。 |
collections | 提供了一些额外的数据结构,如有序字典、默认字典、计数器等。 |
csv | 用于读写 CSV 格式文件。可以读取 CSV 文件中的数据,并将数据写入到 CSV 文件中。 |
logging | 用于记录日志信息。可以记录程序运行中的各种信息,包括调试信息、错误信息等。 |
socket | 用于进行网络通信。提供了 socket 编程接口,可以创建网络连接、进行数据传输等。 |
io | 提供了文件 IO 相关的功能。包括文件操作、流操作等。 |
time | 用于处理时间。包括获取当前时间、时间格式化、时间戳转换等功能。 |
multiprocessing | 用于进行多进程处理。可以创建多个进程并进行进程间通信。 |
threading | 用于进行多线程处理。可以创建多个线程并进行线程间通信。 |
subprocess | 用于管理子进程。可以创建和管理子进程,并与子进程进行交互。 |
sqlite3 | 提供了 SQLite 数据库的接口。可以在 Python 中操作 SQLite 数据库。 |
email | 用于发送邮件。可以创建和发送邮件,添加附件等。 |
http | 提供了 HTTP 请求相关的功能。可以发送 HTTP 请求、处理 HTTP 响应等。 |
html | 用于解析 HTML 文件。可以从 HTML 文件中提取数据。 |
xml | 用于解析 XML 文件。可以从 XML 文件中提取数据。 |
4.3 面向对象
4.3.1 创建类
使用 class 语句来创建一个新类,class 之后为类的名称并以冒号结尾:
class ClassName: '类的帮助信息' #类文档字符串 class_suite #类体
参数说明
- ClassName:用于指定类名,一般使用大写字母开头,如果类名中包括两个单词,第二个单词的首字母也大写,这种命名方法也称为“驼峰式命名法”,这是惯例。当然,也可根据自己的习惯命名,但是一般推荐按照惯例来命名。
- 类的帮助信息可以通过
ClassName.__doc__查看。 - class_suite:类体,主要由类变量(或类成员)、方法和属性等定义语句组成。如果在定义类时,没想好类的具体功能,也可以在类体中直接使用 pass 语句代替。
self
类的方法与普通的函数只有一个区别,它们必须有一个额外的第一个参数,按照惯例它的名称是 self,可以命名为其他名称。类示例如下:
class Employee: '所有员工的基类' empCount = 0 def __init__(self, name, salary): self.name = name self.salary = salary Employee.empCount += 1 def displayCount(self): print(f"Total Employee {Employee.empCount}") def displayEmployee(self): print(f"Name : {self.name}, Salary: {self.salary}")
self 代表的是类的实例,代表当前对象的地址,而 self.__class__ 则指向类
创建实例
class 语句本身并不创建该类的任何实例。所以在类定义完成以后,可以创建类的实例,即实例化该类的对象。创建类的实例的语法如下:
其中,ClassName 是必选参数,用于指定具体的类;parameterlist 是可选参数,当创建一个类时,没有创建_init_() 方法,或者 _init_() 方法只有一个 self 参数时,parameterlist 可以省略。
访问属性
可以使用点号.来访问对象的属性。例如:
属性常用方法
方法名 | 功能描述 |
getattr(obj, name[, default]) | 访问对象的属性。 |
hasattr(obj,name) | 检查是否存在一个属性。 |
setattr(obj,name,value) | 设置一个属性。如果属性不存在,会创建一个新属性。 |
delattr(obj, name) | 删除属性。 |
内置属性
属性名 | 功能描述 |
__dict__ | 类的属性(包含一个字典,由类的数据属性组成) |
__doc__ | 类的文档字符串 |
__name__ | 类名 |
__module__ | 类定义所在的模块 |
__bases__ | 类的所有父类构成元素(包含了一个由所有父类组成的元组) |
Python 使用了引用计数这一简单技术来跟踪和回收垃圾。在 Python 内部记录着所有使用中的对象各有多少引用。一个内部跟踪变量,称为一个引用计数器。
当对象被创建时, 就创建了一个引用计数, 当这个对象不再需要时, 也就是说, 这个对象的引用计数变为 0 时, 它被垃圾回收。但是回收不是"立即"的, 由解释器在适当的时机,将垃圾对象占用的内存空间回收。
垃圾回收机制不仅针对引用计数为 0 的对象,同样也可以处理循环引用的情况。循环引用指的是,两个对象相互引用,但是没有其他变量引用他们。这种情况下,仅使用引用计数是不够的。Python 的垃圾收集器实际上是一个引用计数器和一个循环垃圾收集器。作为引用计数的补充, 垃圾收集器也会留心被分配的总量很大(即未通过引用计数销毁的那些)的对象。 在这种情况下, 解释器会暂停下来, 试图清理所有未引用的循环。
4.3.2 继承
面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。通过继承创建的新类称为子类或派生类,被继承的类称为基类、父类或超类。继承语法如下:
在 python 中继承中的一些特点:
- 如果在子类中需要父类的构造方法就需要显式的调用父类的构造方法,或者不重写父类的构造方法。
- 在调用基类的方法时,需要加上基类的类名前缀,且需要带上 self 参数变量。区别在于类中调用普通函数时并不需要带上 self 参数
- Python 总是首先查找对应类型的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找(先在本类中查找调用的方法,找不到才去基类中找)。
如果在继承元组中列了一个以上的类,那么它就被称作"多重继承" 。
class 派生类名(基类名[, 基类名2, ...]): ...
issubclass(sub,sup) 判断一个类是另一个类的子类或者子孙类
isinstance(obj,Class) 判断 obj 是 Class 类的实例或者是一个 Class 子类的实例
方法重写
class Parent: # 定义父类 def myMethod(self): print('调用父类方法')class Child(Parent): # 定义子类 def myMethod(self): prin('调用子类方法')
4.3.3 类属性与方法
类的私有属性
__private_attrs:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时self.__privateAttrs。
类的方法
在类的内部,使用 def 关键字可以为类定义一个方法,与一般函数定义不同,类方法必须包含参数 self,且为第一个参数
类的私有方法
__private_method:两个下划线开头,声明该方法为私有方法,不能在类的外部调用。在类的内部调用self.__privateMethods
Python不允许实例化的类访问私有数据,但可以使用object._className__attrName(对象名._类名__私有属性名)访问属性,参考以下实例:
class Python: __str = "Python"python = Python()print(python._Python__str)
单下划线、双下划线、头尾双下划线说明
__foo__: 定义的是特殊方法,一般是系统定义名字 ,类似 __init__() 之类的。
_foo: 以单下划线开头的表示的是 protected 类型的变量,即保护类型只能允许其本身与子类进行访问,不能用于 from module import *
__foo: 双下划线的表示的是 private 类型的变量, 只能是允许这个类本身进行访问。
4.4 异常
4.4.1 异常处理
捕获异常
捕捉异常可以使用 try/except 语句。try/except 语句用来检测 try 语句块中的错误,从而让 except 语句捕获异常信息并处理。语法为:
try: <语句> #运行别的代码except <名字>: <语句> #如果在try部份引发了'name'异常except <名字>,<数据>: <语句> #如果引发了'name'异常,获得附加的数据else: <语句> #如果没有异常发生
try 的工作原理是,当开始一个 try 语句后,python 就在当前程序的上下文中作标记,这样当异常出现时就可以回到这里,try 子句先执行,接下来会发生什么依赖于执行时是否出现异常。
如果当 try 后的语句执行时发生异常,python 就跳回到 try 并执行第一个匹配该异常的 except 子句,异常处理完毕,控制流就通过整个 try 语句(除非在处理异常时又引发新的异常)。不带任何异常类型的 except 将匹配所有异常。
如果在 try 后的语句里发生了异常,却没有匹配的 except 子句,异常将被递交到上层的 try,或者到程序的最上层(这样将结束程序,并打印默认的出错信息)。
如果在 try 子句执行时没有发生异常,python 将执行 else 语句后的语句(如果有 else 的话),然后控制流通过整个try语句。
try: fh = open("testfile", "w") fh.write("这是一个测试文件,用于测试异常!!")except IOError: print("Error: 没有找到文件或读取文件失败")else: print("内容写入文件成功") fh.close()
一个异常可以带上参数,可作为输出的异常信息参数。可以通过 except 语句来捕获异常的参数,如下所示:
try: 正常的操作 ......................except ExceptionType, Argument: 输出 Argument 的值...
抛出异常
可以使用 raise 语句自己触发异常,语法如下:
raise [exceptionName[(reason)]]
其中 Exception 是异常的类型参数标准异常中任一种,ExceptionName[(reason)] 为可选参数,用于指定抛出的异常名称以及异常信息的相关描述。如果省略,就会把当前的错误原样抛出。如果仅省略 reason,则在抛出异常时,将不附带任何的异常描述信息。抛出异常示例:
try: # 尝试执行一些操作 passexcept ValueError as e: print("处理值错误") raise MyError("由值错误引起的我的错误。") from e
在这个例子中,from e 表示新的异常是在处理 ValueError 异常的上下文中产生的,这有助于调试,因为它能够提供完整的异常链。
断言
Python 提供了 assert 语句来调试程序, assert 语句的基本语法如下:
assert expression [,reason]
参数说明:
- expression:条件表达式,如果该表达式为真时,什么都不做,如果为假时,则抛出 AssertionError 异常。
- reason:可选参数,用于对判断条件进行描述,为了以后更好地知道哪里出现了问题。
try...except...finally 语句
finally 语句无论是否发生异常都将执行最后的代码。语法如下:
try: <语句>except <名字>: <语句> finally: <语句>
示例如下
try: fh = open("testfile", "w") fh.write("这是一个测试文件,用于测试异常!!")except IOError: print("Error: 没有找到文件或读取文件失败")else: print("内容写入文件成功")finally: print("关闭文件") fh.close()
4.4.2 常见异常
异常名称 | 描述 |
NameError | 尝试访问一个未被声明的变量引发的错误 |
IndexError | 索引超出序列范围引发的错误 |
IndentationError | 缩进错误 |
ValueError | 传入的值错误 |
KeyError | 访问一个不存在的字典关键字引发的错误 |
IOError | 输入输出错误(如尝试读取的文件不存在) |
ImportError | 当 import 语句无法找到模块或 from 无法找到模块中找到相应的名称时引发的错误 |
AttributeError | 尝试访问未知的对象属性引发的错误 |
TypeError | 类型不合适引发的错误 |
MemoryError | 内存不足 |
ZeroDivisionError | 除数为 0 引发的错误 |
4.3.3 自定义异常
通过创建一个新的异常类,程序可以命名它们自己的异常。异常应该是通过直接或间接的继承自 Exception 类。
class Networkerror(RuntimeError): def __init__(self, arg): self.args = arg
触发自定义异常
try: raise Networkerror("Bad hostname")# 变量 e 是用于创建 Networkerror 类的实例except Networkerror,e: print e.args
4.5 时间日期
Python 程序能用很多方式处理日期和时间,转换日期格式是一个常见的功能。Python 提供了一个 time 和 calendar 模块可以用于格式化日期和时间。时间间隔是以秒为单位的浮点小数。每个时间戳都以自从1970年1月1日零时经过了多长时间来表示。
Python 的 time 模块下有很多函数可以转换常见日期格式。如函数time.time()用于获取当前时间戳, 如下实例:
import time # 引入time模块ticks = time.time()print("当前时间戳为:", ticks)
4.5.1 时间操作
获取当前时间
import timelocaltime = time.localtime(time.time())print("本地时间为:", localtime)
获取格式化时间
import timelocaltime = time.asctime( time.localtime(time.time()) )print("本地时间为 :", localtime)
按需格式化
time.strftime(format[, t])
4.5.2 日历操作
获取某月日历
import calendarcal = calendar.month(2024, 3)print(cal)