sonny's python是我2021年学习python时候的笔记。学习的内容主要来自runoob.com 以及 Python教程 - 廖雪峰的官方网站 。笔记主要记录了在学习过程中我觉得重要且值得探索的特性,所以该笔记无法作为一份入门教程而存在,希望以后可以通过它唤醒我们的脑海中关于python的记忆。
第一部分 基础语法
1.1 变量
python
属于动态语言,变量是无类型的。
name = 'sonny'
print(sonny)
简单来说,就是不会像Java一样还要指定类型String name = “sonny”
。
1.2 常量
通常大写表示。
1.3 注释
| 符号 | 说明 |
| —- | ——– |
| # | 单行注释 |
| ''' | 多行注释 |
| ““” | 多行注释 |
class KeyError(LookupError):
""" Mapping key not found. """
def __init__(self, *args, **kwargs): # real signature unknown
pass
def __str__(self, *args, **kwargs): # real signature unknown
""" Return str(self). """
pass
1.4 运算符
算数运算符
只列举几个常见的
| ** | 表示 N次方 |
| —- | ———— |
| // | 表示向下取整 |
py的除法
- / 表示除,得到的是浮点数,如
结果为print(10/3)
3.333333333335
`//` 又称之为地板除,结果取整数
10 // 3
结果为
3
#### 逻辑运算符
| 符号 | 说明 |
| ---- | -------- |
| and | 与 |
| or | 或 |
| not | 非 |
#### 成员运算符
在字符串,列表,元组中可用。
- `in` 在
- `not in` 不在
numbers = [1, 2, 3, 4]
a = 1
b = 10
print(a in numbers)
print(b not in numbers)
str = '1234ABCD'
c = '1'
print(c in str)
输出全部为`True`
#### 身份运算符
`is` 判断两个标识是否引用自一个对象
`is not` 判断两个标识是否不是引用自一个对象
> tips: ==用于判断是否等值
#### python中的经典表达式
a=1
b=2
a,b=b,a+b
print(a)
print(b)
要弄清楚`a`和`b`最终的值,就要弄清楚`a,b=b,a+b`是如何计算的。其实 很简单,如下图所示
![image-20210316091654648](https://aliyun-oss-chingsblog.oss-cn-shenzhen.aliyuncs.com/blog/typora/2021/03/16/20210316151346.png)
就是把`b`赋值给`a`,把`a+b`赋值给`b`。其实就是 `(a, b) = (b, a+b)`
类似的可以推测
a,b=b,a
这是将`a`和`b`两个值互换。
### 1.5 数据类型
字符串 `str`;
字节类型 `bytes`,一般在前面加上一个小写的`b`,如 `b'ABC'` ;
### 1.6 格式化输出
1. 第一种方式
| 符号 | 说明 |
| ---- | -------- |
| %d | 整数 |
| %f | 浮点数 |
| %s | 字符串 |
| %x | 十六进制数 |
需要注意的是, 格式化的时候需要额外加上一个%,后面紧接着替换值,例如:
print('你好呀,%s' % '马大姐')
上面将会输出
你好呀,马大姐
2. format()
print('this is {0}, hello {1}'.format('python', 'world'))
上面将会输出`this is python, hello world`.
3. f-string
以f开头的字符串,将变量包在{ } 里面,然后自动替换
s='world'
print(f'hello {s}')
上面将会输出`hello world`.
### 1.7 数组
#### list
列表,用{ } 来表示,list中的元素类型可以不同。
classmate = ['Bob', 'Allen', 'Tracy']
L = [1, 'S', True]
print(len(classmate))
““”
表示获取正数第一个/正数第二个
““”
print(classmate[0])
print(classmate[1])
““”
表示获取倒数第一个/倒数第二个
““”
print(classmate[-1])
print(classmate[-2])
classmate.append('Jack')
classmate.pop()
常用方法
| 方法 | 描述 |
| ----------------------------------- | ---------------------------------------- |
| list.append(obj) | 在列表末尾添加新的对象 |
| list.count(obj) | 统计某个元素在列表中出现的次数 |
| list.extend(seq) | 在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表)从列表中找出某个值第一个匹配项的索引位置 |
| list.index(obj)] | 从列表中找出某个值第一个匹配项的索引位置 |
| list.insert(index, obj) | 将对象插入列表 |
| list.pop([index=-1]) | 移除列表中的一个元素(默认最后一个元素),并且返回该元素的值 |
| list.remove(obj) | 移除列表中某个值的第一个匹配项反向列表中元素 |
| list.reverse() | 反向列表中元素 |
| list.sort( key=None, reverse=False) | 对原列表进行排序 |
| list.clear() | 清空列表 |
| list.copy() | 复制列表 |
#### tuple
元组,初始化以后便不可改变的有序列表。用()来表示。需要注意的是,当元组只有一个元素的时候,规定必须手动加上一个`,` ,否则会被当作同数学中的括号.
roommate = (1, 'S', True)
roommate_of_mine = (1,)
### 1.8 条件判断
#### if /else
age = 20
if age <= 3:
print('3岁小孩')
elif (age > 3) and (age < 18):
print('小屁孩')
else:
print('男人')
### 1.9 循环
#### for
for i in list:
print(i)
看起来很方便,但是如果你是从其他语言(C、Java等)转过来,有时候你会想要拿到list的下标。这时候,只用for肯定不行。在python中,这种场景一般可以用`enumerate`来做。
my_list = ['apple', 'banana', 'grapes', 'pear']
for counter, value in enumerate(my_list, 0):
print(counter, value)
输出:
(1, 'apple')
(2, 'banana')
(3, 'grapes')
(4, 'pear')
上面代码,意思是迭代的同时进行计数(自行指定),让my_list从指定数字开始迭代。这时候,如果你从0开始迭代,那么`counter`就是对应的下标了。
#### while
n = 20
while n > 0:
print(n)
n = n - 2
### 1.10 键值对
就类似java中的`Map/Set`
#### dict
无序集合
d = {'Bob':100, 'Jack': 98}
d['Bob'] = 99
value = d.get('Jack')
value = d.get('Jack', -1)
d.pop('Jack')
#### Set
key不重复的集合
s = set([1, 2, 3])
s.add(4)
s.remove(2)
### 1.11 枚举
#### 创建枚举
一个正常的枚举是继承自`Enum`的。
from enum import Enum
class Color(Enum):
RED = 1
BLACK = 2
BLUE = 3
#### 枚举常用操作
枚举成员不等于枚举的值,枚举成员是Color的一个实例
print(Color.BLUE)
output: Color.BLUE
枚举成员的类型
print(type(Color.RED))
output:
按值查找枚举成员
print(Color(3))
output: Color.BLUE
按名称查找枚举成员
print(Color['BLUE'])
output: Color.BLUE
输出枚举名
print(Color.BLUE.name)
output: BLUE
输出枚举值
print(Color.BLUE.value)
output: 3
遍历枚举
for color in Color:
print(color.value)
#### 枚举成员值重复
`python`中的枚举元素的值是可重复的,第二个重复的枚举成员相当于第一个的别名。
如下面的`BLUE_alias`,是`BLUE`的别名。
from enum import Enum
class Color(Enum):
RED = 1
BLACK = 2
BLUE = 3
BLUE_alias = 3
注意:
- 按值查找枚举成员时,只会转成原名成员(`BLUE`),不会转成别名成员(`BLUE_alias`);
- 按名称查找枚举成员时,就算输入别名(`BLUE_alias`),也会直接转成原名成员(`BLUE`);
- 使用`for`遍历时,不会输出别名枚举成员(`BLUE_alias`);
如下所示
按值查找枚举成员。注意:只会转成原名成员(BLUE),不会转成别名成员(BLUE_alias)
print(Color(3))
output: Color.BLUE
按名称查找枚举成员。注意:就算输入别名(BLUE_alias),也会直接转成原名成员(BLUE)
print(Color['BLUE_alias'])
output: Color.BLUE
遍历枚举
for color in Color:
print(color)
output: Color.RED Color.BLACK Color.BLUE
#### @unique注解
`@unique`不允许枚举值重复
from enum import Enum, unique
@unique
class Color(Enum):
RED = 1
BLACK = 2
BLUE = 3
BLUE_alias = 3
上面代码执行时会报错
Traceback (most recent call last):
File “C:/Users/广西北部湾银行/Desktop/python practice/Enum.py”, line 5, in
class Color(Enum):
File “E:\develop\python3.7\lib\enum.py”, line 869, in unique
(enumeration, alias_details))
ValueError: duplicate values found in
### 1.12 异常处理
try:
...(要执行的代码)
except ***Error:
...(异常时执行)
else:
...(正常运行时执行)
finally:
...(必定执行的代码)
除了`try`关键字, 下面的关键词(`except/else/finally`)均为可选,其中,可以存在多个`except`。
#### 抛出异常
raise。
手动抛出异常
if x>5:
raise Exception("x不能大于5")
不处理异常,向上抛出
try:
...(要执行的代码)
except ***Error:
raise
`pass` 关键字表示忽略异常
#### 自定义异常
继承自Exception类
class MyError(Exception):
def __init__(self, value):
self.value=value
def __str__(self):
return self.value
try:
raise MyError(2)
except MyError as e:
# print(e.value)
raise
### 1.13 文件操作
#### open()
open() 函数常用形式是接收两个参数:文件名(file)和模式(mode)。
使用`with`语句,我们就可以不用显式地调用 `close()` 方法,不管是否出现异常都会自动`close()`,这和手动`try...finally`的效果是一样的。
如下所示:
with open('/path/to/file', 'rw') as f:
print(f.read())
f.write('Hello, world!')
但实际上`open()`有这么多参数
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
参数说明:
* file: 必需,文件路径(相对或者绝对路径)
* mode: 可选,文件打开模式
* buffering: 设置缓冲
* encoding: 一般使用utf8
* errors: 报错级别
* newline: 区分换行符
* closefd: 传入的file参数类型
* opener: 设置自定义开启器,开启器的返回值必须是一个打开的文件描述符。
其中,mode有很多可选项,这里列出常用的几个:
- 如果你想读取文件,传入`r`
- 如果你想读取并写入文件,传入`r+`
- 如果你想覆盖写入文件,传入`w`
- 如果你想在文件末尾附加内容,传入`a`
### 1.14 面向对象
#### 类定义
类名大写开头
class ClassName:
....
#### 实例化
x = ClassName()
#### 构造方法
def init(self):
......
一般来说,类中第一参数永远是`self`(叫别的名也可),表示该实例自身,类似于`java`中的`this`。并且,调用该方法时,不用传递该参数。
class Student:
def __init__(self, name, student_id):
self.name = name
self.student_id = student_id
def total_score(self, math_score, social_score):
return print(f"studentName: {self.name}, id: {self.student_id}. "
f"totalScore: {math_score+social_score}")
调用
y = Student('sonny', '10086')
y.total_score(1, 2)
#### 私有方法
在`Java`中,表示私有一般有 `private/protected/public`来表示方法或者变量的可见范围。
python中约定,私有属性一般可以用`_`或者`__`来表示,如`__name`, `__add()`。但是二者略有差别。
#### 区分`_`和`__`
##### `_` 表达了一种受保护的约定
class Cat:
def _method(self):
print('method from Cat')
littleCat = Cat()
littleCat._method();
用一个下划线的时候,按`python`的约定是表示私有化。在`pycharm`引用`littleCat`实例的时候,是不会提示有`_method()`方法的。但是如果你强行手动敲上`_method`, 在`pycharm`中的也会友情提示你“访问了被保护的成员方法”,如下图所示。
![pycharm访问被保护方法提示](https://aliyun-oss-chingsblog.oss-cn-shenzhen.aliyuncs.com/blog/typora/2021/03/10/20210310092815.png)
而上述代码实际上是可以执行成功的,结果为
method from Cat
> 结论,\_ 只是表达了一种受保护的约定,但是仍然可以调用(但是很反对这么做)
##### `__`是一种伪私有化
首先,明确一下私有方法的两个特点:
1. 在类的外部无法被调用;
2. 不能被子类所覆盖;
然后,我们来观察下面的代码,并试着执行一下
class Cat:
def __aowu(self):
print('嗷呜~ from Cat')
littleCat = Cat()
littleCat.__aowu()
发现执行上述代码会报错,如下所示
File “…“, line 21, in
littleCat.__aowu()
AttributeError: 'Cat' object has no attribute '__aowu'
可以发现,提示了 **方法不存在** ,而不是 **调用了私有方法** 。为什么呢?
原来,python中用到了`name mangling`技术,这个技术主要被各大语言用来解决命名冲突问题。
在py中`name mangling`的直接作用是:把 **__方法名** 变成 **_类名__方法名** 。
所以,这就解释了为什么上述代码在调用`__aowu()`的时候会报 **方法不存在** 的异常,因为它已经变成 `_Cat__aowu()` 了。
那么,若是调用`_Cat__aowu()`,应该可以执行成功吧?
class Cat:
def __aowu(self):
print('嗷呜~ from Cat')
littleCat = Cat()
littleCat.Cat_aowu()
结果如下。是可以执行成功的,和预想的一样
method from Cat
到这里,得到的一个重要结论是:
> 使用双下划线,会把 **\_\_方法名** 变成 **\_类名\_\_方法名** 。
这时候,在Cat 之外无法调用 `\_\_aowu()` ,这就满足了私有方法的“无法在定义它的类之外被调用”特点。
在考虑一下,其实会发现,私有方法的第二个特征“不能被子类所覆盖”其实也满足了。来看一下。
class Cat:
def __aowu(self):
print('嗷呜~ from Cat')
def aowu(self):# 用于暴露一个接口给子类去调用Cat的私有方法__aowu()
self.__aowu() # 从Cat继承而来的方法,间接调用父类Cat的的__aowu()
class Dog(Cat):
def __init__(self):
self.__aowu()
self.aowu()
def __aowu(self):
print('嗷呜~ , learn from Cat!')
Dog()
执行结果为
method from Cat
method from Dog, rewrite from Cat!
有一个`Dog`类继承了`Cat`,`Dog`里面也有一个 `\_\_aowu()` 。根据上面的结论,`Dog`的`\_\_aowu()` 其实是 `\_Dog\_\_aowu()` ,而不是Cat中的 `\_Cat\_\_aowu()` ,所以自然不会覆盖。
如果说 \_\_(双下划线) 是为了表达 **私有** 的概念,那么上面的例子中,Cat的实例`littleCat`不能调用私有方法 `\_\_aowu()` ,其实已经达到了 **私有** 的效果,只不过它是悄悄把方法名修改了,从而达到 **私有** 的效果 ,而并不是语言层面上的 **私有** ,所以说 这是一种 **伪私有化** 。
#### 双下划线方法的覆盖
现在你知道了python用了`name mangling`,也知道在这是一种“伪私有”,如果你想要强行覆盖私有方法,也不是不行。来看下面的代码
class Cat:
def __aowu(self):
print('嗷呜~ from Cat')
def aowu(self):# 用于暴露一个接口给子类去调用Cat的私有方法__aowu()
self.__aowu()
class Dog(Cat):
def __init__(self):
self.__aowu() # 调用Dog自身__aowu()
self.aowu() # 从Cat继承而来的方法,间接调用父类Cat的的__aowu()
self._Cat__aowu() # 调用Dog自己写的__aowu()
def __aowu(self):
print('嗷呜~ , learn from Cat!')
def _Cat__aowu(self):
print('嗷呜222~ , learn from Cat!')
Dog()
可以看到,如果你在子类Dog中,手动增加一个`_Cat__aowu() `方法。如果Dog把父类Cat的`__aowu()` 给重写了,那么第二行输出的应该是 “嗷呜222~ , learn from Cat!”。
试着执行一下,果不其然。
运行结果为
嗷呜~ , learn from Cat!
嗷呜222~ , learn from Cat!
嗷呜222~ , learn from Cat!
所以,非特殊情况不要给一个方法以 `_类名__` 开头。一旦取了这种妖魔鬼怪的名字,指不定就把什么私有方法给给覆盖了。
#### 类的专有方法
| 方法 | 释义 |
| --------------- | ------------- |
| \_\_init\_\_ | 构造函数,在生成对象时调用 |
| \_\_del\_\_ | 析构函数,释放对象时使用 |
| \_\_repr\_\_ | 打印,转换 |
| \_\_setitem\_\_ | 按照索引赋值 |
| \_\_getitem\_\_ | 按照索引获取值 |
| \_\_len\_\_ | 获得长度 |
| \_\_cmp\_\_ | 比较运算 |
| \_\_call\_\_ | 函数调用 |
| \_\_add\_\_ | 加运算 |
| \_\_sub\_\_ | 减运算 |
| \_\_mul\_\_ | 乘运算 |
| \_\_truediv\_\_ | 除运算 |
| \_\_mod\_\_ | 求余运算 |
| \_\_pow\_\_ | 乘方 |