目录

1. 缩进 和 注释
2. 常用运算符
3. 条件语句、三元表达式、空语句
4. for、while、break、continue、else、死循环
5. 函数
6. 类
7. 异常
8. 导入

一、缩进 和 注释

1.1 缩进

Python 采用代码缩进和冒号(:)来区分代码块之间的层级。 Python 对代码的缩进要求非常严格,同一个级别代码块的缩进量必须一样,否则解释器会报 SyntaxError 异常错误。

1.2 注释

单行注释,如: # 这是单行注释

多行注释,如: ''' 这是多行注释 '''

""" 这也是多行注释 """

二、常用运算符

2.1 算术运算符

+:

1. 数值之间的 + 等同于数学意义上的 加法运算。
2. 字符串之间的 + 相当于拼接字符串。
3. 列表之间的 + 相当于 拼接两个列表的内容
4. 元组之间的 + 相当于 拼接两个元组的内容

-: 数值之间的 - 等同于数学意义上的 减法运算

*:

1. 数值之间的 * 等同于数学意义上的 乘法运算
2. (字符串 | 列表 | 元组) 与整形之间的 * 相当于重复拼接自身元素连接成新的(字符串 | 列表 | 元组)

/: 数值之间的 / 等同于数学意义上的 除法运算 提示:整数之间做除法运算,结果都是 浮点数

//: 数值之间的 // 相当于数学意义上的 整除运算 提示:整数之间做整除运算,结果都是 整数

%: 数值之间的 % 相当于数学意义上的 取模运算(也叫求余)

2.2 比较运算符

比较运算符的结果都会返回一个布尔值

>: 大于,用于比较两个对象的大小。>>> 1 > 2 # False

<: 小于,同样用于比较两个对象的大小。>>> 1 < 2 # True

>=: 大于等于。>>> 1 >= 2 # False

<=: 小于等于。>>> 1 <= 2 # True

==: 等于,比较两个对象是否相等。>>> 1 == 2 # False

!=: 不等于,比较两个对象是否不相等。>>> 1 != 2 # True

2.3 逻辑运算符

and: 两者都为真,才为真。>>> False and True # False or: 两者有一个为真,就为真。>>> False or True # True not: 取反。>>> not False # True

提示: 逻辑运算符,是惰性求值(短路特性),由于惰性,只要确定了值就不往后解析代码了。

三、条件语句、三元表达式、空语句

3.1 条件语句

计算机之所以能做很多自动化的任务,是因为它可以自己做条件判断。 比如:根据年龄打印不同的内容,在Python程序中,用 if 实现

age = 20 if age >= 18: print('成年人,可以去网吧上网了!')

根据 Python 的缩进规则, 如果 if 语句判断是True, 就把缩进的 print 语句执行了,否则什么也不做。 也可以给 if 添加 else 语句,意思是,如果 if 判断是 False, 就不去执行 if 的内容了,而是执行 else 的内容。

age = 3 if age >= 18: print('成年人,可以去网吧上网了!') else: print('未成年人,回家好好学习吧!')

注意注意注意!!! 不要少写了冒号:

上面的判断比较粗略,我们可以用 elif 做更细致的判断

age = 3 if age >= 18: print('成年人!') elif age >= 6: print('青少年!') else: print('儿童')

elif 是 else if的缩写,可以有多个elif,所以 if 语句的完整形式就是

if <条件判断1>:       <执行1>   elif <条件判断2>:       <执行2>   elif <条件判断3>:       <执行3>   else:   <执行4> 

if 语句执行有个特点,它是从上往下判断,如果在某个判断上是True,把该判断对应的语句执行后,就忽略掉剩下的 elif 和 else。

age = 20 if age >= 6: print('青少年!') elif age >= 18: print('成年人!') else: print('孩子')

if 判断条件的对象,可以不是一个布尔值

x = 100 if x: print('True')

只要 x 是非零数值、非空字符串、非空列表等,就判断为True,否则为False。

条件语句的四种情况

if if elif if else if elif else ``

3.2 条件表达式

格式为:条件成立时的返回值 if 条件 else 条件不成立时的返回值 a = 1 if False else 2  # a = 2

3.3 空语句

Python 中的 pass 是空语句,是为了保持程序结构的完整性。 pass 不做任何事情,一般用做占位语句。

if True: pass

四、for、while、break、continue、else

4.1 for 循环

for 循环可以遍历任何容器类型,如一个列表或者一个字符串 回忆一下目前学过的容器数据类型: str、list、tuple、dict、set

students = ['Qiu', 'Hugh', 'Michael'] for student in students: print(student)

执行这段代码,会依次打印 students 的每一个元素 所以 for x in ... 循环就是把每个元素代入变量 x,然后执行缩进块的语句

练习:用 for 循环求和 1 - 10 的和

4.2 while 循环

只要条件满足,就不断循环,当条件不满足时退出循环。

例子: 100 + 99 + 98 + ... + 1

result = 0 n = 100 while n > 0: result += 1 n -= 1 print(result)

练习:计算 100 以内所有偶数之和

result = 0 n = 98 while n > 0: result += n n -= 2 print(result)

4.3 break

在循环中,break 语句可以提前退出整个循环 例如,本来要循环打印 1 ~ 100的数字

n = 1 while n <= 100: print(n) n += 1 print('end')

上面的代码可以打印出 1 ~ 100。 如果要提前结束循环,可以用 break 语句。

n = 1 while n <= 100: if n == 10: break print(n) n += 1 print('end')

执行上面的代码可以看到,打印出 1 ~ 9 后,紧接着打印 end,程序结束。 可见 break 的作用是提前结束循环。

4.4 continue

在循环过程中,可以通过 continue 语句,跳过当前这次循环,直接开始下一次循环。

n = 1 while n <= 10: print(n) n += 1

上面的程序可以打印出1 ~ 10。 但是,如果我们想只打印奇数,可以用 continue 语句跳过某些循环

n = 1 while n <= 10: if n % 2 == 0: n += 1 continue print(n) n += 1
n = 0 while n < 10: n += 1 if n % 2 == 0: continue print(n)

执行上面的代码可以看到,打印的不再是1 ~ 10,而是 1,3,5,7,9。 可见 continue 的作用是提前结束本轮循环,并直接开始下一轮循环。

4.5 死循环

很多时候,你写代码稍不注意,会出现死循环的情况,导致程序一直进行中。

死循环:表达式永远为真的循环。

有些时候,如果代码写得有问题,会让程序陷入“死循环”,也就是永远循环下去。这时可以用 Ctrl + C 退出程序,或者强制结束 Python进程。

五、函数

函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。

5.1 定义函数

在 Python 中,定义一个函数要使用 def 语句,依次写出函数名、括号、括号中的参数和冒号: 然后,在缩进块中编写函数体,函数的返回值用 return 语句返回。

以自定义一个求绝对值的 my_abs 函数为例

def my_abs(value): return value if value >= 0 else -value

注意,函数体内部的语句在执行时,一旦执行到 return 时,函数就执行完毕,并将结果返回。因此,函数内部通过条件判断和循环可以实现非常复杂的 逻辑;如果没有 return 语句,函数执行完毕后也会返回结果,只是结果为 None。return None可以简写为 return

5.2 调用函数

格式:函数名(参数)

my_abs(-100)

5.3 函数参数

实参(argument): 就是在调用函数的时候传递进去的参数。 形参(parameter): 就是在定义函数的时候指定的参数。

实参(argument)

在调用函数时传给 函数(或 方法)的值。参数分为两种:

    1. 关键字参数: 在函数调用中前面带有标识符(例如 name=)或者作为包含在前面带有 ** 的字典里的值传入。
        举例来说,3 和 5 在以下对 complex() 的调用中均属于关键字参数:
            ```
            complex(real=3, imag=5)
            complex(**{'real': 3, 'imag': 5})
            ```
 
    2. 位置参数: 不属于关键字参数的参数。位置参数可出现于参数列表的开头以及或者作为前面带有 * 的可迭代对象里的元素被传入。
        举例来说,3 和 5 在以下调用中均属于位置参数:
            ```
            complex(3, 5)
            complex(*(3, 5))
            ```

参数会被赋值给函数体中对应的局部变量。根据语法,任何表达式都可用来表示一个参数;最终算出的值会被赋给对应的局部变量。

形参(parameter)

函数(或方法)定义中的命名实体,它指定函数可以接受的一个或者多个实参(argument)。有五种形参:

    1. positional-or-keyword:位置或关键字,指定一个可以作为 位置参数 传入也可以作为 关键字参数 传入的实参。这是默认的形参类型。
        例如这里的 foo 和 bar: `def func(foo, bar=None): ...`

    2. positional-only(Python3.8+):仅限位置,指定一个只能通过位置传入的参数。仅限位置形参可通过在函数定义的形参列表中它们之后
        包含一个 / 字符来定义,例如下面的 posonly1 和 posonly2: 
            `def func(posonly1, posonly2, /, positional_or_keyword): ...`

    3. keyword-only:仅限关键字,指定一个只能通过关键字传入的参数。仅限关键字形参可通过在函数定义的形参列表中包含单个可变位置形
        参或者在多个可变位置形参之前放一个 * 来定义,例如下面的 kw_only1 和 kw_only2:
            `def func(arg, *, kw_only1, kw_only2): ...`

    4. var-positional:可变位置,指定可以提供由一个任意数量的位置参数构成的序列(附加在其它形参已接受的位置参数之后)。这种形参
        可通过在形参名称前加缀 * 来定义,例如下面的 args:
            `def func(*args, **kwargs): ...`

    5. var-keyword:可变关键字,指定可以提供任意数量的关键字参数(附加在其它形参已接受的关键字参数之后)。这种形参可通过在形参
        名称前加缀 ** 来定义,例如上面的 kwargs。

形参可以同时指定可选和必选参数,也可以为某些可选参数指定默认值。

示例:

def sub(a, b): print(a - b) sub(1, 2) # 调用 sub 函数,给的值 a 为 1,b 为 2,这种位置很重要的参数叫做位置参数。 sub(b=2, a=1) # 关键字参数:在传递参数的时候,按照形参的名字给定参数

关于 *args 和 **kwargs

1. *args: 将位置参数打包成 tuple 给函数
2. **kwargs: 将关键字参数打包成 dict 给函数
def func(*args, **kwargs): print(args) print(kwargs) func(1, 2, a=3, b=4)

其中 1 和 2 是位置参数,会保存在 args 中,a=3 和 b=4 关键字参数,保存在 kwargs 中。 传递到函数中的 args 是一个 tuple,kwargs 是一个字典。

元组/列表/可迭代对象 和 字典拆分出来成位置参数 和关 键字参数

t = ('a', 'b', 'c') info = {'username': 'Qiu', 'city': 'Gz'} def greet(*args, **kwargs): print(args) print(kwargs) greet(*t, **info)

默认参数 有时候一些参数,可能需要给函数调用者,提供一个默认的参数,那么可以通过以下方式实现

def greet(name, city='Gz'): print(name, city) greet('Qiu') # 使用 city 默认值参数 Gz greet('Qiu', city='Hz') # 使用调用方提供的 city 参数

5.4 函数返回值

1. 若函数体内没有 return,默认 return None
2. 函数返回多个值的本质是 返回一个元组
3. return None 可以简写成 return

5.5 变量的作用域 及 执行顺序

1. 局部变量: 函数中的变量,不能在函数外部使用。
2. 全局变量:在函数或者某个代码块外面定义的变量,可以在函数中使用。
def greet(): name = 'Qiu' print('my name is %s' % name) greet() print(name) # raise NameError,说明函数内部的变量,在函数体外无法访问。
name = 'Qiu' def greet(): print(f'My name is {name}') greet()

如果想在函数内部修改全局变量,应该使用 global 关键字。

1. 在函数体内部定义变量(未使用 global 声明),不会影响到全局变量。
    ```python
    name = 'Qiu'
    
    def greet():
        name = 'Hugh'  # 此时不是修改全局变量 user,而是定义一个与全局变量相同名字的局部变量。
        print(f'My name is {name}')
    
    greet()
    print(name)  # name 仍然是 Qiu
```


2. 在函数体内部定义变量时使用 global 声明,会改变全局变量的值。
```python
name = 'Qiu'

def greet():
    global name
    name = 'Hugh'

greet()
print(name)  # 此时的 name 已经被改成了 Hugh
```


3) 关于 Python 代码的执行顺序
```python

def greet():
    global user
    user = 'Qiu'


greet()
print(user)  # 可以输出
```

```python
def greet():
    global user
    user = 'Qiu'
    
print(user)  # raise NameError
```


4. 列表和字典当作全局变量
当全局变量指向的是列表或者字典,在函数中使用它时,可以任意的增删改查 列表/字典 的值;但是,如果要修改全局变量指向的值,则需要加 
global 关键字。

```python
students = ['Liao', 'Ke', 'Chen']

def add_student(name):
    students.append(name)

add_student('Qiu')
print(students)  # 这时 students 已经变成了 ['Liao', 'Ke', 'Chen', 'Qiu']
```

而如果你直接修改 students 的指向,但没有使用 global 关键字,那么做的操作,是在定义一个局部变量。
```python
students = ['Liao', 'Ke', 'Chen']

def add_student(name):
    students = ['Qiu']

add_student()
print(students)  # students 仍然是 ['Liao', 'Ke', 'Chen']
```

5.6 匿名函数

匿名函数,也叫 lambda 表达式,它简化了函数的定义,使代码更为简洁。

my_abs = lambda value: value if value >=0 else -value

六、类

6.1 面向对象的概念和特点

1. 概念
    面向对象的设计思想是从自然界中来的,因为在自然界中,类(Class)和实例(Instance)的概念是很自然的。Class 是一种抽象概念,比
    如我们定义的 Class——Student,是指学生这个概念,而实例(Instance)则是一个个具体的 Student。
    
    面向对象的设计思想是抽象出 Class,根据 Class 创建 Instance。
    
    面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如 Student 类,而实例是根据类创建出来的一
    个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。

2. 特点
    数据封装、继承和多态

6.2 类的定义

在 Python 中,定义类是通过 class 关键字。

class Student: pass

class 后面紧接着是类名,类名通常是 大写开头的单词。

当我们定义一个类的时候,实际上就是定义了一种数据类型,我们自己定义的数据类型 和 Python 自带的数据类型并没有什么区别。

class Student: pass a = list() # a 是 list 类型 student = Student() # student 是 Student 类型

6.3 实例化对象

类的实例化,也叫做创建对象。

语法格式:类名()

例如,定义好了 Student类 以后,可以根据 Student类 创建出 Student的实例。

class Student: pass hugh = Student()

变量 hugh 指向的就是一个 Student类 的实例。 可以自由地给一个实例绑定属性,比如,给实例 hugh 绑定 一个 name 属性。hugh.name = 'Hugh'


6.4 构造方法(构造函数)

由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写上去。 例子:通过定义 __init__ 方法,在创建实例的时候,就把 name, score 等属性绑定上去。

class Student: def __init__(self, name, score): self.name = name self.score = score hugh = Student('Hugh', 90) print(hugh.name)

注意:__init__方法 前后分别有两个下划线!

__init__方法的第一个参数永远是 self,表示创建的实例本身,因此,在__init__方法内部,可以把各个属性绑定到 self。


6.5 访问限制

在 class 内部,可以有属性和方法,而外部代码可以通过直接调用实例对象的方法来操作数据,这样,就隐藏了内部的复杂逻辑。但是,从前面 Student 类的定义来看,外部代码还是可以自由地修改一个实例的属性。

class Student: def __init__(self, name, score): self.name = name self.score = score hugh = Student('Hugh', 90) hugh.score = 100 print(hugh.score) # 100

如果要让内部属性不被外部访问,可以在属性的名称前加上两个下划线。 在 Python 中,实例/类的属性名如果以双下划线开头,并且不以双下划线结尾,就变成了一个私有(private)属性,只有内部可以访问,外部不能访问。

class Student: def __init__(self, name, score): self.__name = name self.__score = score def print_score(self): print(f'P{self.__name}: {self.__score}') hugh = Student('Hugh', 90) print(hugh.__name) # raise AttributeError,外部无法访问私有属性。

这样就确保了外部代码不能随意地修改对象内部的属性,这样通过 访问限制 的保护,代码更加健壮。 问题来了,如果外部代码要获取 name 和 score 怎么办?我们可以给 Student类 增加 get_name 和 get_score 这样的方法。

class Student: def __init__(self, name, score): self.__name = name self.__score = score def get_name(self): return self.__name def get_score(self): return self.__score

如果又要允许外部代码修改 score 怎么办呢?可以再给 Student类 增加 set_score 方法。

class Student: def __init__(self, name, score): self.__name = name self.__score = score def get_name(self): return self.__name def get_score(self): return self.__score def set_score(self, score): self.__score = score

你可能会疑惑,原本直接通过 hugh.score = 99 的方式也可以修改,而且更简单,为什么要大费周折? 两个原因,一是通过定义为私有属性后,外部无法直接访问,需要提供个修改的入口;二是可以在方法内部对传入的参数进行检查,避免传入无效的参数。

class Student: def __init__(self, name, score): self.__name = name self.__score = score def get_name(self): return self.__name def get_score(self): return self.__score def set_score(self, score): if 0 <= score <= 100: self.__score = score else: raise ValueError('bad score')

注意,在 Python 中,变量名类似 __xx__ 的,也就是以 双下划线开头,并且以 双下划线结尾的,是特殊变量,可以直接访问。

有些时候,你还会看到以 一个下划线 开头的实例属性名,比如 _name,这样的实例属性外部是可以访问的,但是,按照约定俗称的规定,当你看到这样 的属性名时,意思是 “虽然我可以被访问,但是请把我视为私有属性,不要随意访问”。

双下划线开头的类/实例属性就一定不能从外部访问吗?其实并不是。 以 __name 为例,在外部不能直接访问 __name 的原因是 Python解释器 对外把 __name 改成了 _Student_name,所以,我们仍然可以通过 _Student__name 来访问 __name 的值。 但是,强烈建议你不要这么做,因为不同版本的 Python解释器 可能会把 __name 改成不同的名字。 总的来说就是,Python 本身没有任何机制阻止你干坏事,一切全靠自觉。

注意下面的这种错误写法

class Student: def __init__(self, name, score): self.__name = name self.__score = score def get_name(self): return self.__name hugh = Student('Hugh', 90) print(hugh.get_name()) # 'Hugh' hugh.__name = 'New Name' print(hugh.__name) # 'New Name'

表面上看,外部代码成功地设置了 __name 属性,但实际上这个 __name 属性 和 class 内部的 __name 属性 不是同一个!内部的 __name 属性已经 被 Python解释器 自动改成了 _Student__name,而外部代码相当于给 hugh 新增了一个 __name 属性。 hugh.get_name() # 会返回 self.__name,'Hugh' 可以看出,self.__name 会自动地寻找 实例的 _Student__name 属性。


6.6 数据封装

数据封装,是使用 类 带来的好处。

例如,在下面的 Student 类中,每个实例就拥有各自的 name 和 score 这些数据。我们可以通过函数来访问这些数据,比如打印一个学生的成绩。

class Student: def __init__(self, name, score): self.name = name self.score = score def print_score(student): print(f'{student.name}: {student.score}') hugh = Student('Hugh', 90) print_score(hugh)

思考一下,既然 Student类 实例本身就拥有这些数据,就没有必要从外面的函数去访问。可以直接在 Student 类的内部定义访问数据的函数,这样就 把 数据 给封装起来了。

class Student: def __init__(self, name, score): self.name = name self.score = score def print_score(self): print(f'{self.name}: {self.score}')

要定义一个方法,除了第一个参数是 self 以外,其它和普通函数一样。要调用一个方法,只需要在实例对象上直接调用,除了 self 不用传递,其 它参数正常传入。hugh.print_score()

这样一来,我们从外部看 Student类,就只需要知道,创建实例需要给出 name 和 score,而如何打印,是在 Student类 的内部定义的,这些数据和 逻辑被 封装 起来了,调用很容易,但却不用知道内部实现的细节。 封装的另一个好处是可以给 Student类 增加新的方法,比如 get_grade

class Student: def __init__(self, name, score): self.name = name self.score = score def get_grade(self): if self.score >= 90: return 'A' elif self.score >= 60: return 'B' else: return 'C' def print_score(self): print(f'{self.name}: {self.score}') lisa = Student('Lisa', 99) print(lisa.name, lisa.get_grade()) # get_grade 方法可以直接在实例对象上调用,不需要知道内部实现细节。

总结

1. 类是创建实例的模板,而实例则是一个个具体的对象,各个实例拥有的数据都互相独立,互不影响。
2. 方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据。
3. 通过在实例上调用方法,我们就直接操作了 对象内部的数据,但无需知道方法内部的实现细节。

6.7 继承和多态

当我们定义一个类的时候,可以从现有的类继承,新的类称为子类(sub class),而被继承的类称为基类/父类/超类 (base class/super class)。 前面写过的类,都没有显式的继承自某个类,它们最终都会继承自 object类。object类 是 Python 中所有类的基类,如果定义一个类时没有指定继承自哪个类, 则默认继承自 object类。

例子:

class Animal: def run(self): print('Animal is running...') class Dog(Animal): pass # Dog类 继承自 Animal 类 class Cat(Animal): pass # Cat类 继承自 Animal 类

对于 Dog类 来说,Animal类是它的父类;对于 Animal类 来说,Dog类 就是它的子类。Cat 和 Dog 类似。

继承有什么好处呢?

最大的好处就是,子类获得了父类的全部功能。
由于 Animal 实现了 run()方法,因此,Dog 和 Cat 作为 Animal 的子类,什么也不用做,就自动拥有了 run()方法。
class Animal: def run(self): print('Animal is running...') class Dog(Animal): pass # Dog类 继承自 Animal 类 class Cat(Animal): pass # Cat类 继承自 Animal 类 dog = Dog() dog.run() # Animal is running... cat = Cat() cat.run() # Animal is running...

继承的另外一个好处:多态。

在前面的例子里,无论是 Cat 还是 Dog,它们 run() 的时候,显示的都是 Animal is running...。正常来说,符合逻辑的做法应该是分别显示 Dog is running... 和 Cat is running.. 。因此,我们对 Dog 和 Cat 类的改进如下。

class Animal: def run(self): print('Animal is running...') class Dog(Animal): def run(self): print('Dog is running...') class Cat(Animal): def run(self): print('Cat is running...') dog = Dog() dog.run() # Dog is running... cat = Cat() cat.run() # Cat is running... def go(animal): animal.run() go(dog) # Dog is running... go(cat) # Cat is running...

当子类和父类中存在相同的 run()方法 时,我们说,子类的 run()方法 覆盖了 父类的 run()方法。也叫 子类重写了父类的 run()方法。 当一个子类对象,在调用 run() 方法时,总是会调用 子类的 run()方法。

go 函数接收一个 Animal 类型的对象,当传入 Animal类的不同子类时,表现的形式 就不同,称为多态。 多态的好处在于,当我们需要传入 Dog, Cat 实例时,我们只需要正常接收 Animal类型 就可以了,因为 Dog 和 Cat 都是 Animal类型,然后按照 Animal 类型进行操作就可以了。由于 Animal类型 有 run()方法,因此传入的任意类型,只要是 Animal类或者是它的子类,就会自动调用实际类型的 run()方法,这 就是多态的意思。

对于一个对象,我们只需要知道它是 Animal类型,无需确切的知道它的子类型,就可以放心地调用 run()方法,而具体调用的 run()方法 是作用在 Animal、 Dog、Cat 还是其它 Animal类的子类上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种 Animal的 子类时,只要确保 run()方法 编写正确,不用管原来的代码是如何调用的。

这就是著名的开闭原则:

1. 对扩展开放:允许新增 Animal 的子类。
2. 不需要修改依赖 Animal类型的 go() 等函数。

类还可以一级一级地继承下去,就好比从 爷爷 到 爸爸,再到 儿子 这样的关系。而任何类,最终都可以追溯到 根类root,这些继承关系看上去就像一颗倒着的树。

tree

鸭子类型 对于 Python 这样的动态类型语言来说,不一定需要传入 Animal类型。我们只需要确保传入的对象有一个 run() 方法就可以了。

class Timer: def run(self): print('时间滴答滴答的走~') def go(animal): animal.run() go(Timer()) # 时间滴答滴答的走~

这就是鸭子类型,它并不要求严格的继承体系,一个对象只要看起来像鸭子,走起路来像鸭子,那它就可以被看作是鸭子。


复习一下,isinstance, issubclass 的用法

class Animal: pass class Dog(Animal): pass class Cat(Animal): pass print(isinstance(Dog(), Dog)) # True print(isinstance(Dog(), Animal)) # True print(isinstance(Dog(), Cat)) # False print(issubclass(Dog, Animal)) # True print(issubclass(Cat, Animal)) # True

可以看出,dog 既属于 Dog类,也属于 Animal类。 所以,在继承关系中,如果一个实例的类型是某个子类,那它的类型也可以被看作是父类。

还可以对子类增加一其它些方法,以 Dog类 为例。

class Animal: def run(self): print('Animal is running...') class Dog(Animal): def eat(self): print('Eating meat...') dog = Dog() dog.eat() # Eating meat...

6.8 类属性和实例属性

在 Python 中,根据类创建的实例可以任意绑定属性。给实例绑定属性的方式有

1. 在类外部,通过 `实例.属性名 = 值` 
2. 在实例方法里,通过 `self.属性名 = 值`
class Student: def __init__(self, name): self.name = name # 在实例方法里,绑定实例属性 hugh = Student('Hugh') hugh.score = 90 # 在类外部,绑定实例属性

但是,如果 Student类 本身需要绑定一个属性呢?可以直接在 定义类时定义属性,这种属性称为 类属性,归 类 所有。

class Student: name = 'SSStudent' hugh = Student() print(hugh.name) # SSStudent print(Student.name) # SSStudent

可以发现,定义了类属性后,这个属性虽然归类所有,但类的所有实例都可以访问到。 关于实例属性的查找过程,在上面的例子中,因为 hugh 实例本身并没有 name 属性,所以会继续查找 类(Student) 的 name 属性;如果实例本身有 name 属性,那么会返回实例自身的属性。

class Student: name = 'SSStudent' hugh = Student() print(hugh.name) # 因为实例自身并没有 name 属性,所以会继续查找 class(Student) 的 name 属性,返回 SSStudent hugh.name = 'Hugh' # 给实例绑定 name 属性 print(hugh.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的 name 属性 del hugh.name # 删除实例的 name 属性 print(hugh.name) # 再次调用 hugh.name,由于实例的 name 属性没有找到,类的 name 属性就显示出来了

提示:从上面的例子可以看出,在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性会屏蔽掉类属性。但是当你删除实例 属性后,再次使用相同的名称,访问到的将是 类属性。

总结

1. 实例属性属于各个实例所有,互不干扰
2. 类属性属于类所有,所有实例共享一个属性
3. 不要对实例属性和类属性使用相同的名字,否则容易产生难以发现的错误

6.9 实例方法、类方法、静态方法

6.9.1 实例方法

在类里面,定义的普通方法,就是实例方法,实例方法的第一个参数一般约定写 self,也可以使用其它名称(但不建议),它代表实例本身。

class Student: def say(self): pass
6.9.2 类方法

使用 @classmethod 装饰的方法,表示是类方法,类方法的第一个参数一般约定写 cls,也可以使用其它名称(但不建议),它表示类本身。

class Student: @classmethod def say_good_morning(cls): print(f'Good Morning!, {cls}') hugh = Student() hugh.say_good_morning() # Good Morning!, <class '__main__.Student'> Student.say_good_morning() # Good Morning!, <class '__main__.Student'>

6.9.3 静态方法

使用 @staticmethod 装饰的方法,表示静态方法。

class Student: @staticmethod def say_good_morning(): print('Good Morning!') hugh = Student() hugh.say_good_morning() # Good Morning! Student.say_good_morning() # Good Morning!

总结

1. 实例可以直接调用 实例方法、类方法、静态方法
2. 类可以直接调用 类方法、静态方法
3. 类可以通过 类.实例方法(实例对象) 的方式调用 实例方法

6.10 property

通过 property 可以将 方法 变为 属性的形式去访问。

比较原始的方式,访问 和 修改 实例属性

class Student: def __init__(self, name, score): self.__name = name self.__score = score def get_score(self): return self.__score def set_score(self, value): if 0 <= value <= 100: self.__score = value def del_score(self): del self.__score hugh = Student('Hugh', 90) print(hugh.get_score()) hugh.set_score(100) print(hugh.get_score())

property 方式

class Student: def __init__(self, name, score): self.__name = name self.__score = score @property def score(self): return self.__score @score.setter def score(self, value): if 0 <= value <= 100: self.__score = value @score.deleter def score(self): del self.__score hugh = Student('Hugh', 90) print(hugh.score) hugh.score = 100 print(hugh.score) del hugh.score

七、异常

异常即是一个事件,该事件会在程序执行过程中发生,影响了程序的正常执行。 一般情况下,在 Python 无法正常处理程序时就会发生一个异常。异常是 Python 对象,表示一个错误。

当 Python 脚本发生异常时我们需要捕获处理它,否则程序会终止执行。

a = 1 / 0 # raise ZeroDivisionError: division by zero print('Hello World')

7.1 处理异常

捕捉异常可以使用 try/except 语句。

try/except 语句用来检测 try 语句块中的错误,从而让 except 语句捕获异常信息并处理。 如果你不想在异常发生时结束你的程序,只需在 try 里捕获它。

让我们用一个例子来看看 try 的机制。

try: print('try ...') r = 10 / 0 print('result:', r) except ZeroDivisionError as e: print('except:', e) finally: print('finally') print('END')

当我们认为某些代码可能会出错时,就可以用 try 来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码, 即 except 语句块,执行完 except 后,如果有 finally 语句块,则执行 finally 语句块,至此,执行完毕。

上面的代码在计算 10 / 0 时会产生一个除法运算错误。

try... except: division by zero finally... END

从输出可以看到,当错误发生时,后续语句 print('result:', r) 不会被执行,except 由于捕获到 ZeroDivisionError,因此被执行。 最后,finally 语句被执行。然后,程序继续按照流程往下走。

如果把除数 0 改成 2,则执行结果如下。

try... result: 5 finally... END

由于没有错误发生,所以 except 语句块不会被执行,但是 finally 如果有,则一定会被执行(可以没有 finally 语句)

你还可以猜测,错误应该有很多种类,如果发生了不同类型的错误,应该由不同的 except 语句块处理。没错,可以有多个 except 来捕获不同类型的错误。

try: print('try ...') r = 10 / int('a') print('result:', r) except ValueError as e: print('ValueError:', e) except ZeroDivisionError as e: print('ZeroDivisionError:', e) finally: print('finally ...') print('END')

int()函数可能会抛出 ValueError,所以我们用一个 except 捕获 ValueError,用另一个 except 捕获ZeroDivisionError。 此外,如果没有错误发生,可以在 except 语句块后面加一个 else,当没有错误发生时,会自动执行else语句。

try: print('try ...') r = 10 / int('2') print('result:', r) except ValueError as e: print('ValueError:', e) except ZeroDivisionError as e: print('ZeroDivisionError:', e) else: print('no error!') finally: print('finally ...') print('END')

八、导入

模块:用来从逻辑上组织 Python 代码(变量、函数、类、逻辑)去实现一个功能。本质就是 .py 结尾的Python文件。 包:用来从逻辑上组织模块的(可以放一堆模块在目录下)。本质就是一个目录(必须带有一个 __init__.py 文件)。

8.1 导入模块

自己写了一个模块,名为hello.py, 内容如下

name = "hello"

在当前同目录的其他文件中

import hello print(hello.name)

或者

from hello import name print(hello.name)

或者

from hello import * # 不建议使用,可能污染全局变量 print(name)

8.2 导入包

新建一个包名为 info,并在包内新建一个文件为 test.py, 并在里面输入 name = 'info.test'

在包外部导入方式

from info.test import name from info import test