一、文件相关概念（一）文件路径# 绝对路径示例 "D:\\lecture\\ex\\file.txt" "D/lecture/ex/file.txt" # 相对路径示例 # 假设`file.txt保存在D盘的lecture目录的ex子目录下，源程序保存在D盘的lecture目录下，那么包含相对路径的文件名应该保存为： "ex\\file.txt" "ex/file.txt" # Windows系统中建立路径的特殊符号 # ".\" :当前目录 # "..\"：上一级目录（二）文件编码在Python 3.x版本中：文件默认编码是UTF-8；字符串使用的是Unicode编码；文本文件使用Unicode编码。在Python语言中，对文本文件的存取以字符为单位，输入/输出的字符流开始和结束由程序控制。每个文件的结尾处通常由一个结束标志EOF。二、文件夹与目录操作（一）os.path模块模块功能介绍：https://www.runoob.com/python/python-os-path.html获取工作目录：os.getcwd()# 获取和改变当前目录 import os print("当前的工作目录：", os.getcwd()) os.chdir('../') print("改变后的工作目录：", os.getcwd())当前的工作目录： D:\pythonProject\09 IO编程 改变后的工作目录： D:\pythonProjectimport os print(os.path.dirname("D:\pythonProject\09 IO编程")) #返回path目录 print(os.path.basename("D:\pythonProject\09 IO编程")) #返回path的最后文件名 print(os.path.exists('D://pythonProject/09 IO编程')) #判断文件路径是否存在D:\ pythonProject9 IO编程 True（二）目录与文件操作获得目录中的文件：os.listdir(patn)os.listdir('D:\音乐')['AlbumArtSmall.jpg', 'Folder.jpg', 'Home Free - Auld Lang Syne_英文（en）-auto.srt', 'Joel P West - Summer Storm.lrc', 'Joel P West - Summer Storm.mp3', 'videoplayback.mp4', '周杰伦 - 听妈妈的话.flac', '杨坤、郭采洁 - 答案.mp3', '清明上河图.mp3', '王力宏 - 需要人陪.mp3', '纯音乐 - Love home.mp3', '金玟岐-岁月神偷.mp3']创建目录：os.mkdir(path)os.mkdir("D://pythonProject//09 IO编程//test")删除目录：os.rmdir(path)os.rmdir("D://pythonProject//09 IO编程//test")（三）文件的重命名与删除文件重命名：os.rename(src, dst) src：要修改的文件名；dst：修改后的目录名import os, sys # 列出目录 print("目录为：%s" % os.listdir(os.getcwd())) #重命名 os.rename("test", "test1") print("重命名成功。") # 列出重命名后的目录 print("重命名后的目录为：%s" % os.listdir(os.getcwd()))目录为：['.idea', '.ipynb_checkpoints', '01 Python基础', '02 函数', '03 高级特性', '04 函数式编程', '05 模块', '06 面向对象编程', '07 面向对象的高级编程', '08 爬虫', '09 IO编程', 'draft.py', 'test'] 重命名成功。 重命名后的目录为：['.idea', '.ipynb_checkpoints', '01 Python基础', '02 函数', '03 高级特性', '04 函数式编程', '05 模块', '06 面向对象编程', '07 面向对象的高级编程', '08 爬虫', '09 IO编程', 'draft.py', 'test1']删除文件：os.remove(path)import os, sys # 列出目录 print("目录为：%s" % os.listdir(os.getcwd())) #移除 os.remove("test1.txt") #移除后的目录 print("移除后的目录为：%s" % os.listdir(os.getcwd()))目录为：['.idea', '.ipynb_checkpoints', '01 Python基础', '02 函数', '03 高级特性', '04 函数式编程', '05 模块', '06 面向对象编程', '07 面向对象的高级编程', '08 爬虫', '09 IO编程', 'draft.py', 'test1', 'test1.txt'] 移除后的目录为：['.idea', '.ipynb_checkpoints', '01 Python基础', '02 函数', '03 高级特性', '04 函数式编程', '05 模块', '06 面向对象编程', '07 面向对象的高级编程', '08 爬虫', '09 IO编程', 'draft.py', 'test1']三、文件的基本操作（一）文件打开与关闭https://www.runoob.com/python/python-files-io.html1. 文件打开与新建通过内置的open()函数实现文件打开，基本语法格式为：file = open(filename[,mode[,buffering]])参数说明：file：被创建的文件对象filename：要创建或打开的文件名称，需使用引号括起来mode：可选参数，用于指定文件的打开模式；buffering：可选参数，制定读写文件的缓冲模式。0表示不缓存，1表示缓存，大于1则表示缓冲区大小。默认缓存模式。open()方法的应用场景有如下三种：打开与新建文件open()函数打开一个不存在的文件会抛出异常，指定mode参数值为w、w+、a、a+时，当文件不存在可以创建新的文件。f = open("D://pythonProject//draft.py",'w')以二进制形式打开文件可以打开非文本文件，如图片、音频、视频等文件。f = open("D://pythonProject//picture.jpg","rb")打开文件时制定编码方式f = open("D://pythonProject//picture.jpg","r", encoding = 'utf-8')2. 文件关闭文件关闭的语法格式：file.close()文件打开后要及时关闭，原因在于：资源管理：打开文件会占用系统资源，包括内存和文件描述符等。如果不及时关闭文件，会导致资源泄露，最终可能导致程序崩溃或运行缓慢。数据完整性：在将数据写入文件时，如果没有及时关闭文件，可能会导致数据丢失或不完整。文件在关闭之前可能仍然处于缓冲状态，数据可能还没有完全写入磁盘。3.文件锁定：在某些操作系统中，打开文件时会将其锁定，防止其他程序对其进行修改。如果不及时关闭文件，会导致其他程序无法访问或修改该文4. 件。代码可读性和维护性：显式地关闭文件可以使代码更加清晰和易于理解。关闭文件可以明确地表示文件的使用范围，并帮助避免可能的错误和混乱。# 不同模式下文件的打开与关闭操作 #r:如果文件不存在，报错；不支持写入操作，只读 f = open('data.txt','r') # f = open('test.txt','r') # [Errno 2] No such file or directory: 'test.txt' # f.write('aa') # UnsupportedOperation: not writable#w：只写，如果文件不存在，新建文件；执行写入，会覆盖原有内容 f = open('data.txt','w') f.write('Our wills unite like a fortress') f.close#a：追加，如果文件不存在，新建文件；在原有内容的基础上，追加新内容 f = open('data.txt','a') f.write('Public clamor can melt metals') f.close29#访问模式参数可以省略，默认为r f = open("data.txt") f.close（二）文件读取与写入open()函数打开文件后，会返回一个TextIOWrapper对象，然后可以调用该对象中的方法对文件进行操作。该对象有4个常用方法：write(string)方法：将字符串写入文件，并返回写入的字符数。write语句不会自动换行，如需换行要使用换行符'\n'。read(size)方法：read（）方法从一个打开的文件中读取一个字符串。需要重点注意的是，Python字符串可以是二进制数据，而不是仅仅是文字。语法语法：fileObject.read([size])f = open('data.txt','r') print(f.read(30))Our wills unite like a fortresclose()方法：File 对象的 close（）方法刷新缓冲区里任何还没写入的信息，并关闭该文件，这之后便不能再进行写入。语法：fileObject.close()#!/usr/bin/python # -*- coding: UTF-8 -*- # 打开一个文件 f = open("data.txt", "w") print ("文件名: ", f.name) # 关闭打开的文件 f.close()文件名: data.txtseek（offset [,from]）方法：Offset变量表示要移动的字节数（正数向右，负数向左）。From变量指定开始移动字节的参考位置。如果from被设为0，将从文件的开头作为移动字节的参考位置。如果设为1，则使用当前的位置作为参考位置。如果它被设为2，那么该文件的末尾将作为参考位置。#!/usr/bin/python # -*- coding: UTF-8 -*- # 打开一个文件 f = open("data.txt", "r+") str = f.read(30) print ("读取的字符串是 : ", str) # 查找当前位置 position = f.tell() print ("当前文件位置 : ", position) # 把指针再次重新定位到文件开头 position = f.seek(0, 0) str = f.read(10) print ("重新读取字符串 : ", str) # 关闭打开的文件 f.close()读取的字符串是 : Our wills unite like a fortres 当前文件位置 : 30 重新读取字符串 : Our wills f = open('test.txt','w') f.write('The best way to learn Englihs is using it.') f.close()f = open('test.txt','a') f.write('\nThe best way to learn Englihs is to use it.') f.close()（二）按行对文件进行读写writelines()方法指定一个字符串类型的列表，然后将每行文本作为单独的元素写入列表。fo = open("text.txt","w") print("读写的文件名是：", fo.name) seq = ["Hello,\n","Life is short,\n","I want to learn Python\n"] line = fo.writelines(seq) fo.close()读写的文件名是： text.txtreadline()方法该方法返回一个字符串，遇到结束符即停止，读取的内容包含结束符。f = open("text.txt") while True: lines = f.readline() if lines =='': break else: print(lines) f.close()Hello, Life is short, I want to learn Pythonreadlines()方法从文件指针当前的位置读取后面的所有数据，并将这些数据按照行结束符分隔后，放到列表中返回。f = open("text.txt") data = f.readlines() print(data) f.close()['Hello,\n', 'Life is short,\n', 'I want to learn Python\n']总结：read([size])：从文件当前位置读取size个字节，若无参数，则表示读取至文件结束。readline():该方法每次读取一行内容。readlines()：读取整个文件所有行，并保存在一个列表变量中。
一、定义定义：面向对象编程——Object Oriented Programming，简称OOP，是一种程序设计思想。OOP把对象作为程序的基本单元，一个对象包含了数据和操作数据的函数。面向过程的程序设计把计算机程序视为一系列的命令集合，即一组函数的顺序执行。面向过程把函数继续切分为子函数，即把大块函数通过切割成小块函数来降低系统的复杂度。面向对象的程序设计把计算机程序视为一组对象的集合，而每个对象都可以接收其他对象发过来的消息，并处理这些消息，计算机程序的执行就是一系列消息在各个对象之间传递。举一例说明面向过程和面向对象在程序流程上的不同，假设我们要处理学生的成绩表，为了表示一个学生的成绩：# 面向过程的程序可以用一个dict表示 std1 = {'name':'Michael','score':98} std2 = {'name':'Bob','score':81} # 处理学生成绩可以通过函数实现，比如打印学生的成绩 def print_score(std): print('%s:%s' % (std['name'], std['score'])) print_score(std1) print_score(std2)Michael:98 Bob:81如果采用面向对象的程序设计思想，首先思考的不是程序的执行流程，而是Student这种数据类型应该被视为一个对象，这个对象拥有name和score这两个属性（Property）。如果要打印一个学生的成绩，首先必须创建出这个学生对应的对象，然后，给对象发一个print_score消息，让对象自己把自己的数据打印出来。class Student(object): def __init__(self, name, score): self.name = name self.score = score def print_score(self): print('%s: %s' % (self.name, self.score)) bart = Student('Bart Simpson', 59) lisa = Student('Lisa Simpson', 87) bart.print_score() lisa.print_score()Bart Simpson: 59 Lisa Simpson: 87二、类（Class）和实例（Instance）（一）定义类是抽象的模板，比如Student类，而实例是根据类创建出来的一个个具体的“对象”，每个对象都拥有相同的方法，但各自的数据可能不同。在Python中，通过class关键字定义类：class Student(object): # passclass后面紧接着是类名，即Student，类名通常是大写开头的单词，紧接着是(object)，表示该类是从哪个类继承下来的。定义好了Student类，就可以根据Student类创建出Student的实例，创建实例是通过类名+()实现的：bart = Student() bart<__main__.Student at 0x1ee22272dd0>变量bart指向的就是一个Student的实例，后面的0x10a67a590是内存地址，每个object的地址都不一样；Student__main__.StudentStudent本身则是一个类。可以自由地给一个实例变量绑定属性，比如，给实例bart绑定一个name属性：bart.name = 'Bart Simpson' bart.name'Bart Simpson'由于类可以起到模板的作用，因此，可以在创建实例的时候，把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法，在创建实例的时候，就把name，score等属性绑上去：class Students(object): def __init__(self, name, score): self.name = name self.score = score注意到__init__方法的第一个参数永远是self，表示创建的实例本身，因此，在__init__方法内部，就可以把各种属性绑定到self，因为self就指向创建的实例本身。有了__init__方法，在创建实例的时候，就不能传入空的参数了，必须传入与__init__方法匹配的参数，但self不需要传，Python解释器自己会把实例变量传进去：bart = Students('Bart Simpson', 59) bart.name'Bart Simpson'bart.score59和普通的函数相比，在类中定义的函数只有一点不同，就是第一个参数永远是实例变量self，并且调用时不用传递该参数。此外，可以用默认参数、可变参数、关键字参数和命名关键字参数。(二)数据的封装面向对象编程的一个重要特点就是数据封装。在上面的Student类中，每个实例就拥有各自的name和score这些数据。我们可以通过函数来访问这些数据，比如打印一个学生的成绩：def print_score(std): print('%s:%s' % (std.name, std.score)) print_score(bart)Bart Simpson:59Student实例本身就拥有这些数据，要访问这些数据，就没有必要从外面的函数去访问，可以直接在Student类的内部定义访问数据的函数，这样，就把“数据”给封装起来了。这些封装数据的函数是和Student类本身是关联起来的，我们称之为类的方法：class Student(object): def __init__(self, name, score): self.name = name self.score = score def print_score(self): print('%s: %s' % (self.name, self.score)) bart = Student('Bart Simpson', 59) bart.print_score()Bart Simpson: 59封装的另一个好处是可以给Student类增加新的方法，比如get_grade：class Student(object): def __init__(self, name, score): self.name = name self.score = score def print_score(self): print('%s: %s' % (self.name, self.score)) def get_grade(self): if self.score >= 90: return 'A' elif self.score >= 60: return 'B' else: return 'C' lisa = Student('Lisa', 99) bart = Student('Bart', 59) print(lisa.name, lisa.get_grade()) print(bart.name, bart.get_grade())Lisa A Bart C总结： 类是创建实例的模板，而实例则是一个一个具体的对象，各个实例拥有的数据都互相独立，互不影响；方法就是与实例绑定的函数，和普通函数不同，方法可以直接访问实例的数据；通过在实例上调用方法，我们就直接操作了对象内部的数据，但无需知道方法内部的实现细节。和静态语言不同，Python允许对实例变量绑定任何数据，也就是说，对于两个实例变量，虽然它们都是同一个类的不同实例，但拥有的变量名称都可能不同：bart = Student('Bart Simpson', 59) lisa = Student('Lisa Simpson', 87) bart.age = 8 lisa.age--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) Cell In[41], line 4 2 lisa = Student('Lisa Simpson', 87) 3 bart.age = 8 ----> 4 lisa.ageAttributeError: 'Student' object has no attribute 'age'三、访问限制从前面Student类的定义来看，外部代码可以自由地修改一个实例的name、score属性：class Student(object): def __init__(self, name, score): self.name = name self.score = score bart = Student('Bart Simpson', 59) bart.score59bart.score = 99 bart.score99如果要让内部属性不被外部访问，可以把属性的名称前加上两个下划线__。在Python中，实例的变量名如果以__开头，就变成了一个私有变量，只有内部可访问：class Student(object): def __init__(self, name, score): self.__name = name self.__score = score def print_score(self): print('%s:%s' % (self.__name, self.__score)) bart = Student('Bart Simpson', 59) bart.__name--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) Cell In[6], line 11 8 print('%s:%s' % (self.__name, self.__score)) 10 bart = Student('Bart Simpson', 59) ---> 11 bart.__nameAttributeError: 'Student' object has no attribute '__name'这样即可确保外部代码不能随意修改对象内部的状态。如果需要外部代码获取name和score，可以给Student类增加get_name和get_score方法：class Student(object): def __init__(self, name, score): self.__name = name self.__score = score def get_name(self): return self.__name def get_score(self): return self.__score bart = Student('Bart Simpson', 59) a = Student.get_name(bart) b = Student.get_score(bart) print(a,':',b)Bart Simpson : 59如果要允许外部代码修改score，可以再给Student类增加set_score方法：class Student(object): def __init__(self, name, score): self.__name = name self.__score = score def get_name(self): return self.__name def get_score(self): return self.__score def set_score(self, score): # 在定义方法实，可以对参数做检查，避免传入无效参数 if 0 <= score <= 100: self.__score = score else: raise ValueError('bad score') bart = Student('Bart Simpson', 59) bart.set_score(87) print(bart.get_score())87在Python中，变量名类似__xxx__的，也就是以双下划线开头，并且以双下划线结尾的，是特殊变量，特殊变量是可以直接访问的，不是private变量；下划线开头的实例变量名，比如_name，外部是可以访问的，但按照约定俗成的规定，要视为私有变量，不要随意访问；双下划线开头的实例变量也不是一定不能从外部访问，Python解释器对外把__name变量改成了_Student__name，所以，仍然可以通过_Student__name来访问__name变量；但不同版本的Python解释器可能会把__name改成不同的变量名，因此不要这样访问变量名。一种错误写法示例：>>> bart = Student('Bart Simpson', 59) >>> bart.get_name() 'Bart Simpson' >>> bart.__name = 'New Name' # 设置__name变量！ >>> bart.__name 'New Name'表面上看，外部代码“成功”地设置了__name变量，但实际上这个__name变量和class内部的__name变量不是一个变量！内部的__name变量已经被Python解释器自动改成了_Student__name，而外部代码给bart新增了一个__name变量。四、继承和多态（一）继承当定义一个class的时候，可以从某个现有的class继承，新的class称为子类，被继承的class被称为父类。比如一个名为Animal的class，有一个run()方法可以打印：class Animal(object): def run(self): print('Animal is running...')当需要编写Dog和Cat类时，就可以直接从Animal类继承：class Dog(Animal): pass class Cat(Animal): pass通过继承，子类获得了父类的全部功能：dog = Dog() dog.run() cat = Cat() cat.run()Animal is running... Animal is running...同时可以给子类添加一些方法：class Dog(Animal): def run(self): print('Dog is running...') def eat(self): print('Eating meat...')可以对代码做一些改进：class Dog(Animal): def run(self): print('Dog is running...') class Cat(Animal): def run(self): print('Cat is running...') dog = Dog() dog.run() cat = Cat() cat.run()Dog is running... Cat is running...当子类和父类存在相同的run()方法时，子类的run()覆盖了父类的run()，在运行代码的时候，会优先调用子类的run()。（二）多态a = list() #a是list类型 b = Animal() #b是Animal类型 c = Dog() #c是Dog()类型可以使用isinstance()判断变量的类型：isinstance(a, list)Trueisinstance(b, Animal)Trueisinstance(c,Dog)TrueDog从Animal继承，因此Dog的实例c的数据类型即是Dog，也是Animal。isinstance(c, Animal)True总结：在继承关系中，如果一个实例的数据类型是某个子类，那么它的数据类型可以被看做是父类。def run_twice(animal): animal.run() animal.run() run_twice(Animal())Animal is running... Animal is running...run_twice(Cat())Cat is running... Cat is running...定义一个Tortoise类型，也从Animal派生：class Tortoise(Animal): def run(self): print('Tortoise is running slowly...')run_twice(Tortoise())Tortoise is running slowly... Tortoise is running slowly...新增一个Animal的子类，不必对run_twice()做任何修改。传入的任意类型，只要是Animal类或者子类，就会自动调用实际类型的run()方法。开闭原则：对扩展开放：允许新增Animal子类；数。对修改封闭：不需要修改依赖Animal类型的run_twice()等函数。总结：继承可以把父类的所有功能都直接拿过来，不必重零做起，子类只需要新增自己特有的方法，也可以把父类不适合的方法覆盖重写。五、获取对象信息判断对象的类型可以使用以下办法：（一）使用type()type(123)inttype('str')strtype(None)NoneType如果一个变量指向一个函数或者类，也可以使用type()判断：type(abs)builtin_function_or_method如果要在if语句中判断，就需要比较两个变量的type类型是否相同：type(123) == type(456)Truetype(123) == intTruetype('abc') == intFalsetype('abc') == type('123')Truetype('abc') == strTruetype('abc') == type(123)False如果要判断一个对象是否是函数怎,可以使用types模块中定义的常量：import types def fn(): passtype(fn) == types.FunctionTypeTruetype(abs) == types.BuiltinFunctionTypeTruetype(lambda x:y) == types.LambdaTypeTruetype((x for x in range(10))) == types.GeneratorTypeTrue（二）使用isinstance()对于class的继承关系来说，使用type()不方便，要判断class的类型可以使用isinstance()函数。如果继承关系是object -> Animal -> Dog -> Husky，那么通过isinstance()可以判断一个对象是否是某种类型：class Animal(object): pass class Dog(Animal): pass class Husky(Dog): pass a = Animal() d = Dog() h = Husky()isinstance(h, Husky)Trueisinstance(h, Dog)Trueisinstance(d, Husky)Falseisinstance()判断的是一个对象是否是该类型本身，或者位于该类型的父继承链上。能用type()判断的基本类型也可以用isinstance()判断。isinstance('a', str)Trueisinstance(123, int)Trueisinstance(b'a', bytes)True还可以判断一个变量是否是某些类型中的一种：isinstance([1, 2, 3], (list, tuple))Trueisinstance((1, 2, 3), (list, tuple))True（三）使用dir()如果要获得一个对象的所有属性和方法，可以使用dir()函数，它返回一个包含字符串的list。比如，获得一个str对象的所有属性和方法：dir('ABC')['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'removeprefix', 'removesuffix', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']类似__xxx__的属性和方法在Python中都是有特殊用途的，比如__len__方法返回长度。在Python中，如果你调用len()函数试图获取一个对象的长度，实际上，在len()函数内部，它自动去调用该对象的__len__()方法，所以，下面的代码是等价的：len('ABC')3 'ABC'.__len__()3自己写的类，如果也想用len(myObj)可以写一个__len__()方法：class MyDog(object): def __len__(self): return 100 dog = MyDog() len(dog)100配合getattr()、setattr()以及hasattr()可以直接操作一个对象的状态：class MyObject(object): def __init__(self): self.x = 9 def power(self): return self.x * self.x obj = MyObject()配合getattr()、setattr()以及hasattr()，可以直接操作一个对象的状态：测试对象属性：属性name，则返回False。getattr(object, name[, default]):name，则返回False。getattr()函数用于获取对象的属性值。它接受三个参数：对象object、属性名name和可选的默认值default。如果对象object具有属性name，则返回该属性的值；如果对象object没有属性name，则返回默认值default（如果提供了默认值）；如果没有提供默认值，则会抛出AttributeError异常。setattr(object, name, value):setattr()函数用于设置对象的属性值。它接受三个参数：对象object、属性名name和属性值value。如果对象object具有属性name，则将其设置为value；如果对象object没有属性name，则会创建一个新的属性，并将其设置为value。hasattr(object, name):hasattr()函数用于检查对象是否具有指定的属性。它接受两个参数：对象object和属性名name。如果对象object具有属性name，则返回True；如果对象object没有属性name，则返回False。hasattr(obj, 'x') # 有属性'x'吗？Trueobj.x9hasattr(obj, 'y') # 有属性'y'吗？Falsesetattr(obj, 'y', 19) # 设置一个属性'y'hasattr(obj, 'y') # 有属性'y'吗？Truegetattr(obj, 'y') # 获取属性'y'19obj.y # 获取属性'y'19如果试图获取不存在的属性，会抛出AttributeError的错误：getattr(obj, 'z') # 获取属性'z'--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) Cell In[52], line 1 ----> 1 getattr(obj, 'z') # 获取属性'z'AttributeError: 'MyObject' object has no attribute 'z'可以传入一个default参数，如果属性不存在，就返回默认值：getattr(obj, 'z', 404) # 获取属性'z'，如果不存在，返回默认值404404也可以获得对象的方法：hasattr(obj, 'power') # 有属性'power'吗？Truegetattr(obj, 'power') # 获取属性'power'<bound method MyObject.power of <__main__.MyObject object at 0x000002A390FEE170>>fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn fn # fn指向obj.power<bound method MyObject.power of <__main__.MyObject object at 0x000002A390FEE170>>fn() # 调用fn()与调用obj.power()是一样的81（六）实例属性和类属性Python是动态语言，根据类创建的实例可以任意绑定属性。给实例绑定属性的方法是通过实例变量，或者通过self变量：class Student(object): def __init__(self,name): self.name = name s = Student('Bob') s.score = 90如果Student类本身需要绑定一个属性，可以直接在class中定义属性，这种属性是类属性，归Student类所有：class Student(object): name = 'Student'定义了一个类属性后，这个属性虽然归类所有，但类的所有实例都可以访问到。class Student(object): name = 'Student' s = Student() print(s.name) # 打印name属性，因为实例并没有name属性，所以会继续查找class的name属性Student print(Student.name) # 打印类的name属性Student s.name = 'Michael' # 给实例绑定name属性 print(s.name) # 由于实例属性优先级比类属性高，因此，它会屏蔽掉类的name属性Michaelprint(Student.name) # 但是类属性并未消失，用Student.name仍然可以访问Studentdel s.name # 如果删除实例的name属性 print(s.name) # 再次调用s.name，由于实例的name属性没有找到，类的name属性就显示出来了Student总结： 在编写程序的时候，不要对实例属性和类属性使用相同的名字，因为相同名称的实例属性将屏蔽掉类属性，但是当删除实例属性后，再使用相同的名称，访问到的将是类属性。
一、模块的基本概念模块：在Python中，一个.py文件就称之为一个模块（Module）使用模块的好处：（1）提高了代码的可维护性。（2）编写代码不必从零开始。（3）使用模块还可以避免函数名和变量名冲突。注意：尽量不要与Python的内建函数冲突。Python内建函数介绍：https://docs.python.org/3/library/functions.html二、包的基本概念为了避免模块名冲突，引入按目录组织模块，避免冲突。方法是选择一个顶层的包名，比如mycompany，按照如下目录存放：mycompany ├─ __init__.py ├─ abc.py └─ xyz.py引入包名后，只要顶层的包名不与别人冲突，那么所有模块都不会与别人冲突。abc.py模块的名称就变为了mycompany.abc，xyz.py的模块名就变为了mycompany.xyz。每一个包名目录下都会有一个__init__.py文件，这个文件必须存在，否则Python就会把这个目录当做普通目录，而不是一个包。__init__.py文件可以是空文件，也可以有Python代码。它本身就是一个模块，而它的模块名就是mycompany类似的，可以有多级目录，组成多层级的包结构。mycompany ├─ web │ ├─ __init__.py │ ├─ utils.py │ └─ www.py ├─ __init__.py ├─ abc.py └─ utils.py文件www.py的模块名就是mycompany.web.www，两个文件utils.py的模块名分别是mycompany.utils和mycompany.web.utils。总结：模块是一组Python代码的集合，可以使用其他模块，也可以被其他模块使用。在此模块。创建自己的模块时，要注意：模块名要遵循Python变量命名规范，不要使用中文、特殊字符；模块名不要和系统模块名冲突，最好先查看系统是否已存在该模块，检查方法是在Python交互环境执行import abc，若成功则说明系统存在此模块。三、使用模块以内建的sys为例，写一个hello的模块。#!/user/bin/env python3 #第一行注释让hello.py文件可以直接在Unix/Linux/Mac上运行 # -*- coding: utf-8 -*- #第二行注释表示.py文件本身使用UTF-8编码 'a test module' # 字符串，表示模块的文档注释。任何模块代码的第一个字符串都被视为模块的文档注释 __author__ = 'Bruce Wang' # __author__变量把作者写进去 #以上是一个Python模块的标准文件模板，也可以全部删掉不写。 # 后面是真正的代码 import sys def test(): args = sys.argv if len(args) == 1: print("Hello, world!") elif len(args) == 2: print("Hello, %s!" % args[1]) else: print("Too many arguments!") if __name__ == '__main__': test()Too many arguments!四、作用域类似__xxx__这样的变量（__author__，__name__）是特殊变量，可以被直接引用但是有特殊用途，我们自己的变量一般不要用这种变量名；类似_xxx和__xxx这样的函数或变量是非公开的（private），不应该被直接引用。def _private_1(name): return 'Hello, %s' % name def _private_2(name): return 'Hi, %s' % name def greeting(name): if len(name) > 3: return _private_1(name) else: return _private_2(name) print(greeting("Bruce"))Hello, Bruce我们在模块里公开greeting()函数，而把内部逻辑用private函数隐藏起来了，这样，调用greeting()函数不用关心内部的private函数细节，这也是一种非常有用的代码封装和抽象的方法，即：外部不需要引用的函数全部定义成private，只有外部需要引用的函数才定义为public。五、模块搜索路径加载一个模块时，Python会在指定的路径下搜索对应的.py文件，默认情况下Python解释器会搜索(1)当前目录;（2）所有已安装的内置模块（3）第三方模块。搜索路径存放在sys模块的path变量中：import sys sys.path['D:\\pythonProject\\05 模块', 'C:\\Users\\Bruce\\AppData\\Local\\Programs\\Python\\Python310\\python310.zip', 'C:\\Users\\Bruce\\AppData\\Local\\Programs\\Python\\Python310\\DLLs', 'C:\\Users\\Bruce\\AppData\\Local\\Programs\\Python\\Python310\\lib', 'C:\\Users\\Bruce\\AppData\\Local\\Programs\\Python\\Python310', '', 'C:\\Users\\Bruce\\AppData\\Local\\Programs\\Python\\Python310\\lib\\site-packages', 'C:\\Users\\Bruce\\AppData\\Local\\Programs\\Python\\Python310\\lib\\site-packages\\win32', 'C:\\Users\\Bruce\\AppData\\Local\\Programs\\Python\\Python310\\lib\\site-packages\\win32\\lib', 'C:\\Users\\Bruce\\AppData\\Local\\Programs\\Python\\Python310\\lib\\site-packages\\Pythonwin']
偏函数可以通过设定参数的默认值，降低函数调用的难度。functools.partial就是帮助我们创建一个偏函数。import functools int2 = functools.partial(int, base = 2) int2('1000')8这个函数等价于：# 转换二进制字符串 # int函数默认将字符串转换为整数，base默认为10。如果传入base参数，就可以做n进制转换。 def int2(x, base = 2): return int(x, base) int2('1000')8总结：functools.partial函数作用是将一个函数的某个参数给固定住（也就是设置一个默认值），返回一个新的函数，调用这个新函数会更加简单。在int2函数中，仅仅把base参数重新设置为默认值2，但也可以在函数调用时传入其他参数。int2('1234', base = 8)668创建偏函数的时候，可以接受函数对象、*args、**kw这三个参数，当传入int2 = functools.partial(int, base = 2),就固定住了int函数的关键字参数base。int2('10010')18kw = {'base':2} int('10010',**kw)18当传入：max2 = functools.partial(max, 10)10会作为*args的一部分自动加到左边。也就是：max2(5,6,7)10总结：当函数参数太多需要简化的时候，可以使用functools.partial创建一个新的函数，这个函数可以固定住原函数的部分参数，从而在调用的时候更加简单。
装饰器（decorators）用于拓展原来函数功能的一种函数，目的是在不改变原来函数名（或类名）的情况下，给函数增加新功能。在Python中，装饰器的语法以@开头。装饰器主要应用于：引入日志、函数执行时间统计、执行函数前的预备处理、执行函数后清理功能、权限校验、缓存。【案例1】利用装饰器实现统计函数的执行时间import time def timeit(func): #func()为装饰器绑定的方法，绑定装饰器后自动传入 def result(): start_time = time.time() func() end_time = time.time() print('函数运行时间为：%.2fs'%(end_time - start_time)) return result @timeit def func_0(): time.sleep(3) @timeit def func_1(): time.sleep(3) func_0() func_1()函数运行时间为：3.01s 函数运行时间为：3.01s【案例2】无参数的装饰器应用import time def timeit(func): def result(): start_time = time.time() func() #调用test方法 end_time = time.time() print('函数运行时间为：%.2fs'%(end_time - start_time)) return result @timeit def test(): time.sleep(1) print("测试函数运行") test()测试函数运行 函数运行时间为：1.00s装饰器@timeit会将test()函数进行包装，实际上调用的是result()函数。在result()函数中，先调用了func()，也就是test()函数，然后再计算函数的运行时间并输出。因此，"测试函数运行"会先输出，然后才会输出"函数运行时间为：1.00s"。【案例3】有参数的装饰器应用import time def timeit(func): def result(arg1): #调用test方法的参数 start_time = time.time() func(arg1) #调用test方法 end_time = time.time() print('函数运行时间为：%.2fs'%(end_time - start_time)) return result @timeit def test(mStr): time.sleep(2) print("函数运行时间：" + mStr) test("传入参数")函数运行时间：传入参数 函数运行时间为：2.00s【案例4】定义一个打印日志的装饰器def log(text): def decorator(func): def wrapper(*args, **kw): print('%s %s():' % (text, func.__name__)) return func(*args, **kw) return wrapper return decorator @log('execute') def now(): print('2015-3-25') now()execute now(): 2015-3-25上面的函数，首先执行log('execute')，返回的是decorator函数，再调用返回的函数，参数是now函数，返回值最终是wrapper函数。函数也是对象，它有__name__等属性，但经过decorator装饰之后的函数，它们的__name__已经从原来的'now'变成了'wrapper'：now.__name__'wrapper'因为返回的wrapper()函数名字是'wrapper'，所以，需要把原始函数的__name__等属性复制到wrapper()函数中，否则，有些依赖函数签名的代码执行就会出错。下：不需要编写wrapper.__name__ = func.__name__这样的代码，Python内置的functools.wraps即可实现，所以，一个完整的decorator的写法如下：import functools def log(text): def decorator(func): @functools.wraps(func) # wrapper()的前面加上@functools.wraps(func) def wrapper(*args, **kw): print('%s %s():' % (text, func.__name__)) return func(*args, **kw) return wrapper return decorator【案例5】装饰器可以修饰带参数函数，也可以修饰不带参数函数import time def timeit(func): def result(*arg1,**kwargs): #传入非固定参数，这样即使装饰器函数不带参数也可以被装饰 start_time = time.time() func(*arg1,**kwargs) #调用test方法 end_time = time.time() print('函数运行时间为：%.2fs'%(end_time - start_time)) return result @timeit def test(): time.sleep(2) print("函数运行") test() #调用函数函数运行 函数运行时间为：2.02s【例题】设计一个decorator，可作用于任何函数上，并打印该函数的执行时间：import time, functools def metric(fn): @functools.wraps(fn) def result(*args,**kwargs): start_time = time.time() res = fn(*args, **kwargs) end_time = time.time() print('%s executed in %.fms' % (fn.__name__, (end_time - start_time) * 1000)) return res # 将fn(*args, **kwargs)的返回值返回给调用者 return result # 测试 @metric def fast(x, y): time.sleep(0.0012) return x + y; @metric def slow(x, y, z): time.sleep(0.1234) return x * y * z; f = fast(11, 22) s = slow(11, 22, 33) if f != 33: print('测试失败!') elif s != 7986: print('测试失败!')fast executed in 11ms slow executed in 135ms
一、函数作为返回值高阶函数除了可以接受函数作为参数外，还可以把函数作为结果值返回。实现一个可变参数的求和：# 可变参数求和 def calc_sum(*args): ax = 0 for n in args: ax = ax + n return ax如果不需要立即求和，而是在后面的代码中根据需要计算，可以不返回求和结果，而是返回求和函数。def lazy_sum(*args): def sum(): ax = 0 for n in args: ax = ax + n return ax return sum f = lazy_sum(1,3,5,7,9) #调用函数f，结果是<function __main__.lazy_sum.<locals>.sum()> f<function __main__.lazy_sum.<locals>.sum()># 调用函数f时，才真正求和结果 f()25调用lazy()函数时，每次调用都会返回一个新的函数，即使传入相同的参数。f1 = lazy_sum(1, 3, 5, 7, 9) f2 = lazy_sum(1, 3, 5, 7, 9) f1 == f2Falsef1() == f2() #返回的值相同，评估为 TrueTrue二、闭包（1）在函数嵌套（函数里面再定义函数）的前提下（2）内部函数使用了外部函数的变量（还包括了外部函数的参数）（3）外函数的返回值是内函数的引用返回的函数并没有被立即执行，而是直到调用了f()才执行。def count(): fs = [] for i in range(1,4): def f(): return i * i fs.append(f) return fs f1, f2, f3 = count() f1()9f2()9f3()9三个返回结果全部都是9，原因就在于返回的函数引用了变量i，但它并非立刻执行。等到3个函数都返回时，它们所引用的变量i已经变成了3，因此最终结果为9。注意：返回闭包时牢记一点：返回函数不要引用任何循环变量，或者后续会发生变化的变量。如果要引用循环变量，需要再创建一个函数，用该函数的参数绑定循环变量当前的值，无论该循环变量后续如何更改，已绑定到函数参数的值不变。def count(): def f(j): def g(): return j * j return g fs = [] for i in range(1,4): fs.append(f(i)) # f(i)立即被执行，因此i的当前值被传入f() return fs1f1, f2, f3 = count() f1()4f2()4f3()9如果对外层变量赋值，由于Python解释器会把x当作函数fn()的局部变量，它会报错:def inc(): x = 0 def fn(): # nonlocal x x = x + 1 return x return fn f = inc() print(f()) # 1 print(f()) # 2 # 在x = x + 1的表达式中，Python解释器会认为x是一个局部变量，但是在此之前没有对x进行赋值操作，所以会报错。 # 要解决这个问题，可以在fn函数中加上nonlocal关键字，将x声明为非局部变量，这样就可以对x进行赋值操作。修改后的代码如下： # def fn(): # nonlocal x--------------------------------------------------------------------------- UnboundLocalError Traceback (most recent call last) Cell In[44], line 10 7 return fn 9 f = inc() ---> 10 print(f()) # 1 11 print(f()) # 2Cell In[44], line 5, in inc.<locals>.fn() 3 def fn(): 4 # nonlocal x ----> 5 x = x + 1 6 return xUnboundLocalError: local variable 'x' referenced before assignment使用闭包，就是内层函数引用了外层函数的局部变量。如果只是读外层变量的值，我们会发现返回的闭包函数调用一切正常：def inc(): x = 0 def fn(): # 仅读取x的值: return x + 1 return fn f = inc() print(f()) # 1 print(f()) # 11 1小结变量。一个函数可以返回一个计算结果，也可以返回一个函数。返回一个函数时，牢记该函数并未执行，返回函数中不要引用任何可能会变化的变量。练习：利用闭包返回一个计数器函数，每次调用它返回递增整数：def createCounter(): a = 0 def counter(): nonlocal a a += 1 return a return counter f1 = createCounter() print(f1()) print(f1()) print(f1())1 2 3
一、高阶函数基础（一）、变量可以指向函数abs<function abs(x, /)>abs(-10)10x = abs(-10) print(x)10f = abs print(f)<built-in function abs># 变量f指向了一个函数 # f = abs # print(f(-10))（二）、函数名也是变量# abs指向数值后，就无法 # abs = 10 # abs(-10)（三）、传入函数变量可以指向函数，函数的参数能接受变量，那么一个函数的变量就能指向另一个函数。这种函数即为高阶函数。def add(x,y,f): return f(x) + f(y) add(-5,5,abs)10二、Map函数map函数接受两个参数，一个是函数，一个是Iterable(可迭代对象)，map将传入的函数依次作用于序列中的每个元素，并把结果作为新的Iterator返回。def f(x): return x * x r = map(f,[1,2,3,4,5,6,7,8,9,10]) list(r) # 输出结果：[1, 4, 9, 16, 25, 36, 49, 64, 81, 100][1, 4, 9, 16, 25, 36, 49, 64, 81, 100]上面这个例子也可以使用循环实现：def f(x): return x * x L =[] for i in range(1,11): L.append(f(i)) print(L)[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]但用循环的缺点在于稍微有些抽象，不如直接用map函数形象。三、reduce函数reduce函数把一个函数作用在一个序列[x1, x2, x3, ...]上，这个函数必须接收两个参数，reduce()函数把结果和序列的下一个元素做累计计算。from functools import reduce def add(x,y): return x + y reduce(add, [1,3,5,7])16将序列[1,3,5,7]变换为整数1357from functools import reduce def str2int(x,y): return x *10 + y reduce(str2int, [1,3,5,7])1357将str转换为int函数from functools import reduce def str2int(x,y): return x *10 + y def convert(i): digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9} return digits[i] reduce(str2int, map(convert, '1357'))1357整理为一个str2int函数：from functools import reduce digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9} def str2int(n): def s2t(x,y): return x *10 + y def convert(n): return digits[n] return reduce(s2t, map(convert, n)) str2int('255')255使用lambda函数可以进一步简化：from functools import reduce digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9} def convert(i): return digits[i] def str2int(i): return reduce(lambda x, y: x * 10 + y, map(convert, i)) #使用lambda x, y: x * 10 + y替换了str2int(n)函数 str2int('2566')2566利用map()函数，把用户输入的不规范的英文名字，变为首字母大写，其他小写的规范名字。输入：['adam', 'LISA', 'barT']，输出：['Adam', 'Lisa', 'Bart']：def normalize(names): return names[0].upper() + names[1:].lower() names = ['adam', 'LISA', 'barT'] list(map(normalize, names))['Adam', 'Lisa', 'Bart']请编写一个prod()函数，可以接受一个list并利用reduce()求积：# def prod(*num): # n = 1 # for i in L: # n = n * i # return n # L = [1,2,3,4] # print(prod(*L)) def prod(num): return reduce(lambda x, y: x*y, num) L = [1,2,3,4] print(prod(L))24利用map和reduce编写一个str2float函数，把字符串'123.456'转换成浮点数123.456：from functools import reduce def str2float(s): # 获取小数点的索引位置 period_index = s.index(".") # 获取小数点左侧的字符 left_char = s[:period_index] # 获取小数点右侧的字符 right_char = s[period_index + 1:] # 定义字符串转数字的函数convert() def convert(i): digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9} return digits[i] # 转换小数点左侧数字 left_sum = reduce(lambda x,y: x * 10 + y, map(convert, left_char)) # 转换小数点右侧数字 mid_num = reduce(lambda x,y: x * 10 + y, map(convert, right_char)) right_sum = mid_num / 10 ** len(right_char) return left_sum + right_sum print(str2float('123.456'))python123.456四、filter函数类似map()，filter()接收一个函数和序列，将传入的函数依次作用于每个元素，然后根据返回时True还是False决定保留还是丢弃该元素。例如在一个list中，删掉偶数，只保留奇数，可以这么写：def is_odd(n): return n % 2 == 1 list(filter(is_odd, [1,2,4,5,6,9,10,15]))[1, 5, 9, 15]把一个序列中的空字符串删掉，可以这么写：def not_empty(s): return s and s.strip() list(filter(not_empty,['A','','B', None, 'C',' ']))['A', 'B', 'C']filter()函数返回的是一个Iterator，是一个惰性序列，因此要强迫filter()完成计算结果，就需要用list()函数获得所有的结果并返回list.# 筛选素数 # 构造一个从3开始的奇数序列 def _odd_iter(): n = 1 while True: n = n + 2 yield n #定义一个筛选函数 def _not_divisible(n): return lambda x: x % n > 0 # 定义一个生成器，不断返回下一个素数： def primes(): yield 2 it = _odd_iter() # 初始序列 while True: n = next(it) # 返回序列的第一个数 yield n it = filter(_not_divisible(n), it) # 构造新序列 # primes()是一个无限序列，所以调用的时候需要设置一个退出循环条件： # 打印100以内的素数 for n in primes(): if n < 100: print(n) else: break2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97三、Sorted函数soted()函数是Python内置函数，可以对list进行排序。sorted([36,5,-12,9,21])[-12, 5, 9, 21, 36]key指定的函数将作用于list的每一个元素上，并根据key函数返回的结果进行排序。对比原始的list和key = abs处理过list = [36, 5, -12, 9, -21] key = [36, 5, 12, 9, 21]默认情况下，对字符串排序，是按照ASCII的大小比较的，由于'Z' < 'a'，结果，大写字母Z会排在小写字母a的前面。sorted(['bob', 'about', 'Zoo', 'Credit'])['Credit', 'Zoo', 'about', 'bob']忽略大小写，按照字母序排序。用一个key函数把字符串映射为忽略大小写排序即可。忽略大小写来比较两个字符串，实际上就是先把字符串都变成大写（或者都变成小写），再比较。sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)['about', 'bob', 'Credit', 'Zoo']反向排序，不必改动key函数，可以传入第三个参数reverse=Truesorted(['bob','about','Zoo','Credit'], key = str.lower, reverse = True)['Zoo', 'Credit', 'bob', 'about']小结：sorted()函数排序的关键在于实现一个映射函数。# 请用sorted()对上述列表分别按名字排序 L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)] sorted_list = sorted(L, key = lambda x: x[0]) print(sorted_list)[('Adam', 92), ('Bart', 66), ('Bob', 75), ('Lisa', 88)]# 请用sorted()对上述列表分别按成绩排序 L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)] sorted_list = sorted(L, key = lambda x: x[1]) print(sorted_list)[('Bart', 66), ('Bob', 75), ('Lisa', 88), ('Adam', 92)]
一、切片L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack'] # 取前三个元素 # 如果第一个索引是0，还可以省略： L[0:3] L[:3] ['Michael', 'Sarah', 'Tracy'] # 从索引1开始，取出2个元素出来 L[1:3] ['Sarah', 'Tracy'] # Python支持倒数切片 L[-2:] ['Bob', 'Jack'] L[-2:-1] ['Bob'] S = list(range(100)) print(S) # 取前10个数 print(S[0:10]) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # 取后10个数 print(S[-10:]) # [90, 91, 92, 93, 94, 95, 96, 97, 98, 99] # 前11-12个数 print(S[11:22]) # [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21] # 前10个，每两个取一个 print(S[0:10:2]) print(S[:10:2]) # [0, 2, 4, 6, 8] # 所有数，每5个取一个 print(S[:100:5]) # [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95] # 复制一个list print(S[:]) T = (1,2,3,4,5,6,7,8,9,10) print(T[:3]) # (1, 2, 3) # 字符串也可以看做成一个list Str = 'ABCDEFG' print(Str[0:3]) # ABC# 利用切片操作，实现一个trim()函数，去除字符串首尾的空格 def trim(s): start = 0 end = len(s) # 找到字符串首部的空格 while start < end and s[start] == ' ': start += 1 # 找到字符串尾部的空格 while end > start and s[end-1] == ' ': end -= 1 return s[start:end]二、迭代如果给定一个list或tuple，我们可以通过for循环来遍历这个list或tuple，这种遍历我们称为迭代（Iteration）。字典可以迭代：d = {'a':1, 'b':2,'c':3} for key in d: print(key) for value in d.values(): print(value)字符串可以迭代d = {'a':1,'b':2,'c':3} for key in d: print(key) for value in d.values(): print(value) # 同时迭代key和value for key, value in d.items() print(key,value) # 输出结果 a c b 1 2 3 a 1 b 2 c 3 ''' items()是一个字典(dict)的方法，用于返回字典中所有键值对的元组。每个元组包含两个元素，第一个元素是键(key)，第二个元素是对应的值(value)。 在for key, value in d.items()这个语句中，d是一个字典对象，items()方法会返回一个包含所有键值对的可迭代对象。通过for循环遍历这个可迭代对象，每次迭代时，将键值对解包为key和value两个变量，可以对每个键值对进行操作。 '''使用迭代查找一个list中最小和最大值，并返回一个tuple：def findMinAndMax(L): #没有初始化max_value for i in L: if i > L[0]: max_value = i if i < L[0]: min_value = i print(max_value,min_value) findMinAndMax([1,2,3,1]) # 报错 ''' Traceback (most recent call last): File "D:\pythonProject\高级特性\迭代.py", line 44, in <module> findMinAndMax([1,2,3,1]) File "D:\pythonProject\高级特性\迭代.py", line 43, in findMinAndMax print(max_value,min_value) UnboundLocalError: local variable 'min_value' referenced before assignment ''' # 解释报错原因 ''' 在您提供的代码中，max_value和min_value在某些条件下才会被赋值。如果这些条件从未满足，这些变量将不会被初始化，导致UnboundLocalError。 在您的代码中，由于列表[1,2,3]中的所有元素都不会小于第一个元素（L[0]），min_value从未被赋值。 此外，即使这些条件满足，这些变量也只在if语句的作用域内部有效，而不是整个函数的作用域。 为了确保这些变量在整个函数中都是可用的，您需要在循环之前初始化它们。 此外，您的比较逻辑不应该只与列表的第一个元素比较，而应该与当前已知的最大值或最小值比较。 ''' # 修复代码 def findMinAndMax(L): if not L: # 如果列表为空，返回None return (None, None) max_value = L[0] # 初始化为列表的第一个元素 min_value = L[0] # 初始化为列表的第一个元素 for i in L: if i > max_value: # 如果当前元素大于已知最大值 max_value = i if i < min_value: # 如果当前元素小于已知最小值 min_value = i print(max_value, min_value) # 在这里打印最大值和最小值 findMinAndMax([1, 2, 3])三、列表生成式# 生成[1*1,2*2,3*3,4*4,...,10*10] L =[] for i in range(1,11): L.append(i * i) print(L) # 使用列表生成器 A = [x*x for x in range(1,11)] print(A) # 使用if判断 B = [x*x for x in range(1,11) if x%2 ==0] print(B) # 使用两层循环生成全排列 C = [m+n for m in 'ABC' for n in 'XYZ'] print(C) # 使用运用列表生成式列出当前文件夹下的所有文件名称 import os D= [d for d in os.listdir('.')] # os.listdir可以列出文件和目录 print(D) # for循环可以同时使用多个变量，如dict的items()，可以同时迭代key和value dict = {'A':'X','B':'Y','C':'Z'} for key,value in dict.items(): print(key, '=', value) # 使用两个变量来生成list dict = {'A':'X','B':'Y','C':'Z'} F = [k+'='+v for k,v in dict.items()] print(F) # 将大写变成小写 L = ['Hello', 'World', 'IBM', 'Apple'] G = [l.lower() for l in L] print(G) # if...else..用法 # 示例 ''' H = [x for x in range(1,11) if x%2 == 0] print(H) ''' # 输出结果 ''' File "D:\pythonProject\高级特性\列表生成式.py", line 45 I = [x if x%2 == 0 for x in range(1,11)] ^^^^^^^^^^^^^ SyntaxError: expected 'else' after 'if' expression ''' # if写在for前面的时候，前面必须加else，否则报错 # 这是因为，for前面是一个表达式，它必须根据x计算出一个结果。表达式if x%2 == 0无法根据x计算出结果，因为缺少else J = [x if x%2 == 0 else -x for x in range(1,11)] print(J) # 在一个列表生成式中，for前面的if ... else是表达式，而for后面的if是过滤条件，不能带else四、生成器通过列表生成式，可以直接创建一个列表。但是，受到内存限制，列表容量肯定是有限的。如果列表元素可以按照某种算法推算出来，那么就可以在循环的过程中不断推算出后续的元素。在Python中，这种一边循环一边计算的机制，称为生成器：generator。生成generator有多种方法：方法一、将列表生成式的[]改为()>>> L = [x * x for x in range(10)] >>> L [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] >>> g = (x * x for x in range(10)) >>> g <generator object <genexpr> at 0x1022ef630>创建L和g的区别仅在于最外层的[]和()，L是一个list，而g是一个generator。通过next()函数获得generator的下一个返回值：>>> next(g) 0 >>> next(g) 1 >>> next(g) 4 >>> next(g) 9 >>> next(g) 16 >>> next(g) 25 >>> next(g) 36 >>> next(g) 49 >>> next(g) 64 >>> next(g) 81 >>> next(g) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIterationgenerator保存的是算法，每次调用next(g)，就计算出g的下一个元素的值，直到计算到最后一个元素，没有更多的元素时，抛出StopIteration的错误。通过for循环来迭代generator，可以不需要关心StopIteration的错误。>>> g = (x * x for x in range(10)) >>> for n in g: ... print(n) ... 0 1 4 9 16 25 36 49 64 81方法二、通过函数实现如果推算的算法比较复杂，用类似列表生成式的for循环无法实现的时候，可以用函数来实现。比如，著名的斐波拉契数列（Fibonacci），除第一个和第二个数外，任意一个数都可由前两个数相加得到：1, 1, 2, 3, 5, 8, 13, 21, 34, ...可以用函数表示为：def fib(max): n, a, b = 0, 0, 1 while n < max: print(b) a, b = b, a + b n = n + 1 return 'done'要把fib函数变成generator函数，只需要把print(b)改为yield b即可：def fib(max): n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a + b n = n + 1 return 'done'调用函数：>>> f = fib(6) >>> f <generator object fib at 0x104feaaa0>相较于普通函数，generator的函数在每次调用next()的时候执行，遇到yield语句返回，再次执行时从上次返回的yield语句处继续执行。举个简单的例子，定义一个generator函数，依次返回数字1，3，5：def odd(): print('step 1') yield 1 print('step 2') yield(3) print('step 3') yield(5)调用该generator函数时，首先要生成一个generator对象，然后用next()函数不断获得下一个返回值：>>> o = odd() >>> next(o) step 1 1 >>> next(o) step 2 3 >>> next(o) step 3 5 >>> next(o) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIterationnext()函数的一种错误用法：>>> next(odd()) step 1 1 >>> next(odd()) step 1 1 >>> next(odd()) step 1 1原因在于odd()会创建一个新的generator对象，上述代码实际上创建了3个完全独立的generator，对3个generator分别调用next()当然每个都会返回第一个值。正确的写法是创建一个generator对象，然后不断对这一个generator对象调用next()：>>> g = odd() >>> next(g) step 1 1 >>> next(g) step 2 3 >>> next(g) step 3 5五、迭代器可以使用for循环的数据类型有以下几种：一类是集合数据类型，如list、tuple、dict、set、str等；一类是generator，包括生成器和带yield的generator function。（一）使用isinstance()判断一个对象是否是Iterable对象>>> from collections.abc import Iterable >>> isinstance([], Iterable) True >>> isinstance({}, Iterable) True >>> isinstance('abc', Iterable) True >>> isinstance((x for x in range(10)), Iterable) True >>> isinstance(100, Iterable) False可以被next()函数调用并不断返回下一个值的对象称为迭代器：Iterator。（二）使用isinstance()判断一个对象是否是Iterator对象：>>> from collections.abc import Iterator >>> isinstance((x for x in range(10)), Iterator) True >>> isinstance([], Iterator) False >>> isinstance({}, Iterator) False >>> isinstance('abc', Iterator) False生成器都是Iterator对象，但list、dict、str虽然是Iterable，却不是Iterator。把list、dict、str等Iterable变成Iterator可以使用iter()函数：>>> isinstance(iter([]), Iterator) True >>> isinstance(iter('abc'), Iterator) True（三）总结凡是可作用于for循环的对象都是Iterable类型；凡是可作用于next()函数的对象都是Iterator类型，它们表示一个惰性计算的序列；集合数据类型如list、dict、str等是Iterable但不是Iterator，不过可以通过iter()函数获得一个Iterator对象。
一、调用函数（一）函数基本结构在Python中，定义一个函数要使用def语句，依次写出函数名、括号、括号中的参数和冒号:，然后，在缩进块中编写函数体，函数的返回值用return语句返回。例如：自定义一个求绝对值的my_abs函数def my_abs(x): if x >= 0: return x else: return -x函数体内部的语句在执行时，一旦执行到return时，函数就执行完毕，并将结果返回。如果没有return语句，函数执行完毕后也会返回结果，只是结果为None如果已经把my_abs()的函数定义保存为abstest.py文件了，那么，可以在该文件的当前目录下启动Python解释器，用from abstest import my_abs来导入my_abs()函数，注意abstest是文件名（不含.py扩展名）>>> from abstest import my_abs >>> my_abs(-9) 9（二）空函数可以定义一个什么事也不做的空函数，可以用pass语句：def nop(): passpass可以用来作为占位符，比如现在还没想好怎么写函数的代码，就可以先放一个pass，让代码能运行起来。pass还可以用在其他语句里，比如：if age >= 18: pass缺少了pass，代码运行就会有语法错误。（三）参数检查调用函数时，如果参数个数不对，Python解释器会自动检查出来，并抛出TypeError。但是如果参数类型不对，Python解释器就无法帮我们检查。数据类型检查可以用内置函数isinstance()实现：def my_abs(x): if not isinstance(x, (int, float)): raise TypeError('bad operand type') if x >= 0: return x else: return -x添加了参数检查后，如果传入错误的参数类型，函数就可以抛出一个错误：>>> my_abs('A') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in my_abs TypeError: bad operand type（四）返回多个值# 返回多个值 # 从一个点移动到另一个点，给出坐标、位移、角度，即可计算出新的坐标； # import math语句表示导入math包，并允许后续代码引用math包里的sin、cos等函数。 import math def move(x,y,step,angle=0): nx = x + step * math.cos(angle) ny = y - step * math.sin(angle) return nx,ny x,y = move(100,100,60,math.pi/6) print(x,y) # 运行结果 >>> x, y = move(100, 100, 60, math.pi / 6) >>> print(x, y) 151.96152422706632 70.0 # Python返回的其实是一个tuple,任然是单一值 r = x,y = move(100,100,60,math.pi/6) print(r)（五）总结定义函数时，需要确定函数名和参数个数；如果有必要，可以先对参数的数据类型做检查；函数体内部可以用return随时返回函数结果；函数执行完毕也没有return语句时，自动return None。函数可以同时返回多个值，但其实就是一个tuple。### 求解一元二次方程 import math def quadratic(a,b,c): #quadratic()开根号 d = b ** 2 - 4*a*c if d > 0: x1 = (-b + math.sqrt(d)) / 2*a x2 = (-b - math.sqrt(d)) / 2*a return x1,x2 elif d == 0: x = -b/2*a return x else: return("无解") print(quadratic(5,5,5))二、函数的参数（一）位置参数对于power(x)函数，参数x就是一个位置参数# 计算x2的函数 def power(x): return x * x把power(x)修改为power(x, n)，用来计算x^n^# 计算x的n次方函数 def power(x, n): s = 1 while n > 0: n = n - 1 s = s * x return s（二）默认参数经常计算x^2^，所以，完全可以把第二个参数n的默认值设定为2：def power(x, n=2): s = 1 while n > 0: n = n - 1 s = s * x return s调用power(5)时，相当于调用power(5, 2)注意事项必选参数在前，默认参数在后；当函数有多个参数时，把变化大的参数放前面，变化小的参数放后面。默认参数代码示例：# 一年级小学生注册的函数,name和gender是位置参数，age和city是默认参数 def enroll(name, gender, age=6, city='Beijing'): print('name:', name) print('gender:', gender) print('age:', age) print('city:', city)大多数学生注册时不需要提供年龄和城市，只提供必须的两个参数：>>> enroll('Sarah', 'F') name: Sarah gender: F age: 6 city: Beijing只有与默认参数不符的学生才需要提供额外的信息：enroll('Bob', 'M', 7) enroll('Adam', 'M', city='Tianjin')默认参数使用错误示例：先定义一个函数，传入一个list，添加一个END再返回：def add_end(L=[]): L.append('END') return L连续多次调用add_end()函数的默认参数时，会返回不同结果：>>> add_end() ['END'] >>> add_end() ['END', 'END'] >>> add_end() ['END', 'END', 'END']原因解释：Python函数在定义的时候，默认参数L的值就被计算出来了，即[]，因为默认参数L也是一个变量，它指向对象[]；每次调用该函数，如果改变了L的内容，则下次调用时，默认参数的内容就变了，不再是函数定义时的[]；解决办法：# 用None这个不变对象来实现 def add_end(L=None): if L is None: L = [] L.append('END') return L总结：默认参数必须指向不变对象！（三）可变参数可变参数就是传入的参数个数是可变的，可以是1个、2个到任意个，还可以是0个。示例：给定一组数字a，b，c……，请计算a^2^ + b^2^ + c^2^ + ……# 把a，b，c……作为一个list或tuple def calc(numbers): sum = 0 for n in numbers: sum = sum + n * n return sum但是调用的时候，需要先组装出一个list或tuple：>>> calc([1, 2, 3]) 14 >>> calc((1, 3, 5, 7)) 84如果利用可变参数，调用函数的方式可以简化成这样：# 使用可变参数，在变量numbers前加* def calc(*numbers): sum = 0 for n in numbers: sum = sum + n * n return sum>>> calc(1, 2, 3) 14 >>> calc(1, 3, 5, 7) 84如果已经有一个list或者tuple，要调用一个可变参数，可在list或tuple前面加一个*号，把list或tuple的元素变成可变参数传进去：>>> nums = [1, 2, 3] >>> calc(*nums) 14（四）关键字参数可变参数允许传入0个或任意个参数，这些可变参数在函数调用时自动组装为一个tuple；关键字参数允许传入0个或任意个含参数名的参数，这些关键字参数在函数内部自动组装为一个dict；def person(name, age, **kw): print('name:', name, 'age:', age, 'other:', kw) # 运行结果 >>> person('Michael', 30) name: Michael age: 30 other: {} # 传入任意个关键字参数 >>> person('Bob', 35, city='Beijing') name: Bob age: 35 other: {'city': 'Beijing'} >>> person('Adam', 45, gender='M', job='Engineer') name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}和可变参数类似，也可以先组装出一个dict，然后，把该dict转换为关键字参数传进去：>>> extra = {'city': 'Beijing', 'job': 'Engineer'} >>> person('Jack', 24, city=extra['city'], job=extra['job']) name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'} # 简化写法 >>> extra = {'city': 'Beijing', 'job': 'Engineer'} >>> person('Jack', 24, **extra) name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'} # **extra表示把extra这个dict的所有key-value用关键字参数传入到函数的**kw参数，kw将获得一个dict，注意kw获得的dict是extra的一份拷贝，对kw的改动不会影响到函数外的extra（五）命名关键字参数如果要限制关键字参数的名字，就可以用命名关键字参数，例如，只接收city和job作为关键字参数。这种方式定义的函数如下：def person(name, age, *, city, job): print(name, age, city, job)和关键字参数**kw不同，命名关键字参数需要一个特殊分隔符*，*后面的参数被视为命名关键字参数。如果函数定义中已经有了一个可变参数，后面跟着的命名关键字参数就不再需要一个特殊分隔符*了：def person(name, age, *args, city, job): print(name, age, args, city, job)命名关键字参数必须传入参数名，这和位置参数不同。如果没有传入参数名，调用将报错：>>> person('Jack', 24, 'Beijing', 'Engineer') Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: person() missing 2 required keyword-only arguments: 'city' and 'job'由于调用时缺少参数名city和job，Python解释器把前两个参数视为位置参数，后两个参数传给*args，但缺少命名关键字参数导致报错。命名关键字参数可以有缺省值，从而简化调用：def person(name, age, *, city='Beijing', job): print(name, age, city, job) # 命名关键字参数可以有缺省值，从而简化调用： >>> person('Jack', 24, job='Engineer') Jack 24 Beijing Engineer（六）参数组合在Python中定义函数，可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数，这5种参数都可以组合使用。但是，参数定义的顺序必须是：必选参数、默认参数、可变参数、命名关键字参数和关键字参数。比如定义一个函数，包含上述若干种参数：def f1(a, b, c=0, *args, **kw): print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw) def f2(a, b, c=0, *, d, **kw): print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw) # 函数调用的时候，Python解释器自动按照参数位置和参数名把对应的参数传进去 >>> f1(1, 2) a = 1 b = 2 c = 0 args = () kw = {} >>> f1(1, 2, c=3) a = 1 b = 2 c = 3 args = () kw = {} >>> f1(1, 2, 3, 'a', 'b') a = 1 b = 2 c = 3 args = ('a', 'b') kw = {} >>> f1(1, 2, 3, 'a', 'b', x=99) a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99} >>> f2(1, 2, d=99, ext=None) a = 1 b = 2 c = 0 d = 99 kw = {'ext': None} # 通过一个tuple和dict，也可以调用上述函数： >>> args = (1, 2, 3, 4) >>> kw = {'d': 99, 'x': '#'} >>> f1(*args, **kw) a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'} >>> args = (1, 2, 3) >>> kw = {'d': 88, 'x': '#'} >>> f2(*args, **kw) a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'} # 对于任意函数，都可以通过类似func(*args, **kw)的形式调用它，无论它的参数是如何定义的。（七）总结默认参数一定要用不可变对象，如果是可变对象，程序运行时会有逻辑错误；要注意定义可变参数和关键字参数的语法：*args是可变参数，args接收的是一个tuple；**kw是关键字参数，kw接收的是一个dict；调用函数时传入可变参数和关键字参数的语法：可变参数：（1）直接传入：func(1, 2, 3)（2）先组装list或tuple，再通过*args传入：func(*(1, 2, 3))关键字参数（1）直接传入：func(a=1, b=2)（2）先组装dict，再通过**kw传入：func(**{'a': 1, 'b': 2})命名的关键字参数是为了限制调用者可以传入的参数名，同时可以提供默认值。三、递归函数如果一个函数在内部调用自身本身，这个函数就是递归函数。def fact(n): if n==1: return 1 return n * fact(n - 1) # 运行结果 >>> fact(1) 1 >>> fact(5) 120 >>> fact(100) 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
一、条件判断if语句从上往下判断，判断为True则执行缩进的语句，并停止执行后面的内容。if后面要添加:if <条件判断1>: <执行1> elif <条件判断2>: <执行2> elif <条件判断3>: <执行3> else: <执行4>二、for...in循环for x in ...循环就是把每个元素代入变量x，然后执行缩进块的语句，如依次把list或tuple中的每个元素迭代出来。# for...in...循环 names = ['Bruce','Tom','Tim','Bob'] for name in names: print (name) sum = 0 for x in [0,1,2,3,4,5,6,7,8,9,10]: sum = sum +x print(sum)rangerange(start, stop[, step]) 参数说明： start: 计数从 start 开始。默认是从 0 开始。例如range（5）等价于range（0， 5）; stop: 计数到 stop 结束，但不包括 stop。例如：range（0， 5） 是[0, 1, 2, 3, 4]没有5 step：步长，默认为1。例如：range（0， 5） 等价于 range(0, 5, 1) range(0, 10, 3) # 步长为 3 [0, 3, 6, 9]range()函数，可以生成一个整数序列，再通过list()函数可以转换为list。# range函数 # 计算1+2+3+...+100 a = list(range(101)) sum = 0 for x in a: sum = sum + x print(sum)x = 'runoob' for i in range(len(x)) : print(x[i])三、while循环只要条件满足，就不断循环，条件不满足时退出循环。# while循环：计算100以内所有奇数之和 sum = 0 n = 99 while n > 0: sum = sum + n n = n - 2 print(sum)break在循环中，break语句可以提前退出循环。例如，本来要循环打印1～100的数字，如果要提前结束循环，可以用break语句：n = 1 while n <= 100: if n > 10: # 当n = 11时，条件满足，执行break语句 break # break语句会结束当前循环 print(n) n = n + 1 print('END')continue在循环过程中，也可以通过continue语句，跳过当前的这次循环，直接开始下一次循环。如果我们想只打印奇数，可以用continue语句跳过某些循环：n = 0 while n < 10: n = n + 1 if n % 2 == 0: # 如果n是偶数，执行continue语句 continue # continue语句会直接继续下一轮循环，后续的print()语句不会执行 print(n)