本文已参与「新人创作礼」活动,一起开启掘金创作之路。
这篇文章持续更新一些学习这门课时语法方面踩过的坑/遇到的问题,如果有错误的地方欢迎交流指正。
数字numbers
-
关于float和int的强制类型转换:
- float()方法可以接受的参数类型包括代表整数/浮点数的字符串、整数
- int()方法可以接收到参数类型包括代表整数的字符串、浮点数
- 如果将一个代表浮点数的字符串传给
int(),那么会引起ValueError
>>> int('5') 5 >>> float('5.0') 5.0 >>> float('5') 5.0 >>> int(5.0) 5 >>> float(5) 5.0 >>> int('5.0') Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: invalid literal for int() with base 10: '5.0' >>> int(float('5.0')) 5 -
将数字按n位输出(补0)的两种方式,请看下面例子:
# 将数字num按6位输出,差位补0 num = 123 # 方法1:f-string格式控制 print(f'{num:06}') # 000123 # 如果是 print(f'{num:6}') 则输出 123 # 方法2:str.zfill() num_str = str(num) result = num_str.zfill(6) # 字符串右对齐补0 print(result) # 000123 -
将n进制(2 <= n <= 36)数字num转化为10进制数输出: 可以使用int(str, base)方法,
str接收一个代表数字的字符串,base接收这个数字所使用的进制。该方法返回一个int类型的数字>>> num1 = 101010 >>> print(int(str(num1), 2)) 42 >>> num2 = 123456 >>> print(int(str(num2), 7)) 22875 >>> num3_str = '123456' >>> t = int(num3_str, 7) >>> print(t) 22875 >>> print(type(t)) <class 'int'> -
round() 函数的进位准则 奇进偶舍是一种比较精确比较科学的计数保留法,是一种数字修约规则。具体要求如下(以保留两位小数为例):
-
要求保留位数的后一位如果是4或者4以下的数字,则舍去, 例如 5.214保留两位小数为5.21。
-
如果保留位数的后一位如果是6或者6以上的数字,则进上去, 例如5.216保留两位小数为5.22。
-
如果是一位小数保留整数部分 且 小数部分是5,则根据整数部分个位数(奇进偶舍)决定保留结果。
-
如果是n位小数保留n-1位,且第n位小数是5,则要根据第n-1位的小数(奇进偶舍)决定保留结果。
-
如果保留位数的后一位如果是5,且该位数后有数字,则应进上去。例如5.2152保留两位小数为5.22,5.2252保留两位小数为5.23,5.22500001保留两位小数为5.23。
>>> round(5.215,2)#实际并没有进位 5.21 >>> round(5.225,2) 5.22 >>> >>> round(1.5)#此处进位 2 >>> round(1.5)==round(2.5)#偶数舍去 True >>> round(1.15,1) 1.1 >>> round(1.25,1) 1.2 >>> round(1.151,1) 1.2 >>> round(1.251,1) 1.3 -
从统计学的角度,“奇进偶舍”比“四舍五入”要科学,在大量运算时,它使舍入后的结果误差的均值趋于零,而不是像四舍五入那样逢五就入,导致结果偏向大数,使得误差产生积累进而产生系统误差,“奇进偶舍”使测量结果受到舍入误差的影响降到最低。
-
NB:Python由于浮点数的精度问题,它不是严格的“奇进偶舍”,例如官方文档中的例子:
The behavior of round() for floats can be surprising: for example, round(2.675, 2) gives 2.67 instead of the expected 2.68. This is not a bug: it’s a result of the fact that most decimal fractions can’t be represented exactly as a float. See Floating Point Arithmetic: Issues and Limitations for more information.
至于原因,可以参考Python中关于round函数的小坑中的解释:
这跟浮点数的精度有关。我们知道在机器中浮点数不一定能精确表达,因为换算成一串1和0后可能是无限位数的,机器已经做出了截断处理。那么在机器中保存的2.675这个数字就比实际数字要小那么一点点。这一点点就导致了它离2.67要更近一点点,所以保留两位小数时就近似到了2.67。
-
字符串string
-
str.issapce():如果字符串中只包含空白,则返回 True,否则返回 False
-
避免在循环中用+和+=操作符来累加字符串. 由于字符串是不可变的, 这样做会创建不必要的临时对象, 并且导致二次方而不是线性的运行时间. 作为替代方案, 你可以将每个子串加入列表, 然后在循环结束后用
.join连接列表. (也可以将每个子串写入一个 cStringIO.StringIO 缓存中.) -
fstring是Python3.6版本之后一种很灵活的控制字符串输出的方式,这篇博文有很详细的介绍。 关于如何用变量指定浮点数输出宽度,可以参考下面这种做法:
num = 12345.678 width = 10 # 保留两位小数 print(f"{num:.2f}") # 12345.68 # 保留两位小数,width个占位符,不足的使用0补充 print(f"{num:0{width}.2f}") # 0012345.68 -
[:-1]对于Python的字符串中意味着什么?>>> 'test\n' 'test\n' >>> 'test\n'[:-1] 'test'因为它对空字符串也是有效的,所以它在移除 假若存在的最末位的字符(包括\n\r\t等) 时非常安全
切片不仅对字符串有效,对任何序列都有效。
对于文件中的line,可以使用
line.rstrip('\n')来删除最后的换行符。但有时候文件最后一行并不是以换行符结束(而是以EOF-end of file),这时就可以用切片来移除末尾行最后任意一个符号。 -
在 str.rstrip([chars]) 中,当chars由n个字符组成时(即chars=char1char2char3...charn),不能将chars视作一个字符串。 返回的新字符串会把str末尾所有属于chars的char都删除,直到遇到一个非chars的字符为止。
str.lstrip([chars])同理s1 = "123@4'@#@x@#x" print(s1.rstrip('@#x')) # 输出结果是"123@4'"而不是"123@4'@#@x" print(s1.rstrip('@''#''x')) # 输出结果是"123@4'"而不是"123@4"或"123@4@#@x" # 下面这两种写法是错误的 # print(s1.rstrip('@', '#', 'x')) # 会报错TypeError: rstrip expected at most 1 arguments, got 3 # print(s1.rstrip(['@', '#', 'x'])) # 会报错TypeError: rstrip arg must be None or str -
index() 方法检测字符串中是否包含子字符串 str ,如果指定 beg(开始) 和 end(结束) 范围,则检查是否包含在指定范围内,在的话返回索引位置。该方法与 python find()方法一样,只不过如果str不在 string中会报一个异常,而find方法不在的话返回-1。
列表list
切片slice
-
列表的append()方法返回值是None Type,它只能用于修改原列表,无法连续嵌套使用,例如这样的写法是不合法的
list1 = [1] list1.append(2).append(3) # 错误写法 -
列表切片中的深复制与浅复制,请看下面代码
a = [1, 2, 3, 4, 5, 6] b = a[:3] # 浅复制,修改列表a的值不影响列表b的值 c = a # 深复制,列表a与列表c指向同一处内存地址,其中一个改变,另一个随着改变 a[:3] = [6, 7, 8] print(a) # [6, 7, 8, 4, 5, 6] print(b) # [1, 2, 3] print(c) # [6, 7, 8, 4, 5, 6] -
二维数组的切片:对一个二维数组,想取其中的某一列元素,该怎么操作呢? 请看下面这个例子:
# 现有一个二维数组a,想取其中第二列元素[2, 5, 6] a =[[1,2,3], [4,5,6], [7,8,9]] # 先来看几个错误的尝试: print(a[0:3][1:2]) # [[4, 5, 6]] # 上面那行代码实际上是先对a做了切片[0:3]得到[[1,2,3],[4,5,6],[7,8,9]],再对其做切片[1:2]得到[[4, 5, 6]] # 同理下面这行代码是先对a做了切片[1:2]得到[[4,5,6]],再对其做切片[0:3]得到[[4, 5, 6]] print(a[1:2][0:3]) # # 这种写法也是错误的: # print(a[:, 1]) # TypeError: list indices must be integers or slices, not tuple # 正确的写法: b = [i[1] for i in a] print(b) # [2, 5, 8]
列表生成式list comprehension
列表生成式/列表推导式/列表解析式:列表推导式提供了从序列创建列表的简单途径。通常应用程序将一些操作应用于某个序列的每个元素,用其获得的结果作为生成新列表的元素,或者根据确定的判定条件创建子序列。
每个列表推导式都在 for 之后跟一个表达式(表达式可以是任意的,意思是你可以在列表中放入任意类型的对象),然后有零到多个 for 或 if 子句(各语句之间是嵌套关系)。返回结果是一个根据表达从其后的 for 和 if 上下文环境中生成出来的列表。
另:如果希望表达式推导出一个元组,此时元组的括号不可省略
# 对每个元素进行操作
>>> seq = [2, 4, 6]
>>> [3*x for x in seq]
[6, 12, 18]
# 生成列表嵌套列表
>>> [[x, x**2] for x in seq]
[[2, 4], [4, 16], [6, 36]]
# 对序列中每个元素调用某个方法
>>> freshfruit = [' banana', ' loganberry ', 'passion fruit ']
>>> [weapon.strip() for weapon in freshfruit]
['banana', 'loganberry', 'passion fruit']
# 用if子句对序列元素进行过滤
>>> [3*x for x in seq if x > 3]
[12, 18]
>>> [3*x for x in seq if x < 2]
[]
# 不同元素来自于不同序列
>>> vec1 = [2, 4, 6]
>>> vec2 = [4, 3, -9]
>>> [x*y for x in vec1 for y in vec2]
[8, 6, -18, 16, 12, -36, 24, 18, -54]
>>> [x+y for x in vec1 for y in vec2]
[6, 5, -7, 8, 7, -5, 10, 9, -3]
>>> [vec1[i]*vec2[i] for i in range(len(vec1))]
[8, 12, -54]
# 列表推导式可以使用复杂表达式或者嵌套函数
>>> [str(round(355/113, i)) for i in range(1, 6)]
['3.1', '3.14', '3.142', '3.1416', '3.14159']
列表推导式的执行顺序:左边第二个语句是最外层,依次往右进一层;左边第一条语句是最后一层。
# 列表推导式的执行顺序:左边第二个语句是最外层,依次往右进一层;左边第一条语句是最后一层。
[x*y for x in range(1,5) if x > 2 for y in range(1,4) if y < 3]
# 执行顺序
list1 = []
for x in range(1,5):
if x > 2:
for y in range(1,4):
if y < 3:
list1.append(x * y)
# 该列表为[3, 6, 4, 8]
在一个列表生成式中,for前面的if ... else是表达式,而for后面的if是过滤条件,不能带else
[x if x % 2 == 0 else -x for x in range(1, 11)]
# 输出[-1, 2, -3, 4, -5, 6, -7, 8, -9, 10]
[x if x % 2 == 0 for x in range(1, 11)]
# 报错
# File "<stdin>", line 1
# [x if x % 2 == 0 for x in range(1, 11)]
# ^
# SyntaxError: invalid syntax
元组tuple
- 元组用"()"标识,内部元素用逗号隔开。但是元组不能二次赋值,相当于只读列表
集合set
s.update( "字符串" )与s.update( {"字符串"} )含义不同:s.update( {"字符串"} )将字符串添加到集合中,有重复的会忽略。s.update( "字符串" )将字符串包含的每个字符逐个添加到集合中,有重复的会忽略。>>> s = set(("Google", "Runoob", "Taobao")) >>> print(s) {'Google', 'Runoob', 'Taobao'} >>> s.update({"Facebook"}) >>> print(s) {'Google', 'Runoob', 'Taobao', 'Facebook'} >>> s.update("Yahoo") >>> print(s) {'h', 'o', 'Facebook', 'Google', 'Y', 'Runoob', 'Taobao', 'a'} >>>s.intersection()不改变原集合只返回取交集的结果s.intersection_update()改变原集合,不返回值s.difference()与s.difference_update()、s.symmetric_difference()与s.symmetric_difference_update()同理s.union()不改变原集合只返回取并集的结果 注意,没有s.union_update()方法,想将原集合修改为与其他集合取并集的结果应使用s.update()方法>>> a = {1,2,3} >>> b = {3,4,5} >>> a.intersection(b) {3} >>> a {1, 2, 3} >>> a.intersection_update(b) >>> a {3} >>> a.union(b) {3, 4, 5} >>> a {3} >>> a.union_update(b) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'set' object has no attribute 'union_update' >>> a.update(b) >>> a {3, 4, 5}
字典dictionary
- Python 字典 items() 方法以 'dict_items' 类型返回可遍历的(键, 值) 元组数组。
dict.items()常用for k, v in dict.items()来遍历字典 - 列表是有序的对象结合,字典是无序的对象集合。两者之间的区别在于:字典当中的元素是通过键来存取的,而不是通过偏移存取
- 字典的key值必须是可哈希的
- 关于字典的参考资料: Python3如何将两个列表合并成字典 Python字典 你必须知道的用法系列 list,tuple,dict的数据结构
一些内置方法
-
enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列(对象),同时列出数据和数据下标,一般用在 for 循环当中
enumerate(sequence, [start=0])>>>seasons = ['Spring', 'Summer', 'Fall', 'Winter'] >>> list(enumerate(seasons)) [(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')] >>> list(enumerate(seasons, start=1)) # 下标从 1 开始 [(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')] >>> seq = ['one', 'two', 'three'] >>> for i, element in enumerate(seq): ... print(i, element) ... 0 one 1 two 2 three -
zip()函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的一个可迭代对象,这样做的好处是节约了不少的内存
zip([iterable, ...])
list(zip([iterable, ...]))机器学习模型训练中,经常需要打乱数据集,用 zip() 函数可以实现如下:import random X = [1, 2, 3, 4, 5, 6] y = [0, 1, 0, 0, 1, 1] zipped_data = list(zip(X, y)) # 将样本和标签一 一对应组合起来,并转换成list类型方便后续打乱操作 random.shuffle(zipped_data) # 使用random模块中的shuffle函数打乱列表,原地操作,没有返回值 new_zipped_data = list(map(list, zip(*zipped_data))) # zip(*)反向解压,map()逐项转换类型,list()做最后转换 new_X, new_y = new_zipped_data[0], new_zipped_data[1] # 返回打乱后的新数据 print('X:',X,'\n','y:',y) print('new_X:',new_X, '\n', 'new_y:',new_y) -
range() 的负数step与reversed() 函数 range()函数返回的是一个可迭代对象(非列表类型)
range(stop)range(start, stop[, step])range的step参数为负数的逻辑是:先按正序的左闭右开区间取值,然后再反序输出。而不是在左开右闭的区间里反序取值# for i in range(a,b,-1) # 当a > b时,需要加第三个参数-1,代表倒序输出 # 倒序输出range()时,仍然是在左闭右开区间内倒序,即取不到第二个参数 for a in range(5,-3,-1): print(a) # 输出5到-2reversed()函数返回一个反转的迭代器。 可以对range(a,b)使用reversed(),相当与倒序遍历[a,b)
for b in reversed(range(-3, 5)): print(b) # 输出4到-3 -
sorted()函数 week4重点 sorted() 是Python3的内置函数,它可以对所有可迭代的对象进行排序操作,并且不改变原对象的值,返回一个新的排序后的列表。
sorted(iterable, key=None[, reverse=False])其中key可以为lambda函数 如果想实现由大到小排序,可以令key值为负。也可以通过传入第三个参数 reverse=True来实现反向排序。 如果需要依靠多个元素排序,则用括号括起,依次声明。例如:maximum_dict1 = sorted(dict1.items(), key=lambda i: (-len(i[1]), -i[0][1], i[0][0]))i代表传入的第一个参数(一个可迭代对象,这里是dict1.items())中的每一个元素。
NB: sort() 是应用在list上的方法,它具有如下特性:
- 这个方法会修改原始的 list(返回值为None)
- 通常这个方法不如sorted()方便
- 如果你不需要原始的 list,list.sort()方法效率会稍微高一些
文件处理
-
f.readlines()返回的是一个列表,包含文件中包含的所有行。
# 打开一个文件 f = open("/tmp/foo.txt", "r") str = f.readlines() print(str) # 关闭打开的文件 f.close() # 输出如下 # ['Python 是一个非常好的语言。\n', '是的,的确非常好!!\n'] -
open(filename, mode)将 mode 设置为 w+ 或 a+ 时,发现直接进行读操作,得到的内容都是空,但原因不太相同: 如果 mode 设置为 w+,即使没有执行 write 操作,也会将文件内容清空,因此这个时候直接进行读草稿,读到的是空内容。f = open("E:\\administrator\\Desktop\\test.txt", "w+") ``` 如果 mode 设置为 a+,文件指针位置默认在最后面,因为读内容时,是按照指针的位置往后读,所以若指针位置在最后,读出来即是空。在读之前,一定要注意确认好指针位置是对的。 ```python f = open("E:\\administrator\\Desktop\\test.txt", "a+") f.write("append content") print(f.tell()) #此时指针在文件字符末尾处 f.seek(0) print(f.tell()) # 指针回到0的位置 str = f.read() print(str) f.close()f = open("E:\\administrator\\Desktop\\test.txt", "w+")
关于类与继承
- Python中所有东西都是对象(objecet),每一个对象(objecet)都是由某个对象(objecet)创建的,每一个对象都是继承自某种类型。有些对象(objecet)是类(class)
- 类,class,可以分为类的属性和方法。Python中以双下划线开头和结尾的属性称为特殊属性,由于对象的方法也属于属性,因此以双下划线开头和结尾的方法称为特殊方法。
- 定义类的参数时,默认顺序为:位置参数positional arguments,可变参数,默认参数。位置参数应该始终在可变参数之前,否则会被传入到可变参数内,视为可变参数的一部分
- ?如果函数的参数列表第n位是
*,那么第n位后的参数必须以arg_name=arg的形式传入,否则就会报错。可以用这种方法强制用户输入参数对应的值 - 子类a想要继承父类A,只需要在定义子类时将父类名称写在子类名称后的括号里,例如:
class a(A): def __init__(self, name): self.name = name - 子类会继承父类的所有属性和方法,可以重写父类的属性和方法,还可以定义属于自己的属性和方法。
- 一个类的实例被作为参数传入另一个类时,该实例可以作为另一个类的属性值。可以通过
另一个类的实例.属性(即类的实例).属性的属性(即类的实例的属性)来调用 isinstance(x, Class)可以用于判断x是否为Class的一个实例,返回True或False
类的属性
- 通常我们都在类的__init__方法中定义类的属性,语法
self.属性名 = 属性 - Python支持在类的外部添加和删除属性
class Person: def __init__(self, name, age): self.name = name if age < 0: raise PersonException(“Age should be a positive integer!") self.age = age # 实例化一个student student_1 = Person('Tony', 22) # 给student_1添加属性degree student_1.degree = 'Bachelor' # 删除student_1的age属性 del student_1.age a.__name__返回对象的名称,比如类型(type, class)对象的名称就是系统内置的或自定义的名称字符串,类型的实例通常没有属性 __name__a.__class__返回的是类型(type),等效于type(a)a.__base__返回的是类(class)a.__bases__元组,包含 类型对象(type, class) C 的全部基类,类型的实例通常没有属性__bases__a.__dict__返回a自身的各项属性,但是不包括它从父类继承的属性dir(a)可以查看a的所有属性,包括自身属性和从父类继承的属性- 子类的初始化函数
.__init__实际上是绑定在父类初始化函数上的
类的方法
a.__repr__()等效于repr(a),会返回<类名object at内存地址>,它是面向开发者的,要求准确无误的指向对象a.__str__()等效于print(a),自定义返回值时只能是字符串,它是面向用户的,具有更强的可读性。- 对于一个类a来说:
- 如果只定义了__repr__方法,
repr(a),a.__repr__(),print(a),a.__str__()都返回.repr()方法的返回值 - 如果只定义了__str__方法,
repr(a),a.__repr__()返回<类名object at内存地址>;print(a),a.__str__()返回.str()方法的返回值 - 如果__repr__方法和__str__方法都有定义,则
repr(a),a.__repr__()返回.__repr__()方法的返回值;print(a),a.__str__()返回.__str__()方法的返回值
- 如果只定义了__repr__方法,
一些常见的困惑
关于main
一些人不明白为什么要在主程序前写if __name__ == '__main__':
详见:Python中if name == ‘main‘:是什么意思?
在函数调用时,当前的程序名称是main,被调用的程序名称是其“文件名”。这样在调用上述函数的时候,if条件不满足,各种检验不会运行。
因为即使是一个打算被用作脚本的文件,也应该是可导入的。所有的顶级代码在模块导入时都会被执行。但如果简单的导入导致这个脚本的主功能(main functionality)被执行,这是不必要的。 所以主功能应该放在一个main()函数中,防止该脚本被导入时主功能被执行。 NB:不要去 调用函数、创建对象或者执行那些不应该在使用pydoc时被执行的操作。
关于Python命名规则
module_name # 模块名
package_name # 包名
ClassName # 类名
method_name # 方法名
ExceptionName # 异常名
function_name # 函数名
GLOBAL_VAR_NAME # 全局变量名
instance_var_name # 实例变量名
function_parameter_name # 函数参数名
local_var_name # 本地变量名
命名约定
- 所谓”内部(Internal)”表示仅模块内可用, 或者, 在类内是保护或私有的
- 用单下划线(_)开头表示模块变量或函数是protected的(使用from module import *时不会包含)
- 用双下划线(__)开头的实例变量或方法表示类内私有
- 将相关的类和顶级函数放在同一个模块里。不像Java,没必要限制一个类一个模块。
- 对类名使用大写字母开头的单词(如CapWords, 即Pascal风格),但是模块名应该用小写加下划线的方式(如lower_with_under.py)。尽管已经有很多现存的模块使用类似于CapWords.py这样的命名,但现在已经不鼓励这样做,因为如果模块名碰巧和类名一致,这会让人困扰
应该避免的名称
- 单字符名称,除了计数器和迭代器。
- 包/模块名中的连字符(-)
- 双下划线开头并结尾的名称(Python保留,例如__init__)
什么是Foo?
Foo / **Bar **is an intentionally meaningless placeholder word often used in computer programming.常被作为函数/方法的名称或变量名