1. 变量和简单数据类型

1.1 变量命名和使用

变量命名规则:

  • 只能包含字母(abc…)、数字(123..)、下划线(_),且不能以数字开头
  • 不能用空格、应用下划线来隔开不同单词
  • 使用小写的变量名,且简短又具有描述性,如name_length比length_of_persons_name好,name比n好
  • 慎用小写字母l和大写字母O,容易被错看成数字1和0

1.2 字符串

用引号或双引号括起来的都是字符串。在字符串中含有单引号时,需将字符串有双引号括起来。


函数用法

  • str.makerans,创建字符与字符之间的映射表,str是一个类

    1. intab = "aeiou"
    2. outtab = "12345"
    3. trantab = str.maketrans(intab, outtab)
    4. print(trantab)
    5. # {97: 49, 101: 50, 105: 51, 111: 52, 117: 53}
  • string.translate(trantab) ,根据映射表对string进行映射,如果映射表没有该字符,则不映射。

    str1 = "this is string example....wow!!!"
    print (str1.translate(trantab)) 
    #th3s 3s str3ng 2x1mpl2....w4w!!!
    
  • str.split(str="", num=string.count(str))

按照str将字符串分割成num个并存入列表中

  • str.strip(str='')

移除字符串中首尾指定的字符,首尾有多个连续一样的字符也会移除,

1.3 数字

python使用两个*来表示乘方(幂)运算。
在用+号进行字符串拼接时,如果包含数字,python无法自动将其转换为字符串,需使用str()函数。


1.4 格式化输出

  • %06d,表示输出的整数显示位数,不⾜以0补全,超出当前位数则原样输出
  • %6d,表示输出的整数显示位数,不足以空格补全
  • %.2f,表示⼩数点后显示的⼩数位数
  • 还可用f’{表达式}’ ```python age = 18 name = ‘TOM’ weight = 75.5 student_id = 1

    我的名字是TOM

    print(‘我的名字是%s’ % name)

    我的学号是0001

    print(‘我的学号是%4d’ % student_id)

    我的体重是75.50公⽄

    print(‘我的体重是%.2f公⽄’ % weight)

    我的名字是TOM,今年18岁了

    print(‘我的名字是%s,今年%d岁了’ % (name, age))

我的名字是TOM,明年19岁了

print(‘我的名字是%s,明年%d岁了’ % (name, age + 1))

我的名字是TOM,明年19岁了

print(f’我的名字是{name}, 明年{age + 1}岁了’)


---


<a name="7YVPn"></a>
# 2. 列表
<a name="yCMV2"></a>
## 2.1 在列表中添加元素

- `list.append()`,在列表末尾添加一个元素
- `list.insert(index,value)`,在列表index处添加一个元素,此操作将把index处以及index后的所有元素向右平移一个位置

---

在列表中删除元素:

- `del list[index]`,删除列表index处的元素。
- `value=list.pop(index)`,删除列表index处的值,并将该值返回。index默认为列表最后一个元素索引。

注意:在删除元素不是列表最后一个时,删除过后,列表中的所有元素的位置会调整,即索引会向左移一位。

- `list.remove(value)`,删除列表**第一个**值为value的元素。

---

<a name="RibwJ"></a>
## 2.2 对列表排序

- `list.sort(reverse=False)`,对原列表**按顺序(ASCLL码顺序)**从小到大排列,当reverse为True时,逆序。
- `sorted(list,reverse=False)`,与sort()一样,但sorted不改变原列表,而是返回一个新的排序后的列表。
- `list.reverse()`,对反转列表,这里的反转是**指索引顺序**的反转。

---

<a name="N1AQ5"></a>
## 2.3 创建数值列表

- `list(range(start,end,stride))`,使用range创建迭代式,使用list()将其转换成列表,创建从start到end的列表,不包含end,步长为stride,默认为1。

---

<a name="vTlms"></a>
## 2.4 对数字列表进行统计运算

- `min(list);max(list);sum(list)`

---

<a name="3YgFT"></a>
## 2.5 列表解析式
`list = [function(value),for value in range()]`

- `list = [value**2 for value in range(1,11)]`,表示遍历1到10,对其做求平方运算,并将运算后的值添加到列表中。

<a name="mcFmx"></a>
## 2.6 列表访问(切片)

- **访问最后一个元素**时,可使用`list[-1]`。避免索引出错。
- `newlist=list[x:y]`,切取原列表**索引位置x到y-1(不包含y**)的元素并返回一个新的列表。列表索引从0开始。
- `newlist=list[:y];newlist=list[x:]`切取0到y-1的元素;切取x到列表最后一个元素(包含该元素)。
- `newlist=list[-x:]`切取列表最后x个元素。
- `newlist=list[::x]`,对list列表进行切片,每隔x个元素提取一个元素出来
```python
list_1 = [1,2,3,4,5]
newlist = list_1[::2]
print(newlist) # [1,3,5]

2.7 复制列表

  • newlist=list[:],复制一个新列表,本质是切片。注意不要直接把列表赋给一个新的变量,这没有意义。

3. 集合

  • 创建集合的方式

set = {'1','2','3'}

  • 集合之间的运算
    name1 = {'Alex', 'Jack', 'Rain', 'Ruby', 'Mack'}
    name2 = {'铁蛋', '催化', '赵四', 'Alex', 'Mack'}
    # 交集
    name1.intersection(name2) # 返回一个集合{'Alex', 'Mack'}
    # 并集 合并两个集合的元素并去除重复,返回一个新的集合
    name1.union(name2) # {'赵四', 'Alex', '催化', 'Ruby', '铁蛋', 'Mack', 'Jack', 'Rain'}
    # 差集 只在集合name1,不再集合name2的元素,返回一个新的集合
    name1.difference(name2)# {'Ruby', 'Rain', 'Jack'}
    

    集合的特点

    • 集合内部不允许重复的元素
    • 关系测试

4. 字典

注意:字典中键名是唯一的,值可以不唯一

4.1 创建与修改字典:

  • dict={},创建一个空字典
  • dict[key]=value,如果字典中没有键key,则添加新的键值对,如果已经有键key,则修改该键的value。
  • del dict[key],删除字典中的键key对应的键值对。

4.2 字典遍历:

  • for key, value in dict.items(),遍历字典的键和值。注意遍历的顺序不一定与存储时的顺序一样。
  • for key in dict.keys(),遍历字典的所有键。
  • for value in dict.values(),遍历字典的所有值,包含重复的值。可使用set()来去除重复的值

5. 用户输入与while循环小tips

  • return = input('字符串'),获取键盘输入,并返回,注意:不管输入什么,return都是字符串类型。
  • 删除包含特定值的所有列表元素:
    while value in list:
      list.remove(value)
    
    知道list中不再有value时,才停止循环,这可以删除list中的所有value元素。

  • 在for循环和while循环后可以加else语句,该语句是循环正常结束之后执行的代码 ```python str1 = ‘itheima’ for i in str1: if i == ‘e’:
      print('遇到e不打印')
      break
      print(i)
    
    else: print(‘循环正常结束之后执⾏的代码’)
<a name="9WcWw"></a>
# 6. 函数
tips:在函数定义时,第一行加上三引号圈起来的字符串表示函数描述信息
```python
def function():
    """描述信息"""
    pass

6.1 变量作用域

变量作⽤域指的是变量⽣效的范围,主要分为两类:局部变量和全局变量。

  • 局部变量

定义在函数体内部的变量,只在函数体内部生效。当函数调用完成后,则销毁局部变量

  • 全局变量

函数体内、外都能生效的变量。
如果有有一个数据,函数A和B都要使用,则将该数据存储在一个全局变量里。

a = 100
def testA():
    print(a)
def testB():
    a = 200
    print(a)
testA() # 100
testB() # 200
print(f'全局变量a = {a}') # 全局变量a = 100

思考:在testB函数内部的a = 200中的变量a是在修改全局变量 a 吗?
答:不是。观察上述代码发现,15⾏得到a的数据是100,仍然是定义全局变量a时候的值,而没有返回。
也就是说,在函数体内部是不能修改全局变量的


思考:如何在函数体内部修改全局变量?
在函数中用global关键字声明该变量是全局变量。此时a=200就是在修改全局变量的a而不是新建一个局部变量a

def testB():
 # global 关键字声明a是全局变量
 global a
 a = 200

6.2 函数的参数

  • 位置参数

调⽤函数时根据函数定义的参数位置来传递参数。

def user_info(name, age, gender):
    print(f'您的名字是{name}, 年龄是{age}, 性别是{gender}')
user_info('TOM', 20, '男')

注意:传递和定义参数的顺序及个数必须⼀致。


  • 关键字参数

函数调⽤,通过“键=值”形式加以指定。可以让函数更加清晰、容易使⽤,同时也清除了参数的顺序需求

def user_info(name, age, gender):
    print(f'您的名字是{name}, 年龄是{age}, 性别是{gender}')
user_info('Rose', age=20, gender='⼥')
user_info('⼩明', gender='男', age=16)

注意:函数调⽤时,如果有位置参数时,位置参数必须在关键字参数的前⾯,但关键字参数之间不存在先后顺序。


  • 不定长参数

不定⻓参数也叫可变参数。⽤于不确定调⽤的时候会传递多少个参数(不传参也可以)的场景。此时,可⽤包裹(packing)位置参数,或者包裹关键字参数,来进⾏参数传递,会显得⾮常⽅便。

  • 包裹位置传递 ```python def user_info(*args): print(args)

user_info(‘TOM’) # (‘TOM’,)

user_info(‘TOM’, 18) # (‘TOM’, 18)

注意:传进的所有参数都会被args变量收集,它会**根据传进参数的位置合并为⼀个元组(tuple)**, args是元组类型,这就是包裹位置传递。

   - 包裹关键字传递
```python
def user_info(**kwargs):
    print(kwargs)
# {'name': 'TOM', 'age': 18, 'id': 110}
user_info(name='TOM', age=18, id=110)

即以键值对的方式传入参数,然后被打包成一个字典。

6.2 引用

在python中,值是靠引⽤来传递来的。 我们可以⽤id()来判断两个变量是否为同⼀个值的引⽤。 我们可以将id值理解为那块内存的地址标识。

# 1. int类型
a = 1
b = a
print(b) # 1
print(id(a)) # 140708464157520
print(id(b)) # 140708464157520
a = 2
print(b) # 1,说明int类型为不可变类型
print(id(a)) # 140708464157552,此时得到是的数据2的内存地址
print(id(b)) # 140708464157520
# 2. 列表
aa = [10, 20]
bb = aa
print(id(aa)) # 2325297783432
print(id(bb)) # 2325297783432
aa.append(30)
print(bb) # [10, 20, 30], 列表为可变类型
print(id(aa)) # 2325297783432
print(id(bb)) # 2325297783432

不可变类型的意思是如果变量a和b地址相同,此时改变变量a的值,b的值不会变。
可变类型的意思是,改变变量a的值,则b的值也会改变。

  • 可变类型
    • 列表
    • 字典
    • 集合
  • 不可变类型
    • 整型
    • 浮点型
    • 字符串
    • 元组

对于不可变类型,如果值改变了,其id值也会变。而对于可变类型,值改变了,id值不会变

注意:因此当列表作为参数传入函数时,如果不希望改变原列表的值,则实参写成list[:]


6.3 lambda表达式

1 lambda 参数列表 : 表达式

  • lambda表达式的参数可有可⽆,函数的参数在lambda表达式中完全适⽤。
  • lambda函数能接收任何数量的参数但只能返回⼀个表达式的值
    fn = lambda x, y:x+y
    print(fn(2,3)) # 5
    

lambda的应用

  • 带判断的lambda
    print((lambda **kwargs: kwargs)(name='python', age=20))
    
    列表数据按字典key的值排序 ```python students = [ {‘name’: ‘TOM’, ‘age’: 20}, {‘name’: ‘ROSE’, ‘age’: 19}, {‘name’: ‘Jack’, ‘age’: 22} ]

    按name值升序排列

    students.sort(key=lambda x: x[‘name’]) print(students)

按name值降序排列

students.sort(key=lambda x: x[‘name’], reverse=True) print(students)

按age值升序排列

students.sort(key=lambda x: x[‘age’]) print(students)

这里,参数key是排序用关键字,lamda表达式中x是列表里的每个元素,即字典,对于name,返回字典键为name的值传给sort函数的参数key用于排序时比较的对象。

---

<a name="886d064c"></a>
## 6.4 高阶函数
把函数作为参数传⼊,这样的函数称为⾼阶函数,⾼阶函数是函数式编程的体现。函数式编程就是指这种⾼度抽象的编程范式。

- 高阶函数示例
```python
def sum_num(a, b, f):
    return f(a) + f(b)
result = sum_num(-1, 2, abs)
print(result) # 3

  • 内置高阶函数
    • map()

map(func, lst),将传⼊的函数变量func作⽤到lst变量的每个元素中,并将结果组成新的列表(Python2)/ 迭代器(Python3)返回。

list1 = [1, 2, 3, 4, 5]
def func(x):
    return x ** 2
result = map(func, list1)
print(result) # <map object at 0x0000013769653198>
print(list(result)) # [1, 4, 9, 16, 25]
  • reduce()

reduce(func(x,y),lst),其中func必须有两个参数。每次func计算的结果继续和序列的下⼀个元素做累积计算。

注意:reduce()传⼊的参数func必须接受2个参数。

# 计算 list1 序列中各个数字的累加和。
import functools
list1 = [1, 2, 3, 4, 5]
def func(a, b):
    return a + b
result = functools.reduce(func, list1) # 即func的返回值和列表中的下一个元素会作为参数传入func
print(result) # 15
  • filter()

filter(func, lst)函数⽤于过滤序列, 过滤掉不符合条件的元素, 返回⼀个 filter 对象(属于迭代器,可以用for循环迭代,但不能用下标取出元素)。如果要转换为列表, 可以使⽤ list() 来转换

list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
def func(x):
    return x % 2 == 0
result = filter(func, list1)
print(result) # <filter object at 0x0000017AF9DC3198>
print(list(result)) # [2, 4, 6, 8, 10]

注意:func函数需要返回一个bool值用于判断是否过滤掉此元素。


7. 文件操作

7.1 文件的基本操作

  • 打开
    open(name, mode)
    
    name:是要打开的⽬标⽂件名的字符串(可以包含⽂件所在的具体路径)。
    mode:设置打开⽂件的模式(访问模式):只读、写⼊、追加等。(字符串)
模式 描述
r 以只读⽅式打开⽂件。⽂件的指针将会放在⽂件的开头。这是默认模式。
rb 以⼆进制格式打开⼀个⽂件⽤于只读。⽂件指针将会放在⽂件的开头。这是默认模式。
r+ 打开⼀个⽂件⽤于读写。⽂件指针将会放在⽂件的开头。
rb+ 以⼆进制格式打开⼀个⽂件⽤于读写。⽂件指针将会放在⽂件的开头。
w 打开⼀个⽂件只⽤于写⼊。如果该⽂件已存在则打开⽂件,并从开头开始编辑,即原有内容会被删除。如果该⽂件不存在,创建新⽂件。
wb 以⼆进制格式打开⼀个⽂件只⽤于写⼊。如果该⽂件已存在则打开⽂件,并从开头开始编辑,即原有内容会被删除。如果该⽂件不存在,创建新⽂件。
w+ 打开⼀个⽂件⽤于读写。如果该⽂件已存在则打开⽂件,并从开头开始编辑,即原有内容会被删除。如果该⽂件不存在,创建新⽂件。
wb+ 以⼆进制格式打开⼀个⽂件⽤于读写。如果该⽂件已存在则打开⽂件,并从开头开始编 辑,即原有内容会被删除。如果该⽂件不存在,创建新⽂件。
a 打开⼀个⽂件⽤于追加。如果该⽂件已存在,⽂件指针将会放在⽂件的结尾。也就是说, 新的内容将会被写⼊到已有内容之后。如果该⽂件不存在,创建新⽂件进⾏写⼊。
ab 以⼆进制格式打开⼀个⽂件⽤于追加。如果该⽂件已存在,⽂件指针将会放在⽂件的结 尾。也就是说,新的内容将会被写⼊到已有内容之后。如果该⽂件不存在,创建新⽂件进 ⾏写⼊。
a+ 打开⼀个⽂件⽤于读写。如果该⽂件已存在,⽂件指针将会放在⽂件的结尾。⽂件打开时会是追加模式。如果该⽂件不存在,创建新⽂件⽤于读写。
ab+ 以⼆进制格式打开⼀个⽂件⽤于追加。如果该⽂件已存在,⽂件指针将会放在⽂件的结尾。如果该⽂件不存在,创建新⽂件⽤于读写。

  • 文件对象方法
    • 写 ```python 对象.write(‘内容’)

1. 打开⽂件

f = open(‘test.txt’, ‘w’)

2.⽂件写⼊

f.write(‘hello world’)

3. 关闭⽂件

f.close()


   - 读
      - `⽂件对象.read(num)`
> num表示要从⽂件中读取的数据的⻓度(单位是字节),如果没有传⼊num,那么就表示读取⽂件中所有的数据。

      - `文件对象.readlines()`

readlines可以按照**⾏的⽅式**把整个⽂件中的内容进⾏**⼀次性读取**,并且**返回的是⼀个列表**,其中每⼀⾏ 的数据为⼀个元素。

      - `文件对象.readline()`

readline()⼀次读取⼀⾏内容。
<a name="UTu9D"></a>
## 7.2 文件备份
需求:⽤户输⼊当前⽬录下任意⽂件名,程序完成对该⽂件的备份功能(备份⽂件名为xx[备份]后缀,例如:test[备份].txt)。
```python
# 1. 接收⽤户输⼊⽬标⽂件名
old_name = input('请输⼊您要备份的⽂件名:')

# 2. 规划备份⽂件名
# 2.1 提取⽂件后缀点的位置下标
index = old_name.rfind('.')
if index > 0: # 如果index==0,则说明文件名有误
    postfix = old_name[index:]
# print(index) # 后缀中.的下标
# print(old_name[:index]) # 源⽂件名(⽆后缀)
# 2.2 组织新⽂件名 旧⽂件名 + [备份] + 后缀
new_name = old_name[:index] + '[备份]' + postfix
# 打印新⽂件名(带后缀)
# print(new_name)

# 3. 备份⽂件写⼊数据
# 3.1 打开⽂件
old_f = open(old_name, 'rb')
new_f = open(new_name, 'wb')
# 3.2 将源⽂件数据写⼊备份⽂件
while True:
    con = old_f.read(1024)
    if len(con) == 0:
        break
    new_f.write(con)
# 3.3 关闭⽂件
old_f.close()
new_f.close()

7.3 文件和文件夹的操作

在Python中⽂件和⽂件夹的操作要借助os模块⾥⾯的相关功能。

  • 文件重命名

os.remove(目标文件名,新文件名)

  • 删除文件

os.remove(目标文件名)

  • 创建文件夹

os.mkdir(文件夹名字)

  • 删除文件夹

os.rmdir(文件夹名字)

  • 获取当前目录

os.getcwd()

  • 改变默认目录

os.chdir(目录)

  • 获取目录列表

os.listdir(目录)


应用案例:批量修改文件名

import os
# 设置重命名标识:如果为1则添加指定字符,flag取值为2则删除指定字符
flag = 1
# 获取指定⽬录
dir_name = './'
# 获取指定⽬录的⽂件列表
file_list = os.listdir(dir_name)
# print(file_list)
# 遍历⽂件列表内的⽂件
for name in file_list:
    # 添加指定字符
    if flag == 1:
        new_name = 'Python-' + name
        # 删除指定字符
        elif flag == 2:
            num = len('Python-')
            new_name = name[num:]
            # 打印新⽂件名,测试程序正确性
            print(new_name)

 # 重命名
 os.rename(dir_name+name, dir_name+new_name)

7.4 编码解码的问题

在文件存储和读取文件时涉及到编码、解码的问题。对于GBK编码与utf-8编码的转换,需要先转换成Unicode编码再转换成其他编码,因为Unicode编码和其他编码都有映射关系。

  • 编码与解码
    • 任意编码转换成unicode的过程,都叫解码
    • 把unicode转换成任意编码的过程 ,都叫编码
  • 不同字符编码间的转换

    • 字符串编码成utf-8

      s='路飞'
      utf = s.encode("utf-8") # b'\xe8\xb7\xaf\xe9\xa3\x9e' # 会变成bytes字节格式,
      
    • 编码过后会变成二进制,如果要编码成其他格式需要将其解码,在编码

      # 解码
      s = utf.decode("utf-8") # 因为utf是以utf-8来编码的,所以解码也需要用utf-8
      #在编码成其他格式
      gbk = s.encode("gbk")
      

      bytes字节类型是⽤16进制表示的, 像这样 \xe8 这样2个16进制数是代表⼀个字节(因为⼀个16 进制是占4位,2个就是8位,共1个字节啦),其实就是二进制格式

8. 面向对象基础

8.1 self

self是指调用该函数的对象。

class Washer():
    def wash(self):
        print('我会洗⾐服')
        print(self)

 # 2. 创建对象
haier1 = Washer()
print(haier1) #<__main__.Washer object at 0x000001D0DA1C3F28>
# haier1对象调⽤实例⽅法
haier1.wash() #<__main__.Washer object at 0x000001D0DA1C3F28>
#-----------------------------------------------------------------
haier2 = Washer() 
# <__main__.Washer object at 0x0000022005857EF0>
print(haier2)

注意:打印对象和self得到的结果是⼀致的,都是当前对象的内存中存储地址。


8.2 添加和获取对象属性

属性即是特征,⽐如:洗⾐机的宽度、⾼度、重量…
对象属性既可以在类外⾯添加和获取,也能在类⾥⾯添加和获取

  • 类外面添加对象属性

对象名.属性名=值

  • 类外面获取对象属性

对象名.属性名

  • 类里面获取对象属性

self.属性名

注意:在类定义那里,用self.属性名定义的属性属于对象属性,每个不同对象的对象属性都是独立的,互不影响的,而直接用属性名定义的属于类属性,类属性是所有对象共有的,类属性只能通过类.属性修改。


8.3 魔法方法

在Python中, __xx__() 的函数叫做魔法⽅法,指的是具有特殊功能的函数。

  • __init__()初始化对象,在创建对象时自动被调用,并能够接收创建对象时传入的参数

    class Washer():
      def __init__(self, width, height):
          self.width = width
          self.height = height
          def print_info(self):
              print(f'洗⾐机的宽度是{self.width}')
              print(f'洗⾐机的⾼度是{self.height}')
    haier1 = Washer(10, 20)
    haier1.print_info()
    haier2 = Washer(30, 40)
    haier2.print_info()
    

    init(self) 中的self参数,不需要开发者传递,python解释器会⾃动把当前的对象引⽤(类似指针)传递过去。

  • __str__(),当使⽤print输出对象的时候,默认打印对象的内存地址。如果类定义了__str__ ⽅法,那么就会打印从在这个⽅法中return的数据。 ```python class Washer(): def init(self, width, height):

      self.width = width
      self.height = height
    

    def str(self):

      return '这是海尔洗⾐机的说明书'
    

haier1 = Washer(10, 20)

这是海尔洗⾐机的说明书

print(haier1)


- `__del__()`,当**删除对象**时,python解释器也会默认调用`__del__()`方法
```python
class Washer():
    def __init__(self, width, height):
        self.width = width
        self.height = height
    def __del__(self):
        print(f'{self}对象已经被删除')
haier1 = Washer(10, 20)
del haier1
# <__main__.Washer object at 0x0000026118223278>对象已经被删除
  • getitem(),该函数允许对象可以用对象[key]的方式来操作对象,此时会调用该魔法方法 ```python class Washer(): def init(self, width, height):
      self.width = width
      self.height = height
    
    def getitem(self, key):
      return key
    
    wash = Washer(10,20) print(wash[1]) # 1

- `__len__()`,对该对象用`len()`函数时,调用执行__len__()
```python
class Washer():
    def __init__(self, *args):
        self.name = args
    def __len__(self):
        return len(self.name)
wash = Washer(10,20,30,40)
print(len(wash)) # 4
  • __call__(),该方法的功能类似于在类中重载 () 运算符,使得类实例对象可以像调用普通函数那样,以“对象名()”的形式使用

    class CLanguage:
      # 定义__call__方法
      def __call__(self,name,add):
          print("调用__call__()方法",name,add)
    clangs = CLanguage()
    clangs("C语言中文网","http://c.biancheng.net")
    

    9. 面向对象-继承

    9.1 单继承与多继承

  • 单继承 ```python

    1. 师⽗类

    class Master(object): def init(self):

      self.kongfu = '[古法煎饼果⼦配⽅]'
    

    def make_cake(self):

      print(f'运⽤{self.kongfu}制作煎饼果⼦')
    

2. 徒弟类

class Prentice(Master): pass


- 多继承
```python
class Master(object):
    def __init__(self):
        self.kongfu = '[古法煎饼果⼦配⽅]'
     def make_cake(self):
        print(f'运⽤{self.kongfu}制作煎饼果⼦')
# 创建学校类
class School(object):
    def __init__(self):
        self.kongfu = '[⿊⻢煎饼果⼦配⽅]'
    def make_cake(self):
        print(f'运⽤{self.kongfu}制作煎饼果⼦')
class Prentice(School, Master):
    pass

注意:当⼀个类有多个⽗类的时候,默认使⽤第⼀个⽗类的同名属性和⽅法。也就是说这里虽然继承了四个方法,但实际上只能调用School类里的两个方法。


9.2 子类重写与调用父类同名方法和属性

class Master(object):
    def __init__(self):
        self.kongfu = '[古法煎饼果⼦配⽅]'
    def make_cake(self):
        print(f'运⽤{self.kongfu}制作煎饼果⼦')\
class School(object):
    def __init__(self):
        self.kongfu = '[⿊⻢煎饼果⼦配⽅]'
    def make_cake(self):
        print(f'运⽤{self.kongfu}制作煎饼果⼦')
# 独创配⽅
class Prentice(School, Master):
    def __init__(self):
        self.kongfu = '[独创煎饼果⼦配⽅]'
       def make_cake(self):
        # 如果是先调⽤了⽗类的属性和⽅法,⽗类属性会覆盖⼦类属性,故在调⽤属性前,先调⽤⾃⼰
        ⼦类的初始化
        self.__init__()
        print(f'运⽤{self.kongfu}制作煎饼果⼦')
    # 调⽤⽗类⽅法,但是为保证调⽤到的也是⽗类的属性,必须在调⽤⽅法前调⽤⽗类的初始化
    def make_master_cake(self):
        Master.__init__(self)
        Master.make_cake(self)
    def make_school_cake(self):
        School.__init__(self)
        School.make_cake(self)
  • 子类重写父类方法,只需要子类的方法名和参数一致即可。⼦类和⽗类具有同名属性和⽅法,默认使⽤⼦类的同名属性和⽅法。
  • 如果子类想要使用父类方法,则需要用类.方法名的形式,且需要把对象传进去。

9.3 super()调用父类方法

class Master(object):
    def __init__(self):
        self.kongfu = '[古法煎饼果⼦配⽅]'
    def make_cake(self):
        print(f'运⽤{self.kongfu}制作煎饼果⼦')
class School(Master):
    def __init__(self):
        self.kongfu = '[⿊⻢煎饼果⼦配⽅]'
    def make_cake(self):
        print(f'运⽤{self.kongfu}制作煎饼果⼦')
        # ⽅法2.1
        # super(School, self).__init__()
        # super(School, self).make_cake()
        # ⽅法2.2
        super().__init__()
        super().make_cake()

注意:使⽤super() 可以⾃动查找⽗类。调⽤顺序遵循 mro 类属性的顺序。⽐较适合单继承 使⽤。

9.4 私有属性和方法

在Python中,可以为实例属性和⽅法设置私有权限,即设置某个实例属性或实例⽅法不继承给⼦类,或是不能直接通过对象调用。
设置私有权限的⽅法:在属性名和⽅法名前⾯加上两个下划线 __。

注意:私有属性和私有⽅法只能在类⾥⾯访问和修改。 这里的私有属性和私有方法不是真的强制给设置私有权限,而是python解释器自动给私有属性和方法换了一个名字,你不能通过原有的属性名或方法名直接调用。


10. 面向对象其他特性

10.1 多态

多态指的是⼀类事物有多种形态,(⼀个抽象类有多个⼦类,因⽽多态的概念依赖于继承)。

  • 定义:多态是⼀种使⽤对象的⽅式,⼦类重写⽗类⽅法,调⽤不同⼦类对象的相同⽗类⽅法,可以 产⽣不同的执⾏结果
  • 好处:调⽤灵活,有了多态,更容易编写出通⽤的代码,做出通⽤的编程,以适应需求的不断变化!
  • 实现步骤:
    • 定义⽗类,并提供公共⽅法
    • 定义⼦类,并重写⽗类⽅法
    • 传递⼦类对象给调⽤者,可以看到不同⼦类执⾏效果不同
      class Dog(object):
      def work(self): # ⽗类提供统⼀的⽅法,哪怕是空⽅法
         print('指哪打哪...')
      class ArmyDog(Dog): # 继承Dog类
      def work(self): # ⼦类重写⽗类同名⽅法
         print('追击敌⼈...')
      class DrugDog(Dog):
      def work(self):
         print('追查毒品...')
      class Person(object):
      def work_with_dog(self, dog): # 传⼊不同的对象,执⾏不同的代码,即不同的work函数
         dog.work()
      ad = ArmyDog()
      dd = DrugDog()
      daqiu = Person()
      daqiu.work_with_dog(ad)
      daqiu.work_with_dog(dd)
      

      注意:与JAVA不同的是,由于python定义变量时不需要声明数据类型,所以对于JAVA来说需要一个父类来作为声明变量时使用,而对于python来说,即使不需要父类,在def work_with_dog(self, dog)这里,也能传入不同的对象,只要对象都有work方法。

10.2 类属性和实例属性

  • 类属性就是 类对象 所拥有的属性,它被该类的所有实例对象所共有。
  • 类属性可以使⽤ 类对象实例对象 访问。

    注意:虽然能使用实例对象访问(不能修改)类属性,但如果有同名的对象属性,则访问的是对象属性 因此:类属性只能通过类对象修改,不能通过实例对象修改,如果通过实例对象修改类属性,表示的是创建了⼀个实例属性

class Dog(object):
    tooth = 10
wangcai = Dog()
xiaohei = Dog()
print(Dog.tooth) # 10
print(wangcai.tooth) # 10
print(xiaohei.tooth) # 10

类属性的优点

  • 类的实例: 记录的某项数据 始终保持⼀致时,则定义类属性。
  • 实例属性: 要求 每个对象 为其 单独开辟⼀份内存空间 来记录数据,⽽ 类属性 为全类所共有 ,仅占⽤⼀份内存,更加节省内存空间。

10.3 类方法和静态方法

类方法特点:

  • 第⼀个形参是类对象的⽅法
  • 需要⽤装饰器 @classmethod 来标识其为类⽅法,对于类⽅法,第⼀个参数必须是类对象,⼀般以 cls 作为第⼀个参数。类和实例对象都可以调用。

使用场景:

  • 当⽅法中 需要使⽤类对象 (如访问私有类属性等)时,定义类⽅法
  • 类⽅法⼀般和类属性配合使⽤(即对于一些公共属性的访问,只访问,不能修改,所以设成私有属性)
    class Dog(object):
      __tooth = 10
      @classmethod
      def get_tooth(cls):
          return cls.__tooth
    wangcai = Dog()
    result = wangcai.get_tooth()
    print(result) # 10
    

静态方法特点:

  • 需要通过装饰器 @staticmethod 来进⾏修饰,静态⽅法既不需要传递类对象也不需要传递实例对象(形参没有self/cls)。
  • 静态⽅法 也能够通过 实例对象 和 类对象 去访问

使用场景:

  • 当⽅法中 既不需要使⽤实例对象(如实例对象,实例属性),也不需要使⽤类对象 (如类属性、类⽅法、创建实例等)时,定义静态⽅法
  • 取消不需要的参数传递,有利于减少不必要的内存占⽤和性能消耗
  • 静态方法是类中的函数,不需要实例。静态方法主要是用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及到类中的属性和方法的操作。可以理解为,静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护。

11. 异常

  • 捕获指定异常 ```python try: 可能发⽣错误的代码 except 异常类型: 如果捕获到该异常类型执⾏的代码

    —————————————————————

    try: print(num) except NameError: print(‘有错误’)

捕获多个指定异常

try: print(1/0) except (NameError, ZeroDivisionError): print(‘有错误’)

捕获异常描述信息

try: print(num) except (NameError, ZeroDivisionError) as result: print(result)


- 捕获所有异常

**Exception是所有程序异常类的父类**
```python
try:
    print(num)
except Exception as result:
    print(result)
  • 异常的else与finally

    try:
      f = open('test.txt', 'r')
    except Exception as result:
      f = open('test.txt', 'w')
    else:
      print('没有异常,真开⼼')
    finally:
      f.close()
    
    • else表示的是如果没有异常要执⾏的代码。
    • finally表示的是⽆论是否异常都要执⾏的代码,例如关闭⽂件。

  • 异常的传递
    • 异常的传递 —— 当 函数/方法 执行 出现异常,会 将异常传递 给 函数/方法 的 调用一方
    • 如果 传递到主程序,仍然 没有异常处理,程序才会被终止

提示:

  • 在开发中,可以在主函数中增加 异常捕获
  • 而在主函数中调用的其他函数,只要出现异常,都会传递到主函数的 异常捕获 中
  • 这样就不需要在代码中,增加大量的 异常捕获,能够保证代码的整洁

  • 自定义异常

在Python中,抛出⾃定义异常的语法为 raise 异常类对象 。

# ⾃定义异常类,继承Exception
class ShortInputError(Exception):
    def __init__(self, length, min_len):
        self.length = length
        self.min_len = min_len
 # 设置抛出异常的描述信息
    def __str__(self):
        return f'你输⼊的⻓度是{self.length}, 不能少于{self.min_len}个字符'
def main():
    try:
        con = input('请输⼊密码:')
        if len(con) < 3:
            raise ShortInputError(len(con), 3)
    except Exception as result:
        print(result)
    else:
        print('密码已经输⼊完成')

可以将自定义异常看做是对一些逻辑错误统一写成的一个类。即也许多个地方都会出现同一种逻辑错误,为了避免每个地方都去用一个if语句else语句来判断,不如把它抽象成一个类,即异常类

12. 模块与包

Python 模块(Module),是⼀个 Python ⽂件,以 .py 结尾,包含了 Python 对象定义和Python语句。 模块能定义函数,类和变量,模块⾥也能包含可执⾏的代码。

12.1 导入模块

导入模块的方式

  • import 模块名1,模块名 2。。。
    • 该方法导入模块时,调用功能需要用模块名.功能名()
  • from 模块名 import 功能名 1,功能名2。。。
    • 可直接用功能名()调用
  • from 模块名 import *
    • 可直接用功能名()调用
  • import 模块名 as 别名
  • from 模块名 import 功能名 as 别名

    注意:如果使⽤ from .. import .. 或 from .. import * 导⼊多个模块的时候,且模块内有同名功能。当调用这个同名功能的时候,调用到的是后面导入的模块的功能。

12.2 制作模块

在Python中,每个Python⽂件都可以作为⼀个模块,模块的名字就是⽂件的名字。也就是说⾃定义模块名必须要符合标识符命名规则。

  • 定义模块

新建⼀个Python⽂件,命名为 my_module1.py ,并定义 testA 函数。

def testA(a, b):
    print(a + b)
  • 测试模块

在实际开中,当⼀个开发⼈员编写完⼀个模块后,为了让模块能够在项⽬中达到想要的效果,这个开发 ⼈员会⾃⾏在py⽂件中添加⼀些测试信息.,例如,在my_module1.py⽂件中添加测试代码。

def testA(a, b):
    print(a + b)
# 只在当前⽂件中调⽤该函数,其他导⼊的⽂件内不符合该条件,则不执⾏testA函数调⽤
if __name__ == '__main__':
    testA(1, 1)

注意:⽆论是当前⽂件,还是其他已经导⼊了该模块的⽂件,如果不加‘main’的限制代码,在运⾏的时候都会⾃动执⾏ testA 函数的调⽤。因此加一个限制,只有当该文件为主函数时才能运行。


12.3 模块定位顺序

当导⼊⼀个模块,Python解析器对模块位置的搜索顺序是:
1. 当前⽬录
2. 如果不在当前⽬录,Python则搜索在shell变量PYTHONPATH下的每个⽬录。
3. 如果都找不到,Python会察看默认路径。UNIX下,默认路径⼀般为/usr/local/lib/python/

模块搜索路径存储在system模块的sys.path变量中。变量⾥包含当前⽬录,PYTHONPATH和由安装过程决定的默认⽬录。 注意:

  • ⾃⼰的⽂件名不要和已有模块名重复,否则导致模块功能⽆法使⽤
  • 使⽤from 模块名 import 功能 的时候,如果功能名字重复,调⽤到的是最后定义或导⼊的功能。

12.4 all

如果⼀个模块⽂件中有__all__变量,当使⽤from xxx import *导⼊时,只能导入这个列表中的元素

__all__ = ['testA']
def testA():
    print('testA')
def testB():
    print('testB')

使用from module1 import *时,只有testA会被导入。

12.5 包

包将有联系的模块组织在⼀起,即放到同⼀个⽂件夹下,并且在这个⽂件夹创建⼀个名字为__init__.py⽂件,那么这个⽂件夹就称之为包。

  • 制作包

[New] — [Python Package] — 输⼊包名 — [OK] — 新建功能模块(有联系的模块)。
注意:新建包后,包内部会⾃动创建 init.py ⽂件,这个⽂件控制着包的导⼊⾏为。

  • 导入包

    • 方法1

      import 包名.模块名
      包名.模块名.⽬标
      
    • 方法2

      from 包名 import *
      模块名.⽬标
      

      注意添加all变量。