📢📢📢📣📣📣
哈喽!大家好,我是「奇点」,江湖人称 singularity。刚工作几年,想和大家一同进步🤝🤝
一位上进心十足的【Java ToB端大厂领域博主】!😜😜😜
喜欢java和python,平时比较懒,能用程序解决的坚决不手动解决😜😜😜
✨ 如果有对【java】感兴趣的【小可爱】,欢迎关注我❤️❤️❤️感谢各位大可爱小可爱!❤️❤️❤️
————————————————如果觉得本文对你有帮助,欢迎点赞,欢迎关注我,如果有补充欢迎评论交流,我将努力创作更多更好的文章。 今天继续给大家分享python的学习笔记,之前吧python的基础知识,譬如特点、发展史、基本数据类型,逻辑语句和循环语句和集合,时间、文件操作、异常处理和函数等基础知识,有了这些入门知识,我们就能写一下简单的逻辑代码了。今天我们继续深入学习python到基础知识。
对python 0基础的同学可以看一下我的上篇文章,再来学习这篇文章,大佬可以直接跳过。
0基础跟我学python(1)
https://blog.csdn.net/qq_29235677/article/details/125967844
0基础跟我学python(2)
https://blog.csdn.net/qq_29235677/article/details/126033880

今天我们来继续深入学习python的基础篇,今天到主要内容是面向对象、模块、命名空间和作用域、迭代器和生成器和任何使用测试。相信学习完今天到内容,你对python的基础知识已经基本拿捏了。能够编写相对简单的代码了。
加油,甲人们⛽️(为了油费加油😂)
目录
Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的。本章节我们将详细介绍Python的面向对象编程。
如果你以前没有接触过面向对象的编程语言,那你可能需要先了解一些面向对象语言的一些基本特征,在头脑里头形成一个基本的面向对象的概念,这样有助于你更容易的学习Python的面向对象编程。如果你之前有使用过面向对象的语言,这里进行学习的话就相对简单多了。
我们直接把面向对象的设计方法进行类比,你就会发现基本上如出一辙。
接下来我们先来简单的了解下面向对象的一些基本特征。
和其它编程语言相比,Python 在尽可能不增加新的语法和语义的情况下加入了类机制。
Python中的类提供了面向对象编程的所有基本功能:类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法,方法中可以调用基类中的同名方法。
对象可以包含任意数量和类型的数据。
语法格式如下:
- class ClassName:
-
1> - .
- .
- .
-
类实例化后,可以使用其属性,实际上,创建一个类之后,可以通过类名访问其属性。
类对象支持两种操作:属性引用和实例化。
属性引用使用和 Python 中所有的属性引用一样的标准语法:obj.name。
类对象创建后,类命名空间中所有的命名都是有效属性名。所以如果类定义是这样:
- class MyClass:
- """一个简单的类实例"""
- i = 12345
- def f(self):
- return 'hello world'
-
- # 实例化类
- x = MyClass()
-
- # 访问类的属性和方法
- print("MyClass 类的属性 i 为:", x.i)
- print("MyClass 类的方法 f 输出为:", x.f())
以上创建了一个新的类实例并将该对象赋给局部变量 x,x 为空的对象。
结果
MyClass 类的属性 i 为: 12345
MyClass 类的方法 f 输出为: hello world
类有一个名为 __init__() 的特殊方法(构造方法),该方法在类实例化时会自动调用,像下面这样:
- def __init__(self):
- self.data = []
类定义了 __init__() 方法,类的实例化操作会自动调用 __init__() 方法。如下实例化类 MyClass,对应的 __init__() 方法就会被调用:
x = MyClass()
当然, __init__() 方法可以有参数,参数通过 __init__() 传递到类的实例化操作上。例如:
- class Complex:
- def __init__(self, realpart, imagpart):
- self.r = realpart
- self.i = imagpart
- x = Complex(3.0, -4.5)
- print(x.r, x.i) # 输出结果:3.0 -4.5
类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self。
- class Test:
- def prt(self):
- print(self)
- print(self.__class__)
-
- t = Test()
- t.prt()
输出结果: <__main__.Test object at 0x10b4d2380>
从执行结果可以很明显的看出,self 代表的是类的实例,代表当前对象的地址,而 self.class 则指向类。
self 不是 python 关键字,我们把他换成 xx 也是可以正常执行的:
在类的内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self, 且为第一个参数,self 代表的是类的实例。
- #类定义
- class people:
- #定义基本属性
- name = ''
- age = 0
- #定义私有属性,私有属性在类外部无法直接进行访问
- __weight = 0
- #定义构造方法
- def __init__(self,n,a,w):
- self.name = n
- self.age = a
- self.__weight = w
- def speak(self):
- print("%s 说: 我 %d 岁。" %(self.name,self.age))
-
- # 实例化类
- p = people('小明',10,30)
- p.speak()
结果
小明 说: 我 10 岁。
Python 同样支持类的继承,如果一种语言不支持继承,类就没有什么意义。派生类的定义如下所示:
- class DerivedClassName(BaseClassName):
-
1> - .
- .
- .
-
子类(派生类 DerivedClassName)会继承父类(基类 BaseClassName)的属性和方法。
BaseClassName(实例中的基类名)必须与派生类定义在一个作用域内。除了类,还可以用表达式,基类定义在另一个模块中时这一点非常有用:
class DerivedClassName(modname.BaseClassName):
- #类定义
- class people:
- #定义基本属性
- name = ''
- age = 0
- #定义私有属性,私有属性在类外部无法直接进行访问
- __weight = 0
- #定义构造方法
- def __init__(self,n,a,w):
- self.name = n
- self.age = a
- self.__weight = w
- def speak(self):
- print("%s 说: 我 %d 岁。" %(self.name,self.age))
-
- #单继承示例
- class student(people):
- grade = ''
- def __init__(self,n,a,w,g):
- #调用父类的构函
- people.__init__(self,n,a,w)
- self.grade = g
- #覆写父类的方法
- def speak(self):
- print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))
-
-
-
- s = student('ken',10,60,3)
- s.speak()
结果
ken 说: 我 10 岁了,我在读 3 年级
Python同样有限的支持多继承形式。多继承的类定义形如下例:
- class DerivedClassName(Base1, Base2, Base3):
-
1> - .
- .
- .
-
需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。
- #类定义
- class people:
- #定义基本属性
- name = ''
- age = 0
- #定义私有属性,私有属性在类外部无法直接进行访问
- __weight = 0
- #定义构造方法
- def __init__(self,n,a,w):
- self.name = n
- self.age = a
- self.__weight = w
- def speak(self):
- print("%s 说: 我 %d 岁。" %(self.name,self.age))
-
- #单继承示例
- class student(people):
- grade = ''
- def __init__(self,n,a,w,g):
- #调用父类的构函
- people.__init__(self,n,a,w)
- self.grade = g
- #覆写父类的方法
- def speak(self):
- print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))
-
- #另一个类,多重继承之前的准备
- class speaker():
- topic = ''
- name = ''
- def __init__(self,n,t):
- self.name = n
- self.topic = t
- def speak(self):
- print("我叫 %s,我是一个演说家,我演讲的主题是 %s"%(self.name,self.topic))
-
- #多重继承
- class sample(speaker,student):
- a =''
- def __init__(self,n,a,w,g,t):
- student.__init__(self,n,a,w,g)
- speaker.__init__(self,n,t)
-
- test = sample("Tim",25,80,4,"Python")
- test.speak() #方法名同,默认调用的是在括号中参数位置排前父类的方法
结果
我叫 Tim,我是一个演说家,我演讲的主题是 Python
如果将speaker和student互换
class sample(student,speaker):
返回的结果是
Tim 说: 我 25 岁了,我在读 4 年级
如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法,实例如下:
- class Parent: # 定义父类
- def myMethod(self):
- print ('调用父类方法')
-
- class Child(Parent): # 定义子类
- def myMethod(self):
- print ('调用子类方法')
-
- c = Child() # 子类实例
- c.myMethod() # 子类调用重写方法
- super(Child,c).myMethod() #用子类对象调用父类已被覆盖的方法
super() 函数是用于调用父类(超类)的一个方法。
或者用下面方法调父类的方法,也会调用父类中的方法
Parent.myMethod(c)
执行以上程序输出结果为:
调用子类方法 调用父类方法
如果在子类中需要父类的构造方法就需要显式地调用父类的构造方法,或者不重写父类的构造方法。
子类不重写 __init__,实例化子类时,会自动调用父类定义的 __init__。
- class Father(object):
- def __init__(self, name):
- self.name=name
- print ( "name: %s" %( self.name) )
- def getName(self):
- return 'Father ' + self.name
-
- class Son(Father):
- def getName(self):
- return 'Son '+self.name
-
- if __name__=='__main__':
- son=Son('root')
- print ( son.getName() )
结果:
name: root Son root
如果重写了__init__ 时,实例化子类,就不会调用父类已经定义的 __init__,语法格式如下:
- class Father(object):
- def __init__(self, name):
- self.name=name
- print ( "name: %s" %( self.name) )
- def getName(self):
- return 'Father ' + self.name
-
- class Son(Father):
- def __init__(self, name):
- print ( "hi" )
- self.name = name
- def getName(self):
- return 'Son '+self.name
-
- if __name__=='__main__':
- son=Son('xiaoming')
- print ( son.getName() )
结果
hi Son xiaoming
如果重写了__init__ 时,要继承父类的构造方法,可以使用 super 关键字:
super(子类,self).__init__(参数1,参数2,....)
还有一种经典写法:
父类名称.__init__(self,参数1,参数2,...)
- class Father(object):
- def __init__(self, name):
- self.name=name
- print ( "name: %s" %( self.name))
- def getName(self):
- return 'Father ' + self.name
-
- class Son(Father):
- def __init__(self, name):
- super(Son, self).__init__(name)
- print ("hi")
- self.name = name
- def getName(self):
- return 'Son '+self.name
-
- if __name__=='__main__':
- son=Son('root')
- print ( son.getName() )
name: root hi Son root
相当于java中的private
__private_attrs:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时 self.__private_attrs。
在类的内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self,且为第一个参数,self 代表的是类的实例。
self 的名字并不是规定死的,也可以使用 this,但是最好还是按照约定使用 self。
__private_method:两个下划线开头,声明该方法为私有方法,只能在类的内部调用 ,不能在类的外部调用。self.__private_methods。
- class JustCounter:
- __secretCount = 0 # 私有变量
- publicCount = 0 # 公开变量
-
- def count(self):
- self.__secretCount += 1
- self.publicCount += 1
- print (self.__secretCount)
-
- counter = JustCounter()
- counter.count()
- counter.count()
- print (counter.publicCount)
- print (counter.__secretCount) # 报错,实例不能访问私有变量
结果
1
2
2
Traceback (most recent call last):
File "/usr/local/software/work/pycharmProjects/python-project/chapter15/faceObject.py", line 268, in
print(counter.__secretCount) # 报错,实例不能访问私有变量
AttributeError: 'JustCounter' object has no attribute '__secretCount'
- class Site:
- def __init__(self, name, url):
- self.name = name # public
- self.__url = url # private
-
- def who(self):
- print('name : ', self.name)
- print('url : ', self.__url)
-
- def __foo(self): # 私有方法
- print('这是私有方法')
-
- def foo(self): # 公共方法
- print('这是公共方法')
- self.__foo()
-
- x = Site('菜鸟一枚', 'www.baidu.com')
- x.who() # 正常输出
- x.foo() # 正常输出
- x.__foo() # 报错
name : 菜鸟一枚
url : www.baidu.com
这是公共方法
这是私有方法Traceback (most recent call last):
File "/usr/local/software/work/pycharmProjects/python-project/chapter15/faceObject.py", line 306, in
x.__foo() # 报错
AttributeError: 'Site' object has no attribute '__foo'
类的专有方法:
Python同样支持运算符重载,我们可以对类的专有方法进行重载,实例如下:
- class Vector:
- def __init__(self, a, b):
- self.a = a
- self.b = b
-
- def __str__(self):
- return 'Vector (%d, %d)' % (self.a, self.b)
-
- def __add__(self,other):
- return Vector(self.a + other.a, self.b + other.b)
-
- v1 = Vector(2,10)
- v2 = Vector(5,-2)
- print (v1 + v2)
以上代码执行结果如下所示:
Vector(7,8)
现在对python的面相对象进行一下总结,通过学习不难发现python和java实现面向对象基本上是相同的,下面就对比java对python的面相对象进行比较
| 方面 | python | java | 相同点 |
| 私有方法 | __双下划线实现,都是l | pricate关键字 | 都是不能被外部使用 |
| 重写 | 直接重写父类方法 | @Override | |
| 构造方法 | __init__(self) | 类() | |
| 类方法 | 必须包含self | static修饰 | |
| 类对象引用和实例化 | |||
| 代表实例指向 | self | this | |
| 继承 | A(B) | A extends B | java只允许单继承 python可以多继承 |
| 类属性与方法 | 类.属性/方法 | 类.属性/方法 |
在前面的几个章节中我们基本上是用 python 解释器来编程,如果你从 Python 解释器退出再进入,那么你定义的所有的方法和变量就都消失了。
为此 Python 提供了一个办法,把这些定义存放在文件中,为一些脚本或者交互式的解释器实例使用,这个文件被称为模块。
模块是一个包含所有你定义的函数和变量的文件,其后缀名是.py。模块可以被别的程序引入,以使用该模块中的函数等功能。这也是使用 python 标准库的方法。
使用标准库的例子:
- import sys
-
- print('命令行参数如下:')
- for i in sys.argv:
- print(i)
-
- print('\n\nPython 路径为:', sys.path, '\n')
命令行参数如下:
/usr/local/software/work/pycharmProjects/python-project/chapter15/mode.py
Python 路径为: ['/usr/local/software/work/pycharmProjects/python-project/chapter15', '/usr/local/software/work/pycharmProjects/python-project', '/Applications/PyCharm.app/Contents/plugins/python/helpers/pycharm_display', '/Library/Frameworks/Python.framework/Versions/3.10/lib/python310.zip', '/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10', '/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/lib-dynload', '/usr/local/software/work/pycharmProjects/python-project/venv/lib/python3.10/site-packages', '/Applications/PyCharm.app/Contents/plugins/python/helpers/pycharm_matplotlib_backend']
想使用 Python 源文件,只需在另一个源文件里执行 import 语句,语法如下:
import module1[, module2[,... moduleN]
当解释器遇到 import 语句,如果模块在当前的搜索路径就会被导入。
搜索路径是一个解释器会先进行搜索的所有目录的列表。如想要导入模块 support,需要把命令放在脚本的顶端:
🌰
mysupport.py 模块
- def print_func(par):
- print("Hello : ", par)
- return
用import导入mysupport.py 模块
- # 导入模块
- import mysupport as sp
-
- # 现在可以调用模块里包含的函数了
- sp.print_func("xx")
Hello : xx
一个模块只会被导入一次,不管你执行了多少次 import。这样可以防止导入模块被一遍又一遍地执行。
当我们使用 import 语句的时候,Python 解释器是怎样找到对应的文件的呢?
这就涉及到 Python 的搜索路径,搜索路径是由一系列目录名组成的,Python 解释器就依次从这些目录中去寻找所引入的模块。
这看起来很像环境变量,事实上,也可以通过定义环境变量的方式来确定搜索路径。
搜索路径是在 Python 编译或安装的时候确定的,安装新的库应该也会修改。搜索路径被存储在 sys 模块中的 path 变量,做一个简单的实验,在交互式解释器中,输入以下代码:
- >>> import sys
- >>> sys.path
sys.path 输出是一个列表,其中第一项是空串 '',代表当前目录(若是从一个脚本中打印出来的话,可以更清楚地看出是哪个目录),亦即我们执行python解释器的目录(对于脚本的话就是运行的脚本所在的目录)。
因此若像我一样在当前目录下存在与要引入模块同名的文件,就会把要引入的模块屏蔽掉。
了解了搜索路径的概念,就可以在脚本中修改sys.path来引入一些不在搜索路径中的模块。
现在,在解释器的当前目录或者 sys.path 中的一个目录里面来创建一个fibo.py的文件,代码如下:
- # 斐波那契(fibonacci)数列模块
-
- def fib(n): # 定义到 n 的斐波那契数列
- a, b = 0, 1
- while b < n:
- print(b, end=' ')
- a, b = b, a+b
- print()
-
- def fib2(n): # 返回到 n 的斐波那契数列
- result = []
- a, b = 0, 1
- while b < n:
- result.append(b)
- a, b = b, a+b
- return result
然后进入Python解释器,使用下面的命令导入这个模块:
>>> import fibo
这样做并没有把直接定义在fibo中的函数名称写入到当前符号表里,只是把模块fibo的名字写到了那里。
可以使用模块名称来访问函数:
- >>>fibo.fib(1000)
- 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
- >>> fibo.fib2(100)
- [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
- >>> fibo.__name__
- 'fibo'
如果你打算经常使用一个函数,你可以把它赋给一个本地的名称:
- >>> fib = fibo.fib
- >>> fib(500)
- 1 1 2 3 5 8 13 21 34 55 89 144 233 377
Python 的 from 语句让你从模块中导入一个指定的部分到当前命名空间中,语法如下:
from modname import name1[, name2[, ... nameN]]
例如,要导入模块 fibo 的 fib 函数,使用如下语句:
- >>> from fibo import fib, fib2
- >>> fib(500)
- 1 1 2 3 5 8 13 21 34 55 89 144 233 377
这个声明不会把整个fibo模块导入到当前的命名空间中,它只会将fibo里的fib函数引入进来。
把一个模块的所有内容全都导入到当前的命名空间也是可行的,只需使用如下声明:
from modname import *
这提供了一个简单的方法来导入一个模块中的所有项目。然而这种声明不该被过多地使用。
模块除了方法定义,还可以包括可执行的代码。这些代码一般用来初始化这个模块。这些代码只有在第一次被导入时才会被执行。
每个模块有各自独立的符号表,在模块内部为所有的函数当作全局符号表来使用。
所以,模块的作者可以放心大胆的在模块内部使用这些全局变量,而不用担心把其他用户的全局变量搞混。
从另一个方面,当你确实知道你在做什么的话,你也可以通过 modname.itemname 这样的表示法来访问模块内的函数。
模块是可以导入其他模块的。在一个模块(或者脚本,或者其他地方)的最前面使用 import 来导入一个模块,当然这只是一个惯例,而不是强制的。被导入的模块的名称将被放入当前操作的模块的符号表中。
还有一种导入的方法,可以使用 import 直接把模块内(函数,变量的)名称导入到当前操作模块。比如:
- >>> from fibo import fib, fib2
- >>> fib(500)
- 1 1 2 3 5 8 13 21 34 55 89 144 233 377
这种导入的方法不会把被导入的模块的名称放在当前的字符表中(所以在这个例子里面,fibo 这个名称是没有定义的)。
这还有一种方法,可以一次性的把模块中的所有(函数,变量)名称都导入到当前模块的字符表:
- >>> from fibo import *
- >>> fib(500)
- 1 1 2 3 5 8 13 21 34 55 89 144 233 377
这将把所有的名字都导入进来,但是那些由单一下划线(_)开头的名字不在此例。大多数情况, Python程序员不使用这种方法,因为引入的其它来源的命名,很可能覆盖了已有的定义。
一个模块被另一个程序第一次引入时,其主程序将运行。如果我们想在模块被引入时,模块中的某一程序块不执行,我们可以用__name__属性来使该程序块仅在该模块自身运行时执行。
- # Filename: using_name.py
- if __name__ == '__main__':
- print('程序自身在运行')
- else:
- print('我来自另一模块')
$ python using_name.py 程序自身在运行
- $ python
- >>> import using_name
- 我来自另一模块
- >>>
说明: 每个模块都有一个__name__属性,当其值是'__main__'时,表明该模块自身在运行,否则是被引入。
说明:__name__ 与 __main__ 底下是双下划线, _ _ 是这样去掉中间的那个空格。
Python 本身带着一些标准的模块库,有些模块直接被构建在解析器里,这些虽然不是一些语言内置的功能,但是他却能很高效的使用,甚至是系统级调用也没问题。
这些组件会根据不同的操作系统进行不同形式的配置,比如 winreg 这个模块就只会提供给 Windows 系统。
应该注意到这有一个特别的模块 sys ,它内置在每一个 Python 解析器中。变量 sys.ps1 和 sys.ps2 定义了主提示符和副提示符所对应的字符串:
- >>> import sys
- >>> sys.ps1
- '>>> '
- >>> sys.ps2
- '... '
- >>> sys.ps1 = 'C> '
- C> print('Runoob!')
- Runoob!
- C>
⛳️ 3.包结构
包是一种管理 Python 模块命名空间的形式,采用"点模块名称"。
比如一个模块的名称是 A.B, 那么他表示一个包 A中的子模块 B 。
就好像使用模块的时候,你不用担心不同模块之间的全局变量相互影响一样,采用点模块名称这种形式也不用担心不同库之间的模块重名的情况。
这样不同的作者都可以提供 NumPy 模块,或者是 Python 图形库。
不妨假设你想设计一套统一处理声音文件和数据的模块(或者称之为一个"包")。
现存很多种不同的音频文件格式(基本上都是通过后缀名区分的,例如: .wav,:file:.aiff,:file:.au,),所以你需要有一组不断增加的模块,用来在不同的格式之间转换。
并且针对这些音频数据,还有很多不同的操作(比如混音,添加回声,增加均衡器功能,创建人造立体声效果),所以你还需要一组怎么也写不完的模块来处理这些操作。
这里给出了一种可能的包结构(在分层的文件系统中):
- sound/ 顶层包
- __init__.py 初始化 sound 包
- formats/ 文件格式转换子包
- __init__.py
- wavread.py
- wavwrite.py
- aiffread.py
- aiffwrite.py
- auread.py
- auwrite.py
- ...
- effects/ 声音效果子包
- __init__.py
- echo.py
- surround.py
- reverse.py
- ...
- filters/ filters 子包
- __init__.py
- equalizer.py
- vocoder.py
- karaoke.py
- ...
在导入一个包的时候,Python 会根据 sys.path 中的目录来寻找这个包中包含的子目录。
目录只有包含一个叫做 __init__.py 的文件才会被认作是一个包,主要是为了避免一些滥俗的名字(比如叫做 string)不小心的影响搜索路径中的有效模块。
最简单的情况,放一个空的 :file:__init__.py就可以了。当然这个文件中也可以包含一些初始化代码或者为(将在后面介绍的) __all__变量赋值。
用户可以每次只导入一个包里面的特定模块,比如:
import sound.effects.echo
这将会导入子模块:sound.effects.echo。 他必须使用全名去访问:
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)
还有一种导入子模块的方法是:
from sound.effects import echo
这同样会导入子模块: echo,并且他不需要那些冗长的前缀,所以他可以这样使用:
echo.echofilter(input, output, delay=0.7, atten=4)
还有一种变化就是直接导入一个函数或者变量:
from sound.effects.echo import echofilter
同样的,这种方法会导入子模块: echo,并且可以直接使用他的 echofilter() 函数:
echofilter(input, output, delay=0.7, atten=4)
注意当使用 from package import item 这种形式的时候,对应的 item 既可以是包里面的子模块(子包),或者包里面定义的其他名称,比如函数,类或者变量。
import 语法会首先把 item 当作一个包定义的名称,如果没找到,再试图按照一个模块去导入。如果还没找到,抛出一个 :exc:ImportError 异常。
反之,如果使用形如 import item.subitem.subsubitem 这种导入形式,除了最后一项,都必须是包,而最后一项则可以是模块或者是包,但是不可以是类,函数或者变量的名字。
如果我们使用 from sound.effects import * 会发生什么呢?
Python 会进入文件系统,找到这个包里面所有的子模块,然后一个一个的把它们都导入进来。
但这个方法在 Windows 平台上工作的就不是非常好,因为 Windows 是一个不区分大小写的系统。
在 Windows 平台上,我们无法确定一个叫做 ECHO.py 的文件导入为模块是 echo 还是 Echo,或者是 ECHO。
为了解决这个问题,我们只需要提供一个精确包的索引。
导入语句遵循如下规则:如果包定义文件 __init__.py 存在一个叫做 __all__ 的列表变量,那么在使用 from package import * 的时候就把这个列表中的所有名字作为包内容导入。
作为包的作者,可别忘了在更新包之后保证 __all__ 也更新了啊。
以下实例在 file:sounds/effects/__init__.py 中包含如下代码:
__all__ = ["echo", "surround", "reverse"]
这表示当你使用from sound.effects import *这种用法时,你只会导入包里面这三个子模块。
如果 __all__ 真的没有定义,那么使用from sound.effects import *这种语法的时候,就不会导入包 sound.effects 里的任何子模块。他只是把包sound.effects和它里面定义的所有内容导入进来(可能运行__init__.py里定义的初始化代码)。
这会把 __init__.py 里面定义的所有名字导入进来。并且他不会破坏掉我们在这句话之前导入的所有明确指定的模块。看下这部分代码:
- import sound.effects.echo
- import sound.effects.surround
- from sound.effects import *
这个例子中,在执行 from...import 前,包 sound.effects 中的 echo 和 surround 模块都被导入到当前的命名空间中了。(当然如果定义了 __all__ 就更没问题了)
通常我们并不主张使用 * 这种方法来导入模块,因为这种方法经常会导致代码的可读性降低。不过这样倒的确是可以省去不少敲键的功夫,而且一些模块都设计成了只能通过特定的方法导入。
记住,使用 from Package import specific_submodule 这种方法永远不会有错。事实上,这也是推荐的方法。除非是你要导入的子模块有可能和其他包的子模块重名。
如果在结构中包是一个子包(比如这个例子中对于包sound来说),而你又想导入兄弟包(同级别的包)你就得使用导入绝对的路径来导入。比如,如果模块sound.filters.vocoder 要使用包 sound.effects 中的模块 echo,你就要写成 from sound.effects import echo。
- from . import echo
- from .. import formats
- from ..filters import equalizer
无论是隐式的还是显式的相对导入都是从当前模块开始的。主模块的名字永远是"__main__",一个Python应用程序的主模块,应当总是使用绝对路径引用。
包还提供一个额外的属性__path__。这是一个目录列表,里面每一个包含的目录都有为这个包服务的__init__.py,你得在其他__init__.py被执行前定义哦。可以修改这个变量,用来影响包含在包里面的模块和子包。
这个功能并不常用,一般用来扩展包里面的模块。
命名空间(Namespace)是从名称到对象的映射,大部分的命名空间都是通过 Python 字典来实现的。
命名空间提供了在项目中避免名字冲突的一种方法。各个命名空间是独立的,没有任何关系的,所以一个命名空间中不能有重名,但不同的命名空间是可以重名而没有任何影响。
我们举一个计算机系统中的例子,一个文件夹(目录)中可以包含多个文件夹,每个文件夹中不能有相同的文件名,但不同文件夹中的文件可以重名。

一般有三种命名空间:

命名空间查找顺序:
假设我们要使用变量 root,则 Python 的查找顺序为:局部的命名空间去 -> 全局命名空间 -> 内置命名空间。
如果找不到变量 root,它将放弃查找并引发一个 NameError 异常:
NameError: name 'root' is not defined。
命名空间的生命周期:
命名空间的生命周期取决于对象的作用域,如果对象执行完成,则该命名空间的生命周期就结束。
因此,我们无法从外部命名空间访问内部命名空间的对象。
- # var1 是全局名称
- var1 = 5
- def some_func():
-
- # var2 是局部名称
- var2 = 6
- def some_inner_func():
-
- # var3 是内嵌的局部名称
- var3 = 7
如下图所示,相同的对象名称可以存在于多个命名空间中。

作用域就是一个 Python 程序可以直接访问命名空间的正文区域。
在一个 python 程序中,直接访问一个变量,会从内到外依次访问所有的作用域直到找到,否则会报未定义的错误。Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。
变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。Python 的作用域一共有4种,分别是:
有四种作用域:
规则顺序: L –> E –> G –> B。
在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内置中找。

- g_count = 0 # 全局作用域
- def outer():
- o_count = 1 # 闭包函数外的函数中
- def inner():
- i_count = 2 # 局部作用域
内置作用域是通过一个名为 builtin 的标准模块来实现的,但是这个变量名自身并没有放入内置作用域内,所以必须导入这个文件才能够使用它。在Python3.0中,可以使用以下的代码来查看到底预定义了哪些变量:
- >>> import builtins
- >>> dir(builtins)
Python 中只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的,也就是说这些语句内定义的变量,外部也可以访问,如下代码:
- >>> if True:
- ... msg = 'I am from Runoob'
- ...
- >>> msg
- 'I am from Runoob'
- >>>
实例中 msg 变量定义在 if 语句块中,但外部还是可以访问的。
如果将 msg 定义在函数中,则它就是局部变量,外部不能访问:
- >>> def test():
- ... msg_inner = 'I am from Runoob'
- ...
- >>> msg_inner
- Traceback (most recent call last):
- File "
" , line 1, in - NameError: name 'msg_inner' is not defined
- >>>
从报错的信息上看,说明了 msg_inner 未定义,无法使用,因为它是局部变量,只有在函数内可以使用。
| 全局变量 | 局部变量 | ||
| 作用域 | 全局作用域, 整个程序范围内访问 | 局部作用域,被声明的函数内部访问 | |
| 定义 | 定义在函数外 | 定义在函数内部 |
定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域。
局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。如下实例:
- total = 0 # 这是一个全局变量
- # 可写函数说明
- def sum( arg1, arg2 ):
- #返回2个参数的和."
- total = arg1 + arg2 # total在这里是局部变量.
- print ("函数内是局部变量 : ", total)
- return total
-
- #调用sum函数
- sum( 10, 20 )
- print ("函数外是全局变量 : ", total)
结果
函数内是局部变量 : 30
函数外是全局变量 : 0
当内部作用域想修改外部作用域的变量时,就要用到 global 和 nonlocal 关键字了。
以下实例修改全局变量 num:
- num = 1
- def fun1():
- global num # 需要使用 global 关键字声明
- print(num)
- num = 123
- print(num)
- fun1()
- print(num)
1 123 123
如果要修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量则需要 nonlocal 关键字了,如下实例:
- num = 1
- def outer():
- num = 10
-
- def inner():
- nonlocal num # nonlocal关键字声明
- num = 100
- print(num)
-
- inner()
- print(num)
-
-
- outer()
- print(num)
100
100
1
另外有一种特殊情况,假设下面这段代码被运行:
- a = 10
- def test():
- a = a + 1
- print(a)
- test()
Traceback (most recent call last):
File "/usr/local/software/work/pycharmProjects/python-project/chapter15/namespace.py", line 55, in
test()
File "/usr/local/software/work/pycharmProjects/python-project/chapter15/namespace.py", line 51, in test
a = a + 1
UnboundLocalError: local variable 'a' referenced before assignment
错误信息为局部作用域引用错误,因为 test 函数中的 a 使用的是局部,未定义,无法修改。
修改 a 为全局变量:
- a = 10
- def test():
- global a
- a = a + 1
- print(a)
- test()
11
也可以通过传参数实现
- a = 10
- def test(a):
- a = a + 1
- print(a)
- test(a)
11
迭代是Python最强大的功能之一,是访问集合元素的一种方式。
迭代器是一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
迭代器有两个基本的方法:iter() 和 next()。
字符串,列表或元组对象都可用于创建迭代器:
- >>> list=[1,2,3,4]
- >>> it = iter(list) # 创建迭代器对象
- >>> print (next(it)) # 输出迭代器的下一个元素
- 1
- >>> print (next(it))
- 2
- >>>
迭代器对象可以使用常规for语句进行遍历:
- list=[1,2,3,4]
- it = iter(list) # 创建迭代器对象
- for x in it:
- print (x, end=" ")
1 2 3 4
也可以使用 next() 函数:
- import sys # 引入 sys 模块
-
- list=[1,2,3,4]
- it = iter(list) # 创建迭代器对象
-
- while True:
- try:
- print (next(it))
- except StopIteration:
- sys.exit()
1 2 3 4
把一个类作为一个迭代器使用需要在类中实现两个方法 __iter__() 与 __next__() 。
如果你已经了解的面向对象编程,就知道类都有一个构造函数,Python 的构造函数为 __init__(), 它会在对象初始化的时候执行。
__iter__() 方法返回一个特殊的迭代器对象, 这个迭代器对象实现了 __next__() 方法并通过 StopIteration 异常标识迭代的完成。
__next__() 方法(Python 2 里是 next())会返回下一个迭代器对象。
创建一个返回数字的迭代器,初始值为 1,逐步递增 1:
- class MyNumbers:
- def __iter__(self):
- self.a = 1
- return self
-
- def __next__(self):
- x = self.a
- self.a += 1
- return x
-
- myclass = MyNumbers()
- myiter = iter(myclass)
-
- print(next(myiter))
- print(next(myiter))
- print(next(myiter))
- print(next(myiter))
- print(next(myiter))
1 2 3 4 5
StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 __next__() 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。
在 20 次迭代后停止执行:
- class MyNumbers:
- def __iter__(self):
- self.a = 1
- return self
-
- def __next__(self):
- if self.a <= 20:
- x = self.a
- self.a += 1
- return x
- else:
- raise StopIteration
-
- myclass = MyNumbers()
- myiter = iter(myclass)
-
- for x in myiter:
- print(x)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
在 Python 中,使用了 yield 的函数被称为生成器(generator)。
跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。
在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。
调用一个生成器函数,返回的是一个迭代器对象。
以下实例使用 yield 实现斐波那契数列:
- import sys
-
- def fibonacci(n): # 生成器函数 - 斐波那契
- a, b, counter = 0, 1, 0
- while True:
- if (counter > n):
- return
- yield a
- a, b = b, a + b
- counter += 1
- f = fibonacci(10) # f 是一个迭代器,由生成器返回生成
-
- while True:
- try:
- print (next(f), end=" ")
- except StopIteration:
- sys.exit()
0 1 1 2 3 5 8 13 21 34 55
如果觉得本文对你有帮助,欢迎点赞,欢迎关注我,如果有补充欢迎评论交流,我将努力创作更多更好的文章。