# python语言学习
Date: 2021-02-17


最新学习极客时间物联网课程中，老师使用的语言是python。之前也没有系统学习过，利用假期，跟着两个极客时间专栏学习了一下python 。

> 使用的MicroPython是精简的、高效的python3的实现， 更加适合嵌入式开发。

python语言是精简，即插即用的语言，有非常丰富的工具包，尤其是在机器学习和数据分析方面。

 

### **python开发工具**

常用的开发工具

- Pycharm
- Jupyter
- Visual studio
- Vim (python-mode)
- Spyder
- Atom

### **python语法**

接触过c，java， linux shell perl，其实就会发现python和这些语言语法基础上也都大同小异。

> 下面代码使用的是python3运行

#### **变量**

- 动态类型变量

我理解的动态右值的类型已经是确定的（一个固定的或者的范围内的值）， 所以左值仅是他的一个提供被使用的标记。

```
>>> var1 = 1
>>> type(var1)
<class 'int'>
>>> var1 = 'hello world'
>>> type(var1)
<class 'str'>
```

- 类型

数字类型支持整数，浮点数， 复数。上面例子可以看到python3 中整数也使用了class int,这样实现使得其在语法上没有最大值限制的，可以支持到非常大的整数。

- 字符串

支持三种分割符号，最容易想到的就是在字符中可以包含单引号“'”或者双引号“"”,

```
'hello world'
"hello world"
'''hello world'''
```

字符串转义字符和其他语言类似也是用“\\”

不能修改字符串某个字符

```
>>> var1='hello'
>>> var1[2]='a'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

```

需要重新生成一个返回。字符串拼接可以使用“string.join(iterator)” 或者“+=”

```
>>> ' '.join('helloworld')
'h e l l o w o r l d'
>>> var1='hello'
>>> var2='world'
>>> var1+=var2
>>> var1
'helloworld'
```

print函数输出使用string.format格式

```
>>> print('{} {}'.format(var1,'!'))
helloworld !
```

- 变量范围（scope）

和其他语言一样有局部变量和全局变量的区别，指定全局变量可以使用global，类似C语言中的定义全局变量的头文件， 可以将全局变量定义到指定的.py文件中将其import到要使用这些全局变量的模块或文件中。

可以通过type()查看变量类型

 

#### **操作符**

操作符和其他语言差不多：

- 数字操作符：

+，-，\*，/， % ,// 除后取整，\*\* 求幂

- 字符串操作符：

\*， 重复

```
>>> 'helloworld'*3
'helloworldhelloworldhelloworld'
```

切片,可以通过“-”号指定从末尾开始

```
>>> var1='helloworld'
>>> var1[0:5]
'hello'
>>> var1[5:]
'world'
>>> var1[:-1]
'helloworl'

```

反转：

```
>>> var1[::-1]
'dlrowolleh'
```

另外还有一些内建的函数如，find，sub，split等

- 强制转换

str(),int(),float()

 

#### **注释**

可以通过 井号 # 或者 三个单引号 '''

 

#### **布尔表达式**

常见的布尔表达式： ==， != , >,<,>=，<= 经常使用在条件语句中。

- \== 与 is
    - \==  判断变量的值是否相等
    - is  判断对象的id()是否相等

 

#### **序列化**

序列化是可以将结构化数据转化为一个便于存放、共享、传输的数据流，转化后的数据流也**可以恢复**成原有的结构化数据。 当然数据的序列化同时也要求其**最小数据的空间**以便于存储和传输。

**pickle**是python对象序列化的一个模块， pytho那文档也支持其本身是不安全的， 因为再反序列化过程中可以执行任意代码。使用时要考虑环境，尽量在可信的环境下运行， 或这通过hmac进行验证。 当然也可以选用**json**的替换方案。

序列化一般分为两种格式：

Flat vs. Nested data

Flat：更简单

```
{ "Type" : "A", "field1": "value1", "field2": "value2", "field3": "value3" }
```

Nested data：可以表示更加复杂的数据结构

```
{"A"
    { "field1": "value1", "field2": "value2", "field3": "value3" } }
```

- 序列号文本

1. 1. repr
    2. ast.literal\_eval
    3. CSV file (flat data)
    4. YAML (nested data)
    5. json
    6. xml

- 序列化二进制

1. 1. NumPy Array
    2. pickle
    3. protobuf

 

#### **条件语句**

一般写法和其他语言类似

```
if condition: 
    x = true_value 
elif condition:
    x = elif_value
else: 
    x = false_value
```

One Line写法， 有类似 condition?expression1:expresss2。语句

```
x = true_value if condition else false_value
```

 

#### **循环语句**

- while
- for

1. 1. One Line for

```
[Variable] AggregateFunction([Value] for [item] in [collection])

```

- 其他相关的一些语句
    - range(start, stop, step)
    - break

#### **函数**

- 函数形式

```
def name(param1, param2, ..., paramN):
    statements
    return/yield value # optional
```

python可以为不同参数设定默认值， 类似java的builder实现类创建, 通过工厂方法的方式创建类， 但是python能实现起来更简单直接一些。

```
Object s2 = new ObjectBuilder()
                 .attr1("helloworld")
                 .attr2(100)
                 .buildObject();
```

- 参数：

- - \*args： 不带key值

```
>>> def test1(*argv):
...     for arg in argv:
...         print('{} '.format(arg))
...
>>> test1(1,2,'hello world')
1
2
hello world

```

- - \*\*kwargs：带key值

```
>>> def test2(**argv):
...     for k,v in argv.items():
...          print('{} : {}'.format(k,v))
...
>>> test2(name='hello',age=100)
name : hello
age : 100

```

- 返回值：

通过yield可以按照iterator返回，有些像游标。是否停止依赖函数本身逻辑。

- 内部函数及闭包

返回一个函数，返回一个具体的功能函数， 这个函数所以涉及的变量不会随着函数消亡而消失。

```
>>> def multiplier_of(n):
...     def multiplier(number):
...         return number*n
...     return multiplier
...
>>> multiplywith5 = multiplier_of(5)
>>> multiplywith5(10)
50
>>> del multiplier_of
>>> multiplywith5(10)
50
```

为什么使用闭包：

1. 1. 减少使用全局变量；
    2. 减少常量使用
    3. 装饰器方法使用了闭包

- lambda

 variable = lambda arguments: expression

- 参数传递

对于普通变量类似值传递，并不会修改原有值。传递引用， **如果修改变量可以通过return，将要修改的结果返回**。

对于collection类型的可变变量，如果不是产生新的变量，修改对原有变量生效。

```
>>> list=[1,2,3]
>>> def func(l):
...    l.append(4)
...
>>> func(list)
>>> list
[1, 2, 3, 4]
>>> def func(l):
...    l=l+[4]
...
>>> list=[1,2,3]
>>> func(list)
>>> list
[1, 2, 3]
>>>

```

 

#### **模块**

导入全部模块

```
import my_module
```

导入模块的部分功能

```
from my_module import my_object
```

#### **包**

python官方文档有相关文档 [Packaging Python Projects](https://packaging.python.org/tutorials/packaging-projects/)

需要安装包的话可以通过**pip/pip3**进行安装， 国内常用操作是替换镜像源方便下载。基本上能解决大多数问题，在物联网实战课程中遇到了有些包指定了下载源的情况。

- windows
    - 创建文件%APPDATA%/pip/pip.ini
- linux
    - ~/.pip/pip.conf

```
[global] 
timeout = 6000 
index-url = https://mirrors.aliyun.com/pypi/simple/ 
trusted-host = mirrors.aliyun.com
```

国内其他源

```
阿里云：https://mirrors.aliyun.com/pypi/simple/ 
清华：https://pypi.tuna.tsinghua.edu.cn/simple 
中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple/ 
华中理工大学：http://pypi.hustunique.com/ 
山东理工大学：http://pypi.sdutlinux.org/ 
豆瓣：http://pypi.douban.com/simple/
```

 

#### **递归**

和C，java区别不大，下面是一个斐波那契数列的实现

```
def fib(n):
   if n<=1:
       return n
   else:
       return fib(n-1)+fib(n-2)
```

 

#### **程序调试**

一般是两种方法：

- trace日志：一种是日志跟踪， trace打印调用链，确定故障位置，可以使用**traceback**模块
- pdb：动态调试 通过pdb.set\_trace()， 可以进入调试模式，对应**pdb**模块

 

#### **Collections**

常用的这四种：list，tuple，dictionary，set

- list&tuple： list动态可扩展，tuple静态，tuple性能优于list

当然tuple中如果存放数据结构可变是不受影响的

```
>>> t = ([1,2,3],[4,5,6])
>>> t[0].append(7)
>>> t
([1, 2, 3, 7], [4, 5, 6])

```

- dict&set：dict有键值，set无建值， 无序无索引，尤其是set是随机访问

 

#### **编译链接**

可以使用C或C++扩展python， 也可以在c中调用python函数， 类似java通过JNI调用c或c++写得动态库一样，都有标准流程。

相关手册：

- 官网 [使用 C 或 C++ 扩展 Python](https://docs.python.org/3/library/pdb.html#module-pdb)
- 微软 [Create a C++ extension for Python](https://docs.microsoft.com/en-us/visualstudio/python/working-with-c-cpp-python-in-visual-studio?view=vs-2019)

后者实际操作更方便。

 

#### **迭代器（Iterator）**

与迭代器配合使用的函数**map(),filter(),reduce(), zip(), enumerate()**

前三个函数就是mapreduce的处理步骤， zip是将多个collections合并或者拆分， 而enumerate可以给iterator进行索引，返回相关序号。

另外在函数返回中提到的**yield**也是使用迭代器。

#### **面向对象**

这个和C++，java中的概念一样

python支持多继承， 通过C3算法保证按照一定的顺序初始化。

构造函数使用 **init** ， 通过super()可以调用父类构造函数

 

#### **内存管理**

- 回收策略：引用计数，出现循环引用后可以使用手工回收

```
import gc
collected_objects = gc.collect()
```

- 回收算法：标记、分代回收。
- 内存调试：objgraph工具包

 

#### **I/O操作**

- 文件处理：提前预估各种错误处理防止不应当的崩溃出现。
- 中间件处理：如数据库链接等
- 网络处理： 涉及序列化相关工作，如json相关序列化与反序列化

#### **异常处理**

- 抛出异常

```
try:
  raise TypError
except:
  print('exception')
```

- 处理异常，不指定异常或指定异常，可以列多条

```
try:
   do_something()
except:
   print('exception')

try:
   do_something()
except TypeError:
   print('exception')
```

- 针对没有异常的处理放到else里

```
try:
  do something
except IfTryRaisedException1:
  do something else
except (IfTryRaisedException2, IfTryRaisedException3)
  if exception 2 or 3 is raised then do something
else:
  no exceptions were raised
```

- 正常，异常后必须处理流程放到finally里

```
try:
   do_something()
except TypeError:
   print('exception')
finally:
   close_connections()
```

- 调试assert：可以使用assert做为调试工具。辅助代码开发进行自检，可以手工开启关闭

```
assert <bool>, 'error to throw'
```

- 自定义异常：可以根据需要自定异常类

```
class MyException(Exception): pass
```

 

#### **装饰器**

通过装饰器函数对函数的功能进行修改，但不影响原有函数。可以方便的将一些非业务类功能通过装饰器方式加载到现有的函数上。

```
from functools import wraps
def memoization(fn):
    cache = {}
    miss = object()
 
    @wraps(fn)
    def wrapper(*args):
        result = cache.get(args, miss)
        if result is miss:
            result = fn(*args)
            cache[args] = result
        return result
 
    return wrapper
 
@memoization
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

```

这是陈浩《左耳听风》专栏里的一段代码，通过装饰器给Fibonacci函数增加缓存功能。

 

#### **单元测试**

单元测试一般用于验证模块的正确性。常用模块 unittest，

- mock：

可以使用下面三个技巧：

- - mock：模拟依赖服务或功能
    - mock side effet： 模拟根据不同输入返回不同的值
    - patch： 模拟到类中其他成员函数

总体来说就是通过mock，使得更专注于正在开发的模块，并且能够通过mock立即进行验证。

- Test Converage：

通过相关工具提高代码测试的覆盖率

- 模块化：

不管是使用mock还是测试覆盖，要完成高质量单元测试，代码模块化是基础

#### **并发编程**

- 协程

并发编程的一种方式， 是单线程不涉及线程间切换，有点像dos时代的中断方式，asyncio 需要python3.7以上版本支持

> 之前开发过dos串口相关功能，有两种方式一种是中断方式，一种是轮询方式，轮询实现相对简单一些，中断方式读取准确率高一些，效率更高一些。

```
>>> import asyncio
>>> async def main():
...     print('hello')
...     await asyncio.sleep(1)
...     print('world')

>>> asyncio.run(main())
hello
world
```

一般需要将async/await/createtask结合一起，

 

- futures

futures针对I/O密集型threading和CPU密集型的multiprocessing，提供了更高层的封装。

[Effortless Concurrency with Python's concurrent.futures](https://rednafi.github.io/digressions/python/2020/04/21/python-concurrent-futures.html#anatomy-of-concurrentfutures) 这篇文章也有相关的实例推荐

- GIL

由于引用大量C语言，而大多数非线程安全的，为了避免复杂竞争风险引入GIL，由于他的全局锁多线程其实也是同时只能运行一个线程。

建议的解决方案还是用其他的语言去实现比如Java， C++等. 所以也看到python不是非常适合高性能的场景。

#### **metaclass**

通过使用期其对子类属性进行修改的特性，

#### **虚拟环境**

virtualenv 可以为不同的应用创建不同运行环境， 使得应用之间不会因为依赖包不同导致互相影响。有点类似虚拟机，docker的思路， 不过python做的更加轻量级一些。

 

### 其他

除了语言语法方面， 数据结构基础算法， 设计模式，各种python库的使用， 领域业务知识也是非常必要的。最终这些语法最终还是为了最终应用开发。

编程语言发展的过程，是从实际应用开发的需求出发，提出的语言的改进，从而增加一些语言特性对语言进行改进升级，并抽象成标准， 而我们学习编程语言的过程则是抽象的理论学习， 所以很难在一开始搞清楚一些细节，所以还是需要回到实践中去，去使用， 去慢慢体会。

 

### 参考及引用

[Everything About Python — Beginner To Advanced](https://medium.com/fintechexplained/everything-about-python-from-beginner-to-advance-level-227d52ef32d2)

[What is data serialization](https://docs.python-guide.org/scenarios/serialization/)

[Python核心技术与实战](https://time.geekbang.org/column/intro/176)

[Closures（可以边学习边实践）](https://www.learnpython.org/en/Closures)

[pip pip3 替换国内镜像源](https://cloud.tencent.com/developer/article/1520882)

[Effortless Concurrency with Python's concurrent.futures](https://rednafi.github.io/digressions/python/2020/04/21/python-concurrent-futures.html#anatomy-of-concurrentfutures)

Photo by  Jη ✿@Jeanne\_8L from twitter

