AI智能
改变未来

Python 3 快速入门 3 —— 模块与类

本文假设你已经有一门面向对象编程语言基础,如

Java

等,且希望快速了解并使用

Python

语言。本文对重点语法和数据结构以及用法进行详细说明,同时对一些难以理解的点进行了图解,以便大家快速入门。一些较偏的知识点在大家入门以后根据实际需要再查询官方文档即可,学习时切忌胡子眉毛一把抓。同时,一定要跟着示例多动手写代码。学习一门新语言时推荐大家同时去刷

leetcode

,一来可以快速熟悉新语言的使用,二来也为今后找工作奠定基础。推荐直接在网页上刷

leetcode

,因为面试的时候一般会让你直接在网页编写代码。

leetcode

刷题路径可以按我推荐的方式去刷。以下代码中,以

>>>

...

开头的行是交互模式下的代码部分,

>?

开头的行是交互模式下的输入,其他行是输出。

python

代码中使用

#

开启行注释。

模块

模块是包含

Python

定义和语句的文件,文件后缀名为

.py

,文件名即是模块名。在

pycharm

中创建名为

python-learn

的项目,然后创建

fib.py

文件,并输入以下代码后保存:

# 斐波拉契数列def print_fib(n: int) -> None:"""打印斐波拉契数列:param n: 数列截至范围:return: None"""a, b = 0, 1while a < n:print(a, end=\' \')a, b = b, a+bprint()def get_fib(n: int) -> list:"""获取斐波拉契数列:param n: 数列截至范围:return: 存有数列的list"""result = []a, b = 0, 1while a < n:result.append(a)a, b = b, a+breturn result

接着,打开

pycharm

中的

Python Console

,使用

import

语句导入该模块:

# 导入 fib 模块>>> import fib# 使用 fib 模块中定义的函数>>> fib.print_fib(10)0 1 1 2 3 5 8>>> fib.get_fib(10)[0, 1, 1, 2, 3, 5, 8]# 如果经常使用某个函数,可以把它赋值给局部变量>>> print_fib = fib.print_fib>>> print_fib(10)0 1 1 2 3 5 8# 每个模块中都有一个特殊变量 __name__ 记录着模块的名字>>> print(fib.__name__)fib

使用

as

关键字为导入的模块指定别名:

# 导入 fib 并指定别名为 fibonacci>>> import fib as fibonacci>>> fibonacci.get_fib(10)[0, 1, 1, 2, 3, 5, 8]# 模块名依然为 fib>>> print(fibonacci.__name__)fib

当我们通过

import

语句导入模块时:

  • 首先查找要导入的模块是否为内置模块;
  • 没找到就会根据
    sys.path

    (list)中的路径继续查找。(

    sys.path

    默认值包含:当前路径、环境变量

    PYTHONPATH

    中的路径等)

查看

sys.path

的值:

>>> print("======= start =======")... for path in sys.path:...     print(path)... print("=======  end  =======")======= start =======C:\\software\\jetbrains\\PyCharm 2021.2.3\\plugins\\python\\helpers\\pydevC:\\software\\jetbrains\\PyCharm 2021.2.3\\plugins\\python\\helpers\\pycharm_displayC:\\software\\jetbrains\\PyCharm 2021.2.3\\plugins\\python\\helpers\\third_party\\thriftpyC:\\software\\jetbrains\\PyCharm 2021.2.3\\plugins\\python\\helpers\\pydevC:\\software\\anaconda3\\envs\\python-learn\\python310.zipC:\\software\\anaconda3\\envs\\python-learn\\DLLsC:\\software\\anaconda3\\envs\\python-learn\\libC:\\software\\anaconda3\\envs\\python-learnC:\\software\\anaconda3\\envs\\python-learn\\lib\\site-packagesC:\\software\\jetbrains\\PyCharm 2021.2.3\\plugins\\python\\helpers\\pycharm_matplotlib_backendD:\\code\\python\\python-learn  # fib 模块所在目录D:/code/python/python-learn  # 对应linux路径形式=======  end  =======

当我们要导入的模块路径不在

sys.path

中时,通过

append

extend

函数可以将目标路径手动加入

sys.path

中。前面的例子里,为什么我们没有手动将项目路径加入

sys.path

中就可以导入

fib

模块呢?答案是

pycharm

帮我们做了。在项目中,当我们打开

Python Console

时,

pycharm

执行了以下脚本:

sys.path.extend([\'D:\\\\code\\\\python\\\\python-learn\', \'D:/code/python/python-learn\'])

**注意:**为了保证运行效率,每次解释器会话只导入一次模块。如果更改了模块内容,必须重启解释器;仅交互测试一个模块时,也可以使用

importlib.reload()

,例如

import importlib; importlib.reload(modulename)

以脚本方式执行模块

.py

文件(模块)还可以通过

python

解释器以脚本的方式执行。在

pycharm

项目中打开

Terminal

并执行以下命令可以解释执行

fib

模块(也可点击图形界面的执行按钮):

python fib.py

执行

fib.py

时,解释器从上到下依次解释执行,由于代码中没有任何输出动作所以终端没有任何输出。

当一个

.py

文件(模块)被当作脚本执行时,会被认为是程序的入口,类似于其他语言中的

main

函数,于是

python

解释器会将该模块的特殊变量

__name__

置为

__main__

现在,我们给

fib.py

文件添加一些内容:

# 斐波拉契数列def print_fib(n: int) -> None:"""打印斐波拉契数列:param n: 数列截至范围:return: None"""a, b = 0, 1while a < n:print(a, end=\' \')a, b = b, a+bprint()def get_fib(n: int) -> list:"""获取斐波拉契数列:param n: 数列截至范围:return: 存有数列的list"""result = []a, b = 0, 1while a < n:result.append(a)a, b = b, a+breturn result# 只有当前文件(模块)被当作脚本执行时 if 语句才为真if __name__ == "__main__":import sys# argv[0]始终为文件名print(sys.argv[0], end=" ")# 传入的第一个参数print(sys.argv[1], end=" ")# 传入的第二个参数print(sys.argv[2])# 测试 print_fib 函数print_fib(int(sys.argv[1]))# 测试 get_fib 函数fibSeries = get_fib(int(sys.argv[2]))print(fibSeries)

输入以下命令并执行:

PS D:\\code\\python\\python-learn> python fib.py 10 20fib.py 10 200 1 1 2 3 5 8[0, 1, 1, 2, 3, 5, 8, 13]

如果你了解

Java

,那么

Python

中的包你就不会感到陌生。包是命名空间的一种实现方式,不同包中的同名模块互不影响。包的写法类似于

A.B.C

A

是包,

B

A

的子包,

C

可以是B的子包也可以是模块。包在磁盘上的表现就是目录或者说是路径,以包结构

A.B.C

为例,若

C

为模块,那么对应的路径为

项目路径/A/B/C.py

。同时

Python

只把含

__init__.py

文件的目录当成包。(后面解释这个文件的用处)

以之前创建的

python-learn

项目为例,在根目录下创建包

com.winlsr

,然后将

fib.py

移动到

com.winlsr

下,目录结构如下:

从包中导入

fib

模块:

# 导入 fib 模块>>> import com.winlsr.fib# 使用时必须引用模块的全名>>> com.winlsr.fib.print_fib(10)0 1 1 2 3 5 8

使用

from package import ...

导入模块、函数(建议重启一下解释器):

# 导入 fib 模块>>> from com.winlsr import fib# 使用时直接输入模块名>>> fib.print_fib(10)0 1 1 2 3 5 8# 直接导入 fib 模块中的 print_fib 函数>>> from com.winlsr.fib import print_fib# 直接使用方法即可>>> print_fib(10)0 1 1 2 3 5 8

使用

from package import item

时,item 可以是包的子模块(或子包),也可以是包中定义的函数、类或变量等其他名称。使用

import item

时,

item

只可以是模块或包。有的同学会疑问,导入包有什么用?以当前项目为例,我们导入

com

包(建议重启一下解释器):

# 导入 com 包>>> import com>>> com# 包其实也是模块,对应的文件是包下的 __init__.py 文件?<module \'com\'="" from="" \'d:\\\\code\\\\python\\\\python-learn\\\\com\\\\__init__.py\'=""># 验证猜想:# 将该 __init__.py 文件中添加如下内容def print_info():print("name :", __name__)# 重启解释器后再次导入 com 包>>> import com# 成功调用 __init__.py 文件中定义的函数,猜想正确>>> com.print_info()name : com

从包中导入 *

from package import *

不会导入

package

中的任何模块或子包,除非你在该

package

下的

__init__.py

文件中添加了如下显示说明:

__all__ = ["子模块名1", "子包名1"]

添加该说明后,执行

from package import *

语句会导入指定的子模块、子包。

同样以之前创建的

python-learn

项目为例,我们执行如语句(建议重启一下解释器):

# 希望导入 com.winlsr 包下的 fib 模块>>> from com.winlsr import *# 发现并没有导入>>> fibTraceback (most recent call last):File "<input>", line 1, in <module>NameError: name \'fib\' is not defined

com.winlsr

下的

__init__.py

文件中添加如下内容:

__all__ = ["fib"]

执行同样的语句(建议重启一下解释器):

>>> from com.winlsr import *# 导入成功>>> fib<module \'com.winlsr.fib\'="" from="" \'d:\\\\code\\\\python\\\\python-learn\\\\com\\\\winlsr\\\\fib.py\'="">

注意:通常不建议采用该小结讲解的方法导入模块或子包,而是采用

from package import specific_submodule

dir()

函数

内置函数

dir()

用于查找模块或子包中定义的名称(变量、模块(子包)、函数等),返回结果是经过排序的字符串列表。没有参数时,

dir()

列出当前定义的名称。

>>> from com.winlsr import fib>>> dir(fib)[\'__builtins__\', \'__cached__\', \'__doc__\', \'__file__\', \'__loader__\', \'__name__\', \'__package__\', \'__spec__\', \'get_fib\', \'print_fib\']>>> dir()[\'__builtins__\', \'__doc__\', \'__file__\', \'__loader__\', \'__name__\', \'__package__\', \'__spec__\', \'fib\', \'sys\']

封装

Python

中定义类使用

class

关键字,类中可以定义变量和函数,起到封装的作用。沿用之前创建的项目,在

com.winlsr

包中创建

rectangle

模块并定义矩形类:

class Rectangle:# 一个类只能有一个构造函数def __init__(self):    # 无参构造,self 类似于 this,指向实例对象本身self.length = 0.0  # 构造函数中对实例对象的成员变量width和length进行初始化self.width = 0.0   # 只有创建的实例才有这两个变量# 计算面积def area(self):return self.length * self.width# 计算周长def perimeter(self):return (self.length + self.width) * 2

打开

Python Console

,输入以下语句来使用

Rectangle

类:

>>> from com.winlsr.rectangle import Rectangle# 使用无参构造创建类的实例对象>>> rec = Rectangle()# 调用rec实例的方法>>> rec.perimeter()0.0>>> rec.area()0.0>>> rec.length0.0# Rectangle 类中没有length这个成员变量,只有实例对象中才有>>> Rectangle.lengthTraceback (most recent call last):File "<input>", line 1, in <module>AttributeError: type object \'Rectangle\' has no attribute \'length\'

Python

中实例对象的成员变量也可以不在

__init__()

中定义和初始化,而是直接在类中定义并初始化。原来的

Rectangle

类可以改写为:

class Rectangle:# 成员变量直接定义在类中并初始化,类和实例都有两个变量length = 0.0width = 0.0def area(self):return self.length * self.widthdef perimeter(self):return (self.length + self.width) * 2

注意,如果变量直接定义在类中,那么创建实例时,实例中的变量是中变量的浅拷贝。重启解释器执行以下语句:

>>> from com.winlsr.rectangle import Rectangle>>> rec0 = Rectangle()>>> rec0.width is Rectangle.widthTrue>>> rec1 = Rectangle()>>> rec1.length is Rectangle.lengthTrue

实例

rec0

rec1

创建后的内存分布如下:

对实例

rec0

rec1

中的成员变量进行修改:

# 修改实例的成员变量>>> rec0.width = 1.0... rec0.length = 2.0... rec1.width = 3.0... rec1.length = 4.0# 打印>>> print(Rectangle.width)... print(Rectangle.length)... print(rec0.width)... print(rec0.length)... print(rec1.width)... print(rec1.length)0.00.01.02.03.04.0

根据结果可以发现

rec0

rec1

实例之间的成员变量(不可变 immutable 类型)相互之间不影响,修改后它们的内存分布如下:

注意,如果直接定义在类中的变量是可变(mutable)类型,使用时就应该谨慎。如下,依然在

com.winlsr

包中创建

actor

模块并定义演员类:

class Actor:# 参演的电影movies = []def get_movies(self):return self.moviesdef add_movie(self, movie):self.movies.append(movie)

使用

Actor

类:

>>> from com.winlsr.actor import Actor... lixiaolong = Actor()... xuzheng = Actor()... lixiaolong.add_movie("猛龙过江")... xuzheng.add_movie("我不是药神")... print(lixiaolong.get_movies())... print(xuzheng.get_movies())# 发现两个对象的 movies list 是共享的[\'猛龙过江\', \'我不是药神\']print(xuzheng.get_movies())[\'猛龙过江\', \'我不是药神\']

以上情况是因为多个

Actor

类的实例中的

movies

变量(引用)指向同一个

list

对象,实例

lixiaolong

xuzheng

内存分布如下:

创建后实例后,调用

add_movie()

之前的内存分布:

调用

add_movie()

之后的内存分布:

为了使每个演员的参演电影列表相互独立,在创建

Actor

类的实例时应该为每个实例创建一个新的

list

class Actor:# 参演的电影# 该语句没有用,可以删掉。实例在创建时会通过构造函数改变指向movies的指向movies = []def __init__(self):self.movies = []def get_movies(self):return self.moviesdef add_movie(self, movie):self.movies.append(movie)

重启解释器,再次执行以下语句:

>>> from com.winlsr.actor import Actor... lixiaolong = Actor()... xuzheng = Actor()... lixiaolong.add_movie("猛龙过江")... xuzheng.add_movie("我不是药神")... print(lixiaolong.get_movies())... print(xuzheng.get_movies())[\'猛龙过江\'][\'我不是药神\']

综上,根据前面的示例,如果你不希望多个实例之间共享变量,建议直接将变量定义在

__init__

函数中。最后,

Python

支持静态语言不支持的实例属性动态绑定特性:

# 给 lixiaolong 这个实例动态添加 age 属性>>> lixiaolong.age = 41>>> lixiaolong.age41# 不影响其他实例>>> xuzheng.ageTraceback (most recent call last):File "<input>", line 1, in <module>AttributeError: \'Actor\' object has no attribute \'age\'

访问控制

前面的实例中,我们可以通过

instance.var

的形式来直接访问实例的成员变量。但通常我们不希望实例中的成员变量被直接访问,而是通过

getter

setter

来访问,这需要我们将成员变量设置为

private

Python

中:

  • 带有一个下划线的变量,形如
    _var

    应该被当作是

    API

    (常用于模块中)的非公有部分 (函数或是数据成员)。虽然可以正常访问,但我们应遵循这样一个约定;

  • 类中私有成员变量应当用两个前缀下划线,至多一个后缀下划线标识,形如:
    __var

    。但该变量并不是真正的不能访问,这是因为

    Python

    实现的机制是”名称改写“。这种机制在执行时会将

    __var

    改为

    _classname__var

    ,但你仍然可以通过

    _classname__var

    来访问。

  • 形如
    __var__

    的变量是特殊变量,可以访问,但通常我们不需要定义此类变量。

实验验证,将

Rectangle

类改为如下代码:

class Rectangle:def __init__(self, length, width):self.__length = lengthself.__width = widthdef area(self):return self.__length * self.__widthdef perimeter(self):return (self.__length + self.__width) * 2

重启解释器,执行如下语句:

>>> from com.winlsr.rectangle import Rectangle>>> rec = Rectangle(12.0, 24.0)# 直接访问私有变量 __width,失败>>> print(rec.__width)Traceback (most recent call last):File "<input>", line 1, in <module>AttributeError: \'Rectangle\' object has no attribute \'__width\'# 访问私有变量改写后的名称 _Rectangle__width,成功>>> print(rec._Rectangle__width)24.0

对应

Python Console

如下:

继承

Python

继承语法如下,所有类都默认继承:

class DerivedClassName(BaseClassName):<statement-1>...<statement-n>

下面我们在

com.winlsr

包下创建

person

模块并定义

Person

类,作为

Actor

Teacher

类的基类:

class Person:def __init__(self, name, id_number):self.__name = nameself.__id_number = id_numberdef set_name(self, name):self.__name = namedef get_name(self):return self.__namedef set_id_number(self, id_number):self.__id_number = id_numberdef get_id_number(self):return self.__id_number

com.winlsr

包下创建

actor

模块并定义

Actor

类,它是

Person

类的派生类:

from .person import Personclass Actor(Person):def __init__(self, name, id_number):self.__movies = []# 三种调用父类构造函数的方式super().__init__(name, id_number)# super(Actor, self).__init__(name, id_number)# Person.__init__(self, name, id_number)def add_movie(self, movie):self.__movies.append(movie)def print_info(self):print(self.get_name(), self.get_id_number(), self.__movies, sep=" : ")

com.winlsr

包下创建

teacher

模块并定义

Teacher

类,它也是

Person

类的派生类:

from .person import Personclass Teacher(Person):# 无构造函数,创建对象时会调用父类构造函数def print_info(self):print(self.get_name(), self.get_id_number(), sep=" : ")

重启解释器,执行如下语句:

>>> from com.winlsr.actor import Actor>>> actor = Actor("xuzheng", 123456789)>>> actor.add_movie("我不是药神")>>> actor.print_info()xuzheng : 123456789 : [\'我不是药神\']>>> from com.winlsr.teacher import Teacher>>> teacher = Teacher()# Teacher 类中没有定义 __init__(),这里会调用父类 Person 的构造并传入参数>>> teacher = Teacher("lsl", 123459876)>>> teacher.print_info()lsl : 123459876

对应

Python Console

如下:

Python

也支持一种多重继承。 带有多个基类的类定义语句如下所示:

class DerivedClassName(Base1, Base2, Base3):<statement-1>...<statement-n>

派生类实例如果某一属性在

DerivedClassName

中未找到,则会到

Base1

中搜索它,然后(递归地)到

Base1

的基类中搜索,如果在那里未找到,再到

Base2

中搜索,依此类推。

其他

推荐学习官方文档的迭代器,生成器,生成器表达式。

结语

教程到这里就结束了,最后推荐大家再去看看廖雪峰老师讲解的

异常处理

IO

的内容(也可以用到的时候再看),他比官网讲解的更有条理。学完这些内容就基本入门了,今后可以根据自己应用的领域再进一步学习即可,比如深度学习、web开发等。

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » Python 3 快速入门 3 —— 模块与类