COMP9021笔记

219 阅读17分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

这篇文章持续更新一些学习这门课时语法方面踩过的坑/遇到的问题,如果有错误的地方欢迎交流指正。

数字numbers

  1. 关于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
    
  2. 将数字按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
    
  3. 将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'>
    
  4. 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

  1. str.issapce():如果字符串中只包含空白,则返回 True,否则返回 False

  2. 避免在循环中用+和+=操作符来累加字符串. 由于字符串是不可变的, 这样做会创建不必要的临时对象, 并且导致二次方而不是线性的运行时间. 作为替代方案, 你可以将每个子串加入列表, 然后在循环结束后用.join 连接列表. (也可以将每个子串写入一个 cStringIO.StringIO 缓存中.)

  3. fstring是Python3.6版本之后一种很灵活的控制字符串输出的方式,这篇博文有很详细的介绍。 关于如何用变量指定浮点数输出宽度,可以参考下面这种做法:

    num = 12345.678
    width = 10
    # 保留两位小数
    print(f"{num:.2f}")  # 12345.68
    # 保留两位小数,width个占位符,不足的使用0补充
    print(f"{num:0{width}.2f}")  # 0012345.68
    
  4. [:-1] 对于Python的字符串中意味着什么?

    >>> 'test\n'
    'test\n'
    >>> 'test\n'[:-1]
    'test'
    

    因为它对空字符串也是有效的,所以它在移除 假若存在的最末位的字符(包括\n\r\t等) 时非常安全

    切片不仅对字符串有效,对任何序列都有效。

    对于文件中的line,可以使用line.rstrip('\n')来删除最后的换行符。但有时候文件最后一行并不是以换行符结束(而是以EOF-end of file),这时就可以用切片来移除末尾行最后任意一个符号。

  5. 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
    
  6. index() 方法检测字符串中是否包含子字符串 str ,如果指定 beg(开始) 和 end(结束) 范围,则检查是否包含在指定范围内,在的话返回索引位置。该方法与 python find()方法一样,只不过如果str不在 string中会报一个异常,而find方法不在的话返回-1。

列表list

切片slice

  1. 列表的append()方法返回值是None Type,它只能用于修改原列表,无法连续嵌套使用,例如这样的写法是不合法的

    list1 = [1]
    list1.append(2).append(3)  # 错误写法
    
  2. 列表切片中的深复制与浅复制,请看下面代码

    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]
    
  3. 二维数组的切片:对一个二维数组,想取其中的某一列元素,该怎么操作呢? 请看下面这个例子:

    # 现有一个二维数组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

  1. 元组用"()"标识,内部元素用逗号隔开。但是元组不能二次赋值,相当于只读列表

集合set

  1. 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'}
    >>>
    
  2. 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

  1. Python 字典 items() 方法以 'dict_items' 类型返回可遍历的(键, 值) 元组数组dict.items() 常用for k, v in dict.items()来遍历字典
  2. 列表是有序的对象结合,字典是无序的对象集合。两者之间的区别在于:字典当中的元素是通过键来存取的,而不是通过偏移存取
  3. 字典的key值必须是可哈希的
  4. 关于字典的参考资料: Python3如何将两个列表合并成字典 Python字典 你必须知道的用法系列 list,tuple,dict的数据结构

一些内置方法

  1. 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
    
  2. 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)
    
  3. 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到-2
    

    reversed()函数返回一个反转的迭代器。 可以对range(a,b)使用reversed(),相当与倒序遍历[a,b)

    for b in reversed(range(-3, 5)):
        print(b)
    # 输出4到-3
    
  4. 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()方法效率会稍微高一些

文件处理

  1. f.readlines()返回的是一个列表,包含文件中包含的所有行。

    # 打开一个文件
    f = open("/tmp/foo.txt", "r")  
    str = f.readlines()
    print(str)   
    # 关闭打开的文件
    f.close() 
    # 输出如下
    # ['Python 是一个非常好的语言。\n', '是的,的确非常好!!\n']
    
  2. 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+")
    

关于类与继承

  1. Python中所有东西都是对象(objecet),每一个对象(objecet)都是由某个对象(objecet)创建的,每一个对象都是继承自某种类型。有些对象(objecet)是类(class)
  2. 类,class,可以分为类的属性和方法。Python中以双下划线开头和结尾的属性称为特殊属性,由于对象的方法也属于属性,因此以双下划线开头和结尾的方法称为特殊方法。
  3. 定义类的参数时,默认顺序为:位置参数positional arguments,可变参数,默认参数。位置参数应该始终在可变参数之前,否则会被传入到可变参数内,视为可变参数的一部分
  4. ?如果函数的参数列表第n位是*,那么第n位后的参数必须以arg_name=arg的形式传入,否则就会报错。可以用这种方法强制用户输入参数对应的值
  5. 子类a想要继承父类A,只需要在定义子类时将父类名称写在子类名称后的括号里,例如:
    class a(A):
    	def __init__(self, name):
    		self.name = name
    
  6. 子类会继承父类的所有属性和方法,可以重写父类的属性和方法,还可以定义属于自己的属性和方法。
  7. 一个类的实例被作为参数传入另一个类时,该实例可以作为另一个类的属性值。可以通过另一个类的实例.属性(即类的实例).属性的属性(即类的实例的属性)来调用
  8. isinstance(x, Class)可以用于判断x是否为Class的一个实例,返回True或False

类的属性

  1. 通常我们都在类的__init__方法中定义类的属性,语法self.属性名 = 属性
  2. 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
    
  3. a.__name__返回对象的名称,比如类型(type, class)对象的名称就是系统内置的或自定义的名称字符串,类型的实例通常没有属性 __name__
  4. a.__class__返回的是类型(type),等效于type(a)
  5. a.__base__返回的是类(class)
  6. a.__bases__元组,包含 类型对象(type, class) C 的全部基类,类型的实例通常没有属性__bases__
  7. a.__dict__返回a自身的各项属性,但是不包括它从父类继承的属性
  8. dir(a)可以查看a的所有属性,包括自身属性和从父类继承的属性
  9. 子类的初始化函数.__init__实际上是绑定在父类初始化函数上的

类的方法

  1. a.__repr__()等效于repr(a),会返回<类名 object at 内存地址>,它是面向开发者的,要求准确无误的指向对象
  2. a.__str__()等效于print(a),自定义返回值时只能是字符串,它是面向用户的,具有更强的可读性。
  3. 对于一个类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__()方法的返回值

一些常见的困惑

关于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  # 本地变量名

命名约定

  1. 所谓”内部(Internal)”表示仅模块内可用, 或者, 在类内是保护或私有的
  2. 用单下划线(_)开头表示模块变量或函数是protected的(使用from module import *时不会包含)
  3. 用双下划线(__)开头的实例变量或方法表示类内私有
  4. 将相关的类和顶级函数放在同一个模块里。不像Java,没必要限制一个类一个模块。
  5. 对类名使用大写字母开头的单词(如CapWords, 即Pascal风格),但是模块名应该用小写加下划线的方式(如lower_with_under.py)。尽管已经有很多现存的模块使用类似于CapWords.py这样的命名,但现在已经不鼓励这样做,因为如果模块名碰巧和类名一致,这会让人困扰

应该避免的名称

  1. 单字符名称,除了计数器和迭代器。
  2. 包/模块名中的连字符(-)
  3. 双下划线开头并结尾的名称(Python保留,例如__init__)

什么是Foo?

Foo / **Bar **is an intentionally meaningless placeholder word often used in computer programming.常被作为函数/方法的名称或变量名