Python 简介

安装Python

$E = MC^2$

常用工具介绍

优势:

- 文字、代码、图表可统一在一个地方。
- 可方便转换为其他形式的文件,例如: pdf, html, markdown, slides 等

劣势:

- 内容比较多时容易杂乱。
- 解决方案:添加目录。这是我从jupyter notebook 转为 jupyter lab 的一个主要原因

如有自行编写的需经常重复使用的代码,应进行整理并构建自己的常用库(下面会介绍)

模块安装

conda install <package_name>

conda install -c conda-forge <package_name>

也可以用pip3,类似的傻瓜式操作.

环境

python中有一个virtualenv可用来管理不同的python运作环境。好处:

在命令行中输入以下代码:

conda create --name myenv python=3.8

anaconda3/envs/下构建了一个myenv环境

要切换到这个环境:

conda activate myenv

不用这个环境了:

conda remove --name myenv --all

如果出现有一个package只在pip中有,但我又想安装在conda的一个虚拟环境中,该怎么办? https://stackoverflow.com/questions/41060382/using-pip-to-install-packages-to-anaconda-environment/56889729#56889729

Python 基础

数据类型和变量

整数

浮点数

字符串

如果要很多转义,就有很多的\\。Python 还允许这样:

r'' 中的字符不转义

还可以用 """ """, 通常用于函数和类的说明

字符串内部的字符可以循环

字符串中引用变量

关于编码

什么是编码?

计算机只能处理数字,因此文本需转换为数字。最早的计算机只包含127个字符(大小写英文、数字、符号),这个编码表就是 ASCII。

中文的编码随后出现,名称是 GB2312。

如果编码没有指明,就会出现乱码。为了解决这个问题,出现了Unicode编码。这个编码把所有语言都统一到一套编码里。

最常用的Unicode编码是 UCS-16,用2个字节(byte, 1个byte是8个bit)表示一个字符。如果有偏僻的字符,就用4个字节。现代的操作系统和编程语言一般都支持Unicode。

但是,如果写的东西大部分是英文(比如代码),那么都用Unicode编码会占据大量的存储空间,存储和传输不方便。因此,在传输时,经常使用 UTF-8 编码,这种编码方式会根据字符的常用程度,进行不同长短的编码。

常用的英文字母是1个字节,汉字通常是3个字节,生僻的字符有4-6个字节。

字符 ASCII Unicode UTF-8
A 01000001 00000000 01000001 01000001
x 01001110 00101101 11100100 10111000 10101101

因此,计算机一般这样工作:

Python 3 中字符串以 Unicode 编码。因此,中文可以显示。

字符串前若有b,则这个字符串为bytes的格式

因此,在操作字符串时,时常会遇到strbytes的转换,应当始终用UTF-8编码进行。

另外,时常我们会看到一段这样的代码放在 .py文件的头两行:

这是两段特殊的注释。第一行告诉Linux/OS X 操作系统,这是可执行的程序。第二行告诉Python解释器,这段代码用UTF-8读取。

中国数据的例子

可以发现数据在生成时没有从GB编码转换为UTF-8,给用户带来了额外的不便。

布尔值

& 是bit-wise的操作

上式原因:& 的运算优先级比>高。2&3 的运算是 $$0000 0010\ \& \ 0000 0011 = 0000 0010 = 2$$. 因此,上式实际上等价于 $$5>2>2$$

空值

变量

命名规则:大小写英文、下划线、数字的组合。不可以用数字开头。

赋值时无需指明变量类型。这种语言被称为 动态语言 . 如果需要指明变量类型,则是 静态语言

例如,C++里面赋值是

int a = 34;

如果此时再 a = "ads",则会报错

另外,赋值操作的步骤在内存中实际上分两步。比如:

  1. 在内存中的某个位置放入'asd'
  2. 在内存中创建一个名称是 a 的变量,把它指向'asd'

这个操作是把 a 指向'asd',把 b 指向 a 所指向的数据。当 a 指向的数据变了之后, b 指向的数据并没有变化

常量

常量就是不变的量。命名规则:常量用大写字母。

另外,Python 2 和 3 在除法上有一个不同。

在 Python 2中,上面的除法会给出整数。3 / 2 的结果是 1

这种除法实际上是 floor。在 Python 3中,要得到floor,则

在 Python 2中,要得到浮点的解,则可以这样写:3 / 2.0。此时会给出结果 1.5

另外,Python的整数没有最大最小的范围,因此数字的大小仅受内存的影响。浮点数也没有大小限制,但超出一定范围会表示为 'inf'

List, tuple

List

注意:list 中的 slicing 的终结点是数据实际标号的下一位。这个 slicing 和 pandasDataFrame.loc slicing 不同,但和 .iloc 是一样的。

tuple

tuple 中的元素一旦确定,就不可以更改。

不能更改有什么用?数据更安全。

因此,可以用tuple的地方就尽量用tuple,以免不小心数据进行了不必要的改动

这是因为括号()也可以被理解为代数运算里的“括号”,产生了歧义。Python规定上面这种写法是代数运算里的括号,而非tuple. 若要写一个单元素的tuple,则

以上说明:

因此:

条件判断 if, else

if 从上往下执行,只要发现了 True, 就忽略掉剩下的 elif, else。

只要 if 后面的不是0,不是空str, 不是空的list, 不是None,等,就判断为 True

循环 Loops

dict, set

dict 就像是有索引的电话本,查找数据很快。和list相比,dict是用空间换时间:

dict 的 keys 必须是不可变的。因此不可以是 list

set也是一组key的集合(因此不可以重复),但不存储value

不变的、可变的对象,要注意区分

一个常见的错误(我自己就犯过好多次),是想要直接更改字符串的内容。但字符串是不可变的!

list 本身改变了

a 本身没有变

replace 方法作用在 'xyz' 上,但没有改变 'xyz' 的内容,而是返回了一个新的字符串 'Xyz'。

函数

自定义函数

pass

pass 可以什么都不做,经常用作占位。比如这里应该实现一个什么功能,现在已经想好了,但没来得及编写。为了避免这部分出错,就用pass

返回多个值

注意这里返回的是一个 tuple。 可以这样赋值:

如果没有 return, 则函数返回 None

参数

默认参数

若要计算3次方,可以另写一个。但更好的方式是增加一个参数,可以随意输入想要计算的指数。

更好的方法是设置一个默认的参数值。

注意:

默认参数有个坑:

函数在定义的时候,默认参数lst的值就指向了[]。因为lst指向的是一个变量[],每次调用时如果lst的值被改变,下一次就会指向这个改变了的变量。

为了避开这样的坑,编程时:

可变数目的参数

如果输入参数的数目不确定,可以怎么定义?

args: arguments

可变数目的关键字参数

kwargs: key word arguments

如果要限制传入的关键字参数的名称, 则使用分隔符 * (命名关键字参数)

若已有一个可变参数,则后面的参数不需要分隔符*。同时,后面的参数赋值时必须要有名称, 不可以是位置

在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

递归函数

在函数定义时调用函数本身,就是递归函数

===> fact(5) ===> 5 * fact(4) ===> 5 * (4 * fact(3)) ===> 5 * (4 * (3 * fact(2))) ===> 5 * (4 * (3 * (2 * fact(1)))) ===> 5 * (4 * (3 * (2 * 1))) ===> 5 * (4 * (3 * 2)) ===> 5 * (4 * 6) ===> 5 * 24 ===> 120

List Comprehension, Iterable, Iterator, Lazy Iterable, Generator

Iterable

Python 中的迭代很方便,list, tuple, dict, string...很多都可以直接进入 for ... in

enumerate也很常用

Iterator

注意:上面的l已经被next()取用过2次了,因此for e in l:时从3开始

List comprehension

熟练掌握这种语法以后,会在很多时候发现语法变得简单,例如:

zip 函数

上述实现了zip的反向操作。*的作为函数的参数,作用是把list或tuple中的元素拆开。所以zip(ss,range(3))的结果[('a', 0), ('b', 1), ('c', 2)]被拆解成了('a', 0), ('b', 1), ('c', 2)

Generator

generator的作用:不一次过把所有数据都生产出来,根据算法,随用随生成。

很多时候,生成序列的算法是很清晰的,但想要全部一次都生成却有困难。例如Fibonacci数列。

yield 来制作一个 generator

改成 generator:

和函数不一样的地方:

range函数也是lazy iterable,但它本身不是一个 iterator

和函数相关的一些其他用法

map,reduce,filter

map

map有两个参数:

reduce

reduce 也把一个func作用在一个序列上。

reduce(f,[1,2,3,4]) = f(f(f(1,2),3),4)

filter

filter也接收一个func和一个序列。

lambda 函数

lambda 函数无需给出函数名称。可用于一些较复杂操作时,需要一个自定义的简单函数作为中间步骤的情况。例如,做数据操作时,可能需要先分组然后进行一个四则运算,此时可用lambda定义这个四则运算。

lambda实际可看作是最小单元的函数。复杂的函数可以分解成一个一个的lambda

decorator

用途:有的时候我们需要改变一些已有的函数,比如添加一些功能,但又不想修改这个函数本身,此时可以给这个函数加一点“装饰”

wrapper 👆

@log 放在 cat_miao()的定义处,相当于:

cat_miao = log(cat_miao)

此时的cat_miao已被改变为用log装饰过的新的cat_miao

多个 decorator 的顺序

dog_bark -->

`dog_bark` with `print time` (wrap_2) --> 

    `print func name` with (dog_bark` with `print time`)

若 decorator 本身需要参数

有一个问题:cat_miao的名称在decorator中被改变了

要在wrapper中保留func的原名,可以这样写:

closure

闭包:

闭包的设计在很多语言中都有,在程序设计中比较重要。但我们在数据操作时一般不太常用到。

Partial function

有时我们希望把已有的函数拿过来用,但同时觉得原来函数的参数过多,有些参数想直接设好默认值,定义为一个新的函数

模块 Module

my_package ├─ __init__.py ├─ abc.py └─ xyz.py

相当于把模块又封装了一层。这样模块的名称就变成了某个包下的模块,my_package.xyz

__init__.py告诉python这个是一个package,可以为空,也可以有代码。

模块下也可以有多级的结构

mycompany ├─ web │ ├─ __init__.py │ ├─ utils.py │ └─ www.py ├─ __init__.py ├─ abc.py └─ utils.py

原因是 sub-modules 不会自动被导入

如果要自动在导入 my_package 时就导入 xyz,可以在 __init__.py 中加入:

import my_package.xyz

注意到这个语句:

if __name__=='__main__':
    my_print()

作用是:

如果对module做了改动,结果不会发生变化:

如果要重新导入:

作用域

把内部的逻辑隐藏起来,这样就进行了一种有益的抽象和封装

Object Oriented Programming (OOP) 面向对象编程

面向过程的程序设计:

面向对象:

object表示Student是从object这个类里面“继承”而来的(也即自动拥有了object的一些性质或功能)

类是一个模版,所以,创建时,可以把一些必须有的属性放进去

封装

可以从外部访问实例的数据:

但是,score本身存在于xm内部,没有必要从外面访问。另外,除非知道score的定义方式,用外面的函数来访问,可能出错。对于一般的使用者来讲,没有必要搞清楚实例内部的数据的完整定义,只需了解如何去取用数据就好。这种封装类似于一个黑匣子。使用者只需要知道:

可以给类增加很多方法

访问限制

为了避免外部函数访问内部的数据,可以进行限制。

如果想要拿到name 和score,则定义一个方法传出去即可

此时虽然可以拿到name和score,但没法从外面改动了。如果希望score可以改动,则再定义一个方法

在内部定义数据的方法有两个好处:

双下划线的变量实际上由python解释器改了名字,改为了_class__attribute的格式

但最好不要这样做!

类似的,如果这样写:

实际上只是给xm增加了一个 attribute

继承和多态, inheritance and ploymorphism

定义class时可以从现成的class中继承

给子类增加一些方法:

多态

子类和父类有同样的方法时,子类的方法会覆盖父类的

python中的数据类型都是一种对象。例如,str, list, tuple, 等等,都有自己对应的attribute和method。Animal也一样

c既是Dog, 也是 Animal

但b不是Dog

多态的好处:

因此,完全可以写一个新的instance,只要保证子类的run的写法正确,原来的代码是怎么去调用父类方法,我们完全不管:

duck typing

python 另一个很方便的地方是 duck typing

也就是说,完全可以自己定义一个类,这个类不从Animal继承而来,而只要有Animal的方法或者性质,那么在调用时,这个类就可以看作是Animal

这是python这种“动态语言”的特点,而其他一些语言对此是严格要求的。

获取对象信息

type()

isinstance()

dir()

python的很多函数可通用于不同的类型的数据,比如len,返回长度

getattr(), setattr(), hasattr()

错误处理

高级语言一般都内置了一套错误处理方法,来帮助我们编程

try...except...finally

finally不管有没有错,最终都会运行

错误的种类有很多,都继承自 BaseException。捕获错误时要注意,捕获时会把子类也一起算上:

UnicodeErrorValueError的子类,因此不能被捕获

错误类型和继承关系:https://docs.python.org/3/library/exceptions.html#exception-hierarchy

如果没有出错的情况下要继续运行一些其他的功能,可以加else

错误信息栈

参考资料