@Author: Basil Guo
@Date· Jun. 9, 2021
@Description: Python注释、变量、数据类型、输入输出、模块和包、规范


1. 注释

注释就是给程序做的解释。如果不写注释,可能过了一段时间回来看代码早忘了是做啥子用的。写的代码其实是给人读的,所以要多写注释,但是更新了代码之后也一定要更新注释,不然代码和注释对不上也是很麻烦的事情。

Python注释有两种,单行注释以及多行注释。单行注释只有一个# comment,其中comment就是你的注释内容。多行注释一般用于文档注释、函数注释或者类注释,表明这些是做什么的,有什么功能,一般多行注释使用的是一个多行字符串(这个见下面的字符串小节)。当然你也可以使用多行每行开头是#的也行。

  1. # 我是单行注释
  2. # 我假设
  3. # 自己
  4. # 是多行注释
  5. """
  6. 我是真的
  7. 多行注释
  8. """
  9. '''
  10. 我也是
  11. 多行注释
  12. '''

2. 变量

这里需要区分变量和常量。变量顾名思义就是可以改变的量,常量就是不可改变的量。

Python是强类型语言,同时也是动态类型语言。强类型是指一个数据的类型是固定的,是字符串就是字符串,是数字就是数字,不能在需要A类型变量的地方使用B类型变量。而动态类型语言则表明变量的类型是可以变化的,变量的类型可以通过赋值或其他运算自动推导出来,可以使用type(variable)来查看变量的类型,这是你学的第一个函数。如果变量出现在赋值运算符或复合赋值运算符(=、+=、-=、*=、/=、%=,我们之后介绍其含义)的左边则表示创建变量或修改变量的值,否则表示引用该变量的值。

在Python中定义变量的方式很简单的,以下就是格式。左边是变量名,其实它是一个引用,右边是变量的值,所以联合起来就是把varData赋给varName,=是赋值符号,作边是变量,右边是值。这是你学到的第一个符号。是不是还可以呀!

  1. varName = varData

2.1 引用(变量名)

前面我们说了变量名varName是一个引用,那是什么意思呢?其实你可以这样想,假设你去银行存钱,银行会给你开户,开户后是不是会给你一张银行卡,这个银行卡就是引用,代表了绑定了你这个人和你的钱。如果哪天你想用钱了,要取钱,只需要拿着银行卡去ATM机或者柜台取钱就行了。是不是很好理解呢?那用Python代码表示就是下面这样。我下面的例子全部是以命令行形式运行的,**>>> **是命令提示符,后面才是我写的,所以拷贝的时候不要拷贝**>>> **,也不要多一个空格。Python是以空格来表示代码格式的。

  1. >>> basil_bankcard = 1728 # basil办理了一张卡,存了¥1728
  2. >>> basil_bankcard # 查看卡里的钱
  3. 1728 # 这是basil现在卡里有的钱

有一天basil想办理一张副卡给你ZYH花,该怎么做呢?就如下面这样子。

  1. >>> zyh_bankcard = basil_bankcard # 这是给ZYH办理的副卡
  2. >>> zyh_bankcard # 查看ZYH副卡里的钱
  3. 1728

夏天来了,有一天ZYH用副卡买了一条裙子花了¥300,那两张卡是不是要一起变化呢?不好意思,这里不会。如果世界真的是这样子多好,钱怎么花都不会改变呢。不过这是为什么呢?我们在一开始就说了,Python中的数据都是对象,一旦创建之后就不能再改变了。所以这里不符合客观世界,当然了,如果你要是这样去实现一个银行系统,估计银行就要半夜上门找你了。

  1. >>> dress = 300
  2. >>> zyh_bankcard = zyh_bankcard - dress
  3. >>> zyh_bankcard
  4. 1428
  5. >>> basil_bankcard
  6. 1728
  7. >>>

这个例子说明了Python 采用的是基于值的内存管理方式,如果为不同变量赋值为相同值,这个值在内存中只有一份,多个变量指向同一块内存地址。Python允许多个变量指向同一个值,但是当其中一个变量改变值后,其内存地址将会变化。我们可以使用id()函数查看一个变量的内存地址。以下代码演示了这个例子。

  1. >>> a = b = 1 # 定义了两个变量a和b,都赋值为1
  2. >>> id(a) # 查看变量a的内存地址
  3. 140730865010336
  4. >>> id(b) # 查看变量b的内存地址,和a相同,说明使用的是同一块地址
  5. 140730865010336
  6. >>> a = 2 # 改变变量a的值
  7. >>> id(a) # 查看变量a的内存地址
  8. 140730865010368
  9. >>> id(b) # 查看你变量b的内存地址
  10. 140730865010336

2.2 变量命名

“无规矩不成方圆”,作为一门语言也是需要有一些规则的。Python的变量也是具有命名规则的,这些规则也适用于Python中的函数名、类名以及包名等需要名字的地方。

  • 变量名中只能包含A-Za-z0-9_,也就是大小写字母、数字以及下划线组合;
  • 变量名对大小写敏感,也就是说varNameVarName是两个不同的变量名;
  • 数字不能作为变量名的开头,不然会报错SyntaxError: invalid syntax
  • 下划线开头的变量在 Python 中有特殊含义;
  • 不能使用关键字作为变量名,可以使用print(keyword.kwlist)查看所有 Python 关键字;
  • 不建议使用系统内置的模块名、类型名或函数名做变量名,否则会覆盖,比如max是内置函数,但如果使用max作为变量名,内置函数将不能使用,否则会报错。查看所有内置dir(__builtins__)

2.3 内存管理

如果有一个变量不需要了,以后也不想再用了,可以使用del varName进行删除,删除了再使用会报错name 'varName' is not found。当然了,Python也会自动管理内存空间,即便不手动删除,Python也会自动管理,当内存不够的时候清理部分不再使用的内存,如果没有可清理的,再次申请内存就会报错退出。这里只需要知道你只管用就行,其它的交给Python自己管理就行了。

3. 数据类型

Python中数据类型可以分为基本类型、高级类型和自定义类型。梳理如下思维导图。 4. Python基础知识 - 图1 | 数据类型 | 类型名称 | 示例 | 简要说明 | | —- | —- | —- | —- | | 数字 | int,float,complex | 123,3.14,1.3e5,3+4j | 数字大小没有限制,内置支持复数及其运算 | | 字符串 | str | ‘str’,”I’m”,’’’Python’’’,r’abc’,R’bcd’ | 使用单引号、双引号、三引号作为定界符,以字母r或R引导的表示原始字符串 | | 字节串 | bytes | b’hello’ | 以字母b引导,可以使用单引号、双引号、三引号作为定界符 | | 列表 | list | [1,2],[‘a’,2] | 所有元素放在一对方括号中,元素之间使用逗号分隔,其中的元素可以是任意类型 | | 字典 | dict | {1:’food’,2:’import’} | 所有元素放在一对大括号中,元素之间使用逗号分隔,元素形式为“键:值” | | 元组 | tuple | (2,-4),(3,) | 所有元素放在一对圆括号中,元素之间使用逗号分隔,如果元组中只有一个元素的话,后面的逗号不能省略 | | 集合 | set,fronzenset | {‘a’,’b’} | 所有元素放在一对大括号中,元素之间使用逗号分隔,元素不允许重复;另外,set是可变的,而frozenset是不可变的 | | 布尔型 | bool | True,False | 逻辑值,关系运算符、成员测试运算符、同一性测试运算符组成的表达式的值一般为True或False | | 空类型 | NoneType | None | 空值 | | 异常 | Exception,ValueError,
TypeError | | Python内置大量异常类,分别对应不同类型的异常 | | 文件 | | f = open(‘data.dat’, ‘rb’) | open是Python内置函数,使用指定的模式打开文件,返回文件对象 | | 其他迭代对象 | | 生成器对象
range对象
zip对象
enumerate对象
map对象
filter对象等等 | 具有惰性求值的特点 | | 编程对象 | | 函数(使用def定义)
类(使用class定义)
模块(类型为module) | 类和函数都属于可调用对象,模块用来集中存放函数、类、常量或其他对象 |

3.1 数字类型

Python中的数字类型可以分为intfloat以及complex。即整数、浮点数和复数,他们都是不可变类型,就是一旦赋值确定了就不能改变了。

整数没什么好说的,就是数学上的整数,包括正整数、0、负整数;浮点数就是小数,带有小数点,可以使用科学计数法,不过这里使用e或者E来表示科学计数法的10,例如3.141.2e2;复数也是数学概念,分为实部和虚部,不过数学上使用i表示虚部,Python使用j表示虚部,就这一些差别。他们的运算都符合数学算术运算,不过实际上复数用的不多,他们也可以做类型提升,整数和浮点数运算得到的是一个浮点数,浮点数和复数运算得到的是一个浮点数。

  1. # 这是注释
  2. >>> A = 1 # 定义整数变量A
  3. >>> B = 3.14 # 定义浮点数变量B
  4. >>> C = A+B # 整数A和浮点数B算术运算的结果存到C中
  5. >>> type(C) # 变量C的数据类型是浮点数
  6. <class 'float'>
  7. #
  8. >>> a = 3+4j # 定义复数a
  9. >>> b = 5+6j # 定义复数b
  10. >>> c = a + b # 复数加法,其实
  11. >>> c.real # 复数c的实部
  12. 8.0
  13. >>> c.imag # 复数c的虚部
  14. 10.0
  15. >>> c.conjugate() # 复数c的共轭复数
  16. (8-10j)
  17. >>> a * b # 复数乘法
  18. (-9+38j)
  19. >>> a / b # 复数除法
  20. (0.6393442622950819+0.03278688524590165j)

布尔类型bool比较简单,它属于是int的子类型,其实False就是0True就是1。不过也没这么简单就是了,而且一般也尽量避免布尔类型和其他类型直接运算。以下都表示为逻辑假:

  • 被定义为假值的常量: NoneFalse
  • 任何数值类型的零: 0, 0.0, 0j, Decimal(0), Fraction(0, 1);分别是整数0,浮点数0,复数0,精确小数位数的0,分数0。
  • 空的序列和多项集: '', (), [], {}, set(), range(0),分别是空字符串、空元组、空列表、空字典、空集合以及空迭代对象。

    3.1.1 数据类型转换

    Python没有数据类型的隐式提升,数据的格式已经决定了其类型。可以通过type(varName)来查看其类型。如果要做强制类型转换,就需要使用函数,有四个函数bool()int()float()comple(),可以自己做尝试。 ```python

    表示下面是int转为float的示例

    val = 1 # 定义变量val,值为整数1 type(val) # 查看val的数据类型是int val_f = float(val) # 强制转换为浮点数val_f type(val_f) type(val) # 实际上val的数据类型还是int val_f # val_f的值已经发生了变化 1.0

comment = ‘int -> complex’ # int转为complex的例子 val = 1 # 变量val为int类型,值为1 type(val) # 查看val的数据类型

val_c = complex(val) # 强制类型转换:int -> complex type(val_c) # 查看val_c的数据类型

val_c # 查看val_c的值 (1+0j) ```

3.1.2 进制转换

整数类型除了正常的10进制(decimal)表示,还有2进制(binary)、8进制(octal)以及16进制(hexadecimal)。从他们的英文表示去记忆他们各自的表示形式。N进制就是逢N进位1。例如10进制就是逢10进1位,每一位只有10个数0~9这10个数,16进制是从0~9,然后由a~f表示10~15。所以理论上最大的进制是10位数字+26个字母=36进制

  • 普通形式就是正常的十进制整数;
  • 二进制整数以0b开头,如0b00b1,没有0x2,这是错误的,逢2进1;
  • 八进制整数以0o开头,如0o20o7,没有0o8,这是错误的,逢8进1;
  • 十六进制整数以0x开头,如0x80xf,没有0xg,这是错误的,逢16进1。

    3.2 字符串

    字符串其实在这里包含了字符串,还有字节串。它也是属于不可变类型,这是由其底层结构决定的。

Python有多种表示字符串的方式,''" "''' '''""" """,里面所写的和这些个标点符号组合起来都是字符串。前两个等价,都是单行字符串,后两个等价,都是多行字符串,后两个多用于文档注释或函数注释。这四个在不同类别之间可以嵌套使用。但是遵循的是“就多原则”,简单理解就是多行字符串可以嵌套单行字符串,不能反过来。相同类别之间嵌套需要转义,这个转义字符就是\,下面就是嵌套示例

  1. # 单行字符串自我嵌套
  2. >>> name = '\'basil\'' # 一个单行字符串name
  3. >>> name # 查看name
  4. "'basil'"
  5. # 多行嵌套单行
  6. # 一个多行字符串desc
  7. >>> desc = '''I love
  8. ... 'Python'
  9. ... very much'''
  10. >>> desc # 查看desc
  11. "I love\n'Python'\nvery much"

3.2.1 格式化

3.2.1.1 基本的%格式化

字符串打印的时候可能需要格式化,所谓的格式化,就是相当于你已经定好基调了,打好了模板了,剩下的就是按部就班的把其它东西放进去就行。组装积木的时候,什么地方放长的积木,什么地方放三角积木,都是决定好了的,照着放就能做出来漂亮的模型大楼了。这也是一样的。

格式 解释
%d%5d%05d%-5d 十进制整数、不足5位补足(右对齐)、不足5位补齐补的是0、不足5位补齐(左对齐)、5可以换成任何整数
%o 八进制格式,也适合上面的数字、0、负号
%x%X 十六进制格式,也适合上面的数字、0、负号,X是使用大写的A~Fx是小写的a~f
%f%7.3f 也有0和负号,和上面一样,但是这里小数点前代表的是位数,后面代表的是小数点后占多少位
%e%E 科学计数法的形式格式化,这俩就是大小写eE的区别
%g%G %g%f%e的简写,%G%f%E的简写
%p 用十六进制数格式化变量的地址,就是用于id(varName)这里
%c 字符,所谓的字符就是ASCII码字符,它其实也是整数数字,不过一般不当数字用。它也支持c之前有数字和负号,0就没用了。
%s 字符串
  1. # 整数数字
  2. >>> '%d|%5d|%-5d|%05d|%-05d' % (5, 55, 555, 5555, 555)
  3. '5| 55|555 |05555|555 '
  4. # 后面的数字都是十进制哦,只是要转为8进制和16进制
  5. >>> '%o|%x|%o|%x' % (7, 15, 8, 16)
  6. '7|f|10|10'
  7. # 浮点数,需要引入math数学包,math.pi代表的就是数学上的圆周率π
  8. >>> import math # 学到的第一个如何引入第三方包
  9. >>> '%f|%7.2f|%.2f|%7f|%07.2f|%.50f' % (math.pi, math.pi, math.pi, math.pi, math.pi, math.pi)
  10. '3.141593| 3.14|3.14|3.141593|0003.14|3.14159265358979311599796346854418516159057617187500'
  11. # 字符,可以看出来0没用
  12. >>> '%c|%c|%7c|%07c|%-7c' % ('A', 65, 'B', 67, 'C')
  13. 'A|A| B| C|C '
  14. # 字符串
  15. >>> '%s' % 'I love China'
  16. 'I love China'
  17. # 混合使用,这里使用了一个函数print,用于打印输出
  18. >>> name = 'basil'
  19. >>> age = 25
  20. >>> hobbies = '''China
  21. ... ZYH
  22. ... Python'''
  23. >>> print("My name is '%s'. I am %3d years old. I love %s" % (name, age, hobbies))
  24. My name is 'basil'. I am 25 years old. I love China
  25. ZYH
  26. Python

3.2.1.2 大括号格式化

大括号这种是使用了一个函数str.format()函数。基本语法是使用{}:来替代%这种很糟糕的形式。它既可以按照顺序给参数,也可以不按照顺序给参数。这个函数接受的参数个数也是不定的,可以很多个。字典和列表我们不打算在这里说,所以先了解即可。对象也即是类我们在后面说,也先仅作了解。

  1. >>>"{} {}".format("hello", "world") # 不设置指定位置,按默认顺序
  2. 'hello world'
  3. >>> "{0} {1}".format("hello", "world") # 设置指定位置
  4. 'hello world'
  5. >>> "{1} {0} {1}".format("hello", "world") # 设置指定位置
  6. 'world hello world'
  7. >>> "NAME: {name}, AGE: {age}".format(name='basil', age=22)
  8. 'NAME: basil, AGE: 24'
  9. # 通过字典设置参数
  10. >>> site = {"name": "basil", "age": 24}
  11. >>> print("NAME: {name}, AGE: {age}".format(**site))
  12. 'NAME: basil, AGE: 24'
  13. # 通过列表索引设置参数
  14. >>> L = ['basil', 24]
  15. >>> "NAME: {0[0]}, AGE: {0[1]}".format(L) # "0" 是必须的
  16. 'NAME: basil, AGE: 24'
  17. # 通过传入对象格式化
  18. >>> class AssignValue(object):
  19. ... def __init__(self, value):
  20. ... self.value = value
  21. ...
  22. >>> my_value = AssignValue(6)
  23. >>> print('value 为: {0.value}'.format(my_value)) # "0" 是可选的

相比于%格式化那种数据类型的控制,大括号也有相似的形式

数字 格式 输出 描述
3.1415926 {:.2f} 3.14 保留小数点后两位
3.1415926 {:+.2f} +3.14 带符号保留小数点后两位
-1 {:+.2f} -1.00 带符号保留小数点后两位
2.71828 {:.0f} 3 不带小数
5 {:0>2d} 05 数字补零 (填充左边, 宽度为2)
5 {:x<4d} 5xxx 数字补x (填充右边, 宽度为4)
10 {:x<4d} 10xx 数字补x (填充右边, 宽度为4)
1000000 {:,} 1,000,000 以逗号分隔的数字格式
0.25 {:.2%} 25.00% 百分比格式
1000000000 {:.2e} 1.00e+09 指数记法
13 {:>10d} 13 右对齐 (默认, 宽度为10)
13 {:<10d} 13 左对齐 (宽度为10)
13 {:^10d} 13 中间对齐 (宽度为10)
11 '{:b}'.format(11) ``'{:d}'.format(11) ``'{:o}'.format(11) ``'{:x}'.format(11) ``'{:#x}'.format(11) ``'{:#X}'.format(11) 1011
11
13
b
0xb
0XB
2进制
10进制
8进制
16进制
16进制
16进制

^, <, > 分别是居中、左对齐、右对齐,后面带宽度, : 号后面带填充的字符,只能是一个字符,不指定则默认是用空格填充。+表示在正数前显示 +,负数前显示 -`(空格)表示在正数前加空格。bdox分别是二进制、十进制、八进制、十六进制。此外我们可以使用大括号{}` 来转义大括号。

3.2.1.3 f-string格式化

f-string 是 python3.6 之后版本添加的,称之为字面量格式化字符串,是新的格式化字符串的语法。f-string 格式化字符串以 f 开头,后面跟着字符串,字符串中的表达式用大括号 {} 包起来,它会将变量或表达式计算后的值替换进去,实例如下:

  1. >>> name = 'basil'
  2. >>> f'Hello {name}' # 替换变量
  3. 'Hello basil'
  4. >>> f'{1+2}' # 使用表达式
  5. '3'
  6. >>> w = {'name': 'basil', 'age': 22}
  7. >>> f'{w["name"]}: {w["age"]}'
  8. 'baisl: 22'

用了这种方式明显更简单了,不用再去判断使用 %s,还是 %d。在 Python 3.8 的版本中可以使用 = 符号来拼接运算表达式与结果:

  1. >>> x = 1
  2. >>> print(f'{x+1}') # Python >= 3.6,低版本不可用哈
  3. 2
  4. >>> x = 1
  5. >>> print(f'{x+1=}') # Python >= 3.8,低版本不可用哈
  6. 'x+1=2'

3.2.2 转义字符

用于控制字符串格式的除了格式化,还有转义字符。所谓转义字符,就是多个字符连在一起,有了控制格式的含义。如果需要使用这种字符,还需要额外的转义,或者使用raw字符串,即字符串界定符前面加字幕 r 表示原始字符串,其中特殊字符不进行转义,但是字符串的最后一个字符不能是\,主要用于正则表达式、文件路径、URL等地方。

转义字符 含义 转义字符 含义
\\b 退格,把光标移动到前一列位置 \\ 一个斜线\
\\f 换页符 ' 单引号’
\\n 换行符 " 双引号”
\\r 回车 \\ooo 3位八进制数对应的字符
\\t 水平制表符 \\xhh 2位十六进制数对应的字符
\\v 垂直制表符 \\uhhhh 4位十六进制数表示的Unicode字符

4. 基本的输入输出

Python从命令行接受输入使用input()函数,input()的参数是提示用户输入的字符串,你需要获取输入,需要使用诸如varName = input('Prompt')这种形式,它返回输入结果都是字符串。如果要使用它返回的具体数据类型,可以使用varName = eval(input('tips: ')),也就是使用了eval()函数。这样子Python解释器会自动确定它是什么类型,不过适用于基本类型,不适用高级类型和自定义类型。

Python向命令行输出使用print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False) ,它的参数解释如下

  • value:是待输出的值,可以写0个或者1个或者多个,却决于你的需要;
  • sep: separator的缩写,也就是多个value之间使用什么符号分割,默认是一个空格;
  • file: 一个文件类型的流对象,默认是sys.stdout也就是命令行;
  • end: 表示输出结束的时候,使用什么符号结尾,默认是换行符\n
  • flush: 是否需要强制刷新file流对象,默认是否。


    Python可以使用文件作为输入或者输出的位置。可以使用open(r'C:\test.txt', 'a+')以追加的方式打开。有关打开的方式这里不做详细介绍,因为也用不到,之后我们专门开一章介绍文件流对象。

    5. 函数、类与模块

    Python支持多种编程范式,一种是面向过程,一种是面向对象,还有一种是函数式编程。

面向过程就是传统的写多个函数,解释每一步需要做什么,然后指示计算机去做,这就是面向过程,好处是你可以控制每一步,坏处是太累了。

面向对象也就是OOP(Object-Oriented Programming)的方式,它就是把万事万物想做是对象,规定每个对象所拥有的属性(data)以及可以做的事情(method)。然后通过对象之间的交互,组成一个系统。好处是相对简单,符合人类的思考方式,坏处是要写太多太多的代码了。

函数式编程也不是什么新概念,就是一个老概念,不过这些年很火热。Python通过支持lambda表达式来支持这种编程范式。

从这里开始,这些函数、类、模块,我都将其定义在一个文件中,文件名使用test.py,其中.py后缀表示这是一个Python文件,运行的时候,在命令行先定位到这个文件所在目录,然后输入python test.py,就可以运行了。如果没有特殊声明,都可以这样使用。如果你使用的是某个IDE,则直接点击按钮运行就好了。

5.1 文件

先说文件名后缀,不过目前阶段只需要知道.py文件是源码文件就行了。

  • .py:Python源文件,由Python解释器负责解释执行。
  • .pyw:Python源文件,常用于图形界面程序文件。
  • .pyc:Python字节码文件,可用于隐藏Python源代码和提高运行速度。
    • 对于Python模块,第一次被导入时将被编译成字节码的形式,并在以后再次导入时优先使用.pyc文件,以提高模块的加载和运行速度。
    • 对于非模块文件,直接执行时并不生成.pyc文件,但可以使用py_compile模块的compile()函数进行编译以提高加载和运行速度。
    • 另外,Python还提供了compileall模块,其中包含compile_dir()compile_file()compile_path()等方法,用来支持批量Python源程序文件的编译。
  • .pyo:优化的Python字节码文件,同样无法使用文本编辑器直接查看其内容。可以使用python –O -m py_compile file.pypython –OO -m py_compile file.py进行优化编译。Python 3.5不再支持.pyo文件。
  • .pyd:一般是由其他语言编写并编译的二进制文件,常用于实现某些软件工具的Python编程接口插件或Python动态链接库。

每个Python脚本在运行时都有一个__name__属性,如果脚本作为模块被导入,则其__name__属性的值被自动设置为模块名。如果脚本独立运行,则其__name__属性值被自动设置为__main__,利用__name__属性即可控制Python程序的运行方式。也即是

  1. # 下面main()这个函数名任意起名,只要是你的函数名即可
  2. # 或者你直接在main()的位置写你的代码也可以
  3. if __name__ == "__main__":
  4. main()

5.2 函数

函数在面向过程中叫做函数,在类中叫做方法。它的基本定义格式如下。其中def是关键字,表示定义一个方法;func_name是函数名,只要符合上述变量命名规则即可;args是参数名,可以在函数体内使用,在调用函数的时候需要传进来;函数体下面使用了passpass是一个占位用的关键字,这样程序不会报错,但是你还是需要写真正的函数体才能使函数发挥出来原有的功效。而且注意格式,四个空格或者一个Tab键,Python里面用缩进表示生效范围,而且空格和Tab键不能混用,否则会报错。

  1. def func_name(args):
  2. pass # 函数体,这里注意格式,通过缩进确定函数的范围

例如我们要定义一个打招呼的函数,它接受用户的姓名,然后向这个人打招呼。你可以多次调用该方法,然后就可以向不同的人打招呼了。

  1. # 定义一个函数
  2. def greet(name):
  3. print("Hello", name)
  4. # 调用,你可以多次调用,也可以向自己打招呼
  5. greet('basil') # 将输出,Hello basil

5.3 类

OOP的基本单元是,核心概念是封装、继承和多态。类就是一类同性质的东西,有着共同的抽象,例如人类,运动员类,学生类。封装就是隐藏,对外是黑盒子,不可见,隐藏的是数据,对外需要提供方法以操纵这些数据;继承就是子类可以继承父类的公开的属性和方法;多态就是父类也可以表示子类。在上面这个例子中,先定义一个人类Person,它有姓名和年龄,并且它有一个工作的方法,然后定义一个Student类,除了有Person类有的,它还可以有学号和班级。

一个类,其实就是定义了一个新的数据类型,比如我们定义Person类,这个类有姓名、年龄,以及工作的方法。这里其实介绍的很简单,等后续我们单独更一章用于介绍类。

  1. # 定义一个Person类,class代表类定义方式,Person是类名
  2. # object是父类,也就是Person继承自object类,它是所有类的父亲,
  3. # 如果一个类的父类是object,那么也可以不写,即
  4. # class Person: # 这样子也是定义了一个Person类,默认继承自object
  5. class Person(object):
  6. __name = '' # 隐藏了姓名这个属性,外界不能直接p.__name调用,默认为空
  7. age = 0 # 为了简便,没有隐藏年龄属性,默认为0
  8. def getName(self): # 为了访问,我们提供了公共的获取姓名的方法
  9. return self.__name
  10. def setName(self, name): # 为了访问,我们提供了公共的设置姓名的方法
  11. self.__name == name
  12. def work(self): # 工作的方法
  13. return 'work'
  14. def toString(self): # 打印的方法,这里使用了字符串的格式化方法,下面讲
  15. return "My name is '%s' and I am %3d years old." % (self.__name, self.age)
  16. p = Person() # 创建了一个Person对象
  17. p.setName('basil') # 这个人叫'basil'
  18. p._Person__name = 'ZYH' # 我们改变了他的名字为ZYH,所以Python的封装隐藏就是闹着玩
  19. p.age = 22 # 设置这个人年龄为22
  20. print(p) # 直接打印,会输出默认的格式,这个可以调整,以后再说
  21. print(p.toString()) # 格式化打印

下面再定义一个Student类,它继承自Person,然后它有一个自己的id属性表示学号。

# 上面是Person的定义和调用,这里略了,这个要紧接上面的代码
# 定义一个Student类,继承自Person
class Student(Person):
    id = 0                            # 只有一个自己的独有的属性id表示学号

s = Student()                        # 定义Student对象s
print(s.toString())                    # 先打印一下,看看是什么结果
s = p                                # 把上面的p赋值给s,看看有没有问题
s.id = 1                            # 设置学号为1
print(s.toString())                    # 再次打印的结果是什么呢?

5.4 模块

5.4.1 使用模块

除了内置模块之外的模块都需要引入,不管是官方的,还是第三方的。第三方的模块还需要使用pip进行安装。导入模块有三种方式:

  • import moduleName [as nickname],其中[]是可以不写的,不过如果moduleName很长,别名nickName就很有必要了,例如引入数学库import math as m。如果不起别名,就需要使用完整的圆周率math.pi引用,起了别名之后就使用m.pi即可了,是不是少写了好几个字母。
  • from moduleName import memberName [as nickname] 这里和上面的区别,就是更精确的引入某写属性和函数,例如from math import pi,就是单独引入了pi,以后使用的时候直接pi就代表了圆周率,而且没有引入别的,比如你想用正弦函数sin()就不能用了,必须重新引入。
  • from moduleName import *时,可以从moduleName中引入所有的属性和函数,例如from math imort *现在不仅可以使用pi,还可以使用sin()函数了。不过如果有同名的函数时,后面定义的会覆盖前面定义的。

Python首先在当前目录中查找需要导入的模块文件,如果没有找到则从sys模块的path变量所指定的目录中查找。可以使用sys模块的path变量查看python导入模块时搜索模块的路径,也可以向其中append()自定义的目录以扩展搜索路径。在导入模块时,会优先导入相应的pyc文件(也就是已编译的缓存文件,加载很快),如果相应的pyc文件与py文件时间不相符,则导入py文件并重新编译该模块。

Best Practice最佳实践

  1. 如果需要导入多个模块,一般建议按如下顺序进行导入:标准库 > 成熟的第三方扩展库 > 自己开发的库
  2. 每个import只导入一个模块

可以使用sys.modules.items()显示所有预加载模块的相关信息,不过在使用之前,要先导入sys模块。其实sys.modules是一个字典,字典的items()函数就列出了字典中所有的条目了。

可以使用dir()函数查看任意模块中所有的对象列表,如果调用不带参数的dir()函数,则返回当前所有名字列表。会列出来的包括模块、类、函数、变量。

在2.x中可以使用reload()函数重新导入一个模块,在3.x中,需要使用imp模块或importlib模块的reload()函数。

5.4.2 自定义模块

现在我们用过什么呢?用过内置的函数,例如int()print()input(),以及非内置包math。其实模块就是包。我们有一天感觉别人提供的都满足不了我们的需要了,我们就需要自己去写一个模块了。我们来写一个学生类的包吧,把上面的完善一下。下面我会在代码首行给出代码所存在的文件名。

首先,我们创建一个文件夹用于存储这个包,命名为stu,然后在里面创建三个文件:__init__.pyperson.py以及student.py。后两个文件的代码如下:

# person.py文件

class Person(object):
    __name = ''
    __age = 0

    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    def __str__(self):
        return "My name is %s. And I am %d years old." % (self.__name, self.__age)
# student.py文件

from stu.person import Person


class Student(Person):
    __id = 0

    def __init__(self, name, age, id):
        super().__init__(name, age)
        self.__id = id

    def __str__(self):
        return super().__str__() + " My stu. ID is %d." % self.__id

包的每个目录中都必须包含一个__init__.py文件,该文件可以是一个空文件,仅用于表示该目录是一个包。__init__.py文件的主要用途是设置__all__变量以及所包含的包初始化所需的代码。其中__all__变量中定义的对象可以在使用from moduleName import *时全部正确导入。我们这里留空,所以只需要创建这个文件就可以了。

然后和stu文件夹平级的文件test.py测试文件代码如下:

# test.py 文件

# 导包
import stu as s                        # 不过这个没用,用它还挺复杂的
import stu.person as sp
from stu.student import Student


def main():
    p = sp.Person('basil', 25)
    basil = p
    basil.__Student_id = 1
    zyh = Student('ZYH', 23, 2)

    print("Basil:", basil)
    print("ZYH:", zyh)


if __name__ == '__main__':
    main()

6. Python之禅

其实就是Python的最佳实践的规范约定,还不一定属于规范,只是指导性的建议。不需要特别记忆,只需要看看就好了,使用就是import this,也是导入了一个包,然后打印一些字符串。

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!