基础
概念
比如,完成同一个任务,C语言要写1000行代码,Java只需要写100行,而Python可能只要20行。
代码少的代价是运行速度慢,C程序运行1秒钟,Java程序可能需要2秒,而Python程序可能就需要10秒。
所以Python是一种相当高级的语言。
Python就为我们提供了非常完善的基础代码库,覆盖了网络、文件、GUI、数据库、文本等大量内容,被形象地称作“内置电池(batteries included)”。用Python开发,许多功能不必从零编写,直接使用现成的即可。
总的来说,Python的哲学就是简单优雅,尽量写容易看明白的代码,尽量写少的代码。
第一个缺点就是运行速度慢,和C程序相比非常慢,因为Python是解释型语言
第二个缺点就是代码不能加密。如果要发布你的Python程序,实际上就是发布源代码,凡是编译型的语言,都没有这个问题,而解释型的语言,则必须把源码发布出去。
安装Python
在Windows上安装Python
运行Python
安装成功后,打开命令提示符窗口,敲入python
Python解释器
- CPython
当我们从Python官方网站下载并安装好Python 3.x后,我们就直接获得了一个官方版本的解释器:CPython。这个解释器是用C语言开发的,所以叫CPython。在命令行下运行python就是启动CPython解释器。
CPython是使用最广的Python解释器。教程的所有代码也都在CPython下执行。
- IPython
IPython是基于CPython之上的一个交互式解释器,也就是说,IPython只是在交互方式上有所增强,但是执行Python代码的功能和CPython是完全一样的。好比很多国产浏览器虽然外观不同,但内核其实都是调用了IE。
CPython用>>>作为提示符,而IPython用In [序号]:作为提示符。
- PyPy
PyPy是另一个Python解释器,它的目标是执行速度。PyPy采用JIT技术,对Python代码进行动态编译(注意不是解释),所以可以显著提高Python代码的执行速度。
绝大部分Python代码都可以在PyPy下运行,但是PyPy和CPython有一些是不同的,这就导致相同的Python代码在两种解释器下执行可能会有不同的结果。如果你的代码要放到PyPy下执行,就需要了解PyPy和CPython的不同点。
- Jython
Jython是运行在Java平台上的Python解释器,可以直接把Python代码编译成Java字节码执行。
- IronPython
IronPython和Jython类似,只不过IronPython是运行在微软.Net平台上的Python解释器,可以直接把Python代码编译成.Net的字节码。
直接运行py文件
在Windows上是不行的,但是,在Mac和Linux上是可以的,方法是在.py文件的第一行加上一个特殊的注释:
1 | #!/usr/bin/env python3 |
然后,通过命令给hello.py以执行权限:
1 | chmod a+x hello.py |
就可以直接运行hello.py了
Python基础
以#开头的语句是注释,注释是给人看的,可以是任意内容,解释器会忽略掉注释。其他每一行都是一个语句,当语句以冒号:结尾时,缩进的语句视为代码块。
Python使用缩进来组织代码块,请务必遵守约定俗成的习惯,坚持使用4个空格的缩进。
在文本编辑器中,需要设置把Tab自动转换为4个空格,确保不混用Tab和空格。
- 数据类型
- 整数
Python可以处理任意大小的整数,当然包括负整数,在程序中的表示方法和数学上的写法一模一样,例如:1,100,-8080,0,等等。
十六进制用0x前缀和0-9,a-f表示,例如:0xff00,0xa5b4c3d2,等等。
- 浮点数
浮点数也就是小数,之所以称为浮点数,是因为按照科学记数法表示时,一个浮点数的小数点位置是可变的,比如,1.23x109和12.3x108是完全相等的。浮点数可以用数学写法,如1.23,3.14,-9.01,等等。但是对于很大或很小的浮点数,就必须用科学计数法表示,把10用e替代,1.23x109就是1.23e9,或者12.3e8,0.000012可以写成1.2e-5,等等。
整数和浮点数在计算机内部存储的方式是不同的,整数运算永远是精确的(除法难道也是精确的?是的!),而浮点数运算则可能会有四舍五入的误差。
- 字符串
字符串是以单引号’或双引号”括起来的任意文本,可以用转义字符\来标识
为了简化,Python还允许用r’’表示’’内部的字符串默认不转义
如果字符串内部有很多换行,用\n写在一行里不好阅读,为了简化,Python允许用’’’…’’’的格式表示多行内容,可以自己试试:
1 | >>> print('''line1 |
- 布尔值
布尔值和布尔代数的表示完全一致,一个布尔值只有True、False两种值,要么是True,要么是False,在Python中,可以直接用True、False表示布尔值(请注意大小写)
布尔值可以用and、or和not运算。
- 空值
空值是Python里一个特殊的值,用None表示。None不能理解为0,因为0是有意义的,而None是一个特殊的空值。
- 变量
变量的概念基本上和初中代数的方程变量是一致的,只是在计算机程序中,变量不仅可以是数字,还可以是任意数据类型。
变量在程序中就是用一个变量名表示了,变量名必须是大小写英文、数字和_的组合,且不能用数字开头
在Python中,等号=是赋值语句,可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量
- 常量
所谓常量就是不能变的变量,比如常用的数学常数π就是一个常量。在Python中,通常用全部大写的变量名表示常量
- 除法
在Python中,有两种除法,一种除法是/:
1 | >>> 10 / 3 |
/除法计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数:
1 | >>> 9 / 3 |
还有一种除法是//,称为地板除,两个整数的除法仍然是整数:
1 | >>> 10 // 3 |
你没有看错,整数的地板除//永远是整数,即使除不尽。要做精确的除法,使用/就可以。
因为//除法只取结果的整数部分,所以Python还提供一个余数运算,可以得到两个整数相除的余数:
1 | >>> 10 % 3 |
无论整数做//除法还是取余数,结果永远是整数,所以,整数运算结果永远是精确的。
- 格式化
- 用%实现,举例如下:
1 | >>> 'Hello, %s' % 'world' |
- format()
另一种格式化字符串的方法是使用字符串的format()方法,它会用传入的参数依次替换字符串内的占位符{0}、{1}……,不过这种方式写起来比%要麻烦得多:
‘Hello, {0}, 成绩提升了 {1:.1f}%’.format(‘小明’, 17.125)
‘Hello, 小明, 成绩提升了 17.1%’
- list
list是一种有序的集合,可以随时添加和删除其中的元素。
1 | >>> classmates = ['Michael', 'Bob', 'Tracy'] |
用len()函数可以获得list元素的个数:
1 | >>> len(classmates) |
如果要取最后一个元素,除了计算索引位置外,还可以用-1做索引,直接获取最后一个元素:
1 | >>> classmates[-1] |
以此类推,可以获取倒数第2个、倒数第3个:
1 | >>> classmates[-2] |
list中追加元素到末尾:
1 | >>> classmates.append('Adam') |
可以把元素插入到指定的位置,比如索引号为1的位置:
1 | >>> classmates.insert(1, 'Jack') |
要删除list末尾的元素,用pop()方法:
1 | >>> classmates.pop() |
删除指定位置的元素,用pop(i)方法,其中i是索引位置:
1 | >>> classmates.pop(1) |
list里面的元素的数据类型也可以不同,比如:
1 | >>> L = ['Apple', 123, True] |
list元素也可以是另一个list,比如:
1 | >>> s = ['python', 'java', ['asp', 'php'], 'scheme'] |
要拿到’php’可以写p[1]或者s[2][1],因此s可以看成是一个二维数组,类似的还有三维、四维……数组,不过很少用到。
如果一个list中一个元素也没有,就是一个空的list,它的长度为0:
1 | >>> L = [] |
- tuple 元组
tuple一旦初始化就不能修改,比如同样是列出同学的名字:
1 | >>> classmates = ('Michael', 'Bob', 'Tracy') |
现在,classmates这个tuple不能变了,它也没有append(),insert()这样的方法。其他获取元素的方法和list是一样的
- 条件判断
1 | if <条件判断1>: |
- 循环
一种是for…in循环,依次把list或tuple中的每个元素迭代出来,看例子:
1 | names = ['Michael', 'Bob', 'Tracy'] |
Python提供一个range()函数,可以生成一个整数序列,再通过list()函数可以转换为list。比如range(5)生成的序列是从0开始小于5的整数:
1 | >>> list(range(5)) |
第二种循环是while循环,只要条件满足,就不断循环,条件不满足时退出循环。
1 | n = 1 |
- dict (在其他语言中也称为map,使用键-值(key-value)存储)
用Python写一个dict如下:
1 | >>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85} |
由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉
如果key不存在,dict就会报错:
1 | >>> d['Thomas'] |
要避免key不存在的错误,有两种办法,一是通过in判断key是否存在:
1 | >>> 'Thomas' in d |
二是通过dict提供的get()方法,如果key不存在,可以返回None,或者自己指定的value:
1 | >>> d.get('Thomas') |
注意:返回None的时候Python的交互环境不显示结果。
要删除一个key,用pop(key)方法,对应的value也会从dict中删除:
1 | >>> d.pop('Bob') |
- set
set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。
要创建一个set,需要提供一个list作为输入集合:
1 | >>> s = set([1, 2, 3]) |
- 函数
- 数据类型转换
1 | >>> int('123') |
- 定义函数
定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回。
1 | def my_abs(x): |
- 空函数
如果想定义一个什么事也不做的空函数,可以用pass语句:
1 | def nop(): |
pass语句什么都不做,那有什么用?实际上pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。
pass还可以用在其他语句里,比如:
1 | if age >= 18: |
缺少了pass,代码运行就会有语法错误。
- 数据类型检查
数据类型检查可以用内置函数isinstance()实现:
1 | def my_abs(x): |
添加了参数检查后,如果传入错误的参数类型,函数就可以抛出一个错误:
1 | >>> my_abs('A') |
- 返回多个值
比如在游戏中经常需要从一个点移动到另一个点,给出坐标、位移和角度,就可以计算出新的新的坐标:
1 | import math |
然后,我们就可以同时获得返回值:
1 | >>> x, y = move(100, 100, 60, math.pi / 6) |
==但其实这只是一种假象,Python函数返回的仍然是单一值:==
1 | >>> r = move(100, 100, 60, math.pi / 6) |
原来返回值是一个tuple!但是,在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值,所以,Python的函数返回多值其实就是返回一个tuple,但写起来更方便。
- 可变参数
在Python函数中,还可以定义可变参数。顾名思义,可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。
1 | def calc(*numbers): |
可以传入任意个参数,包括0个参数:
1 | >>> calc(1, 2) |
把list或tuple的元素变成可变参数传进去:
1 | >>> nums = [1, 2, 3] |
- 关键字参数
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。请看示例:
1 | def person(name, age, **kw): |
函数person除了必选参数name和age外,还接受关键字参数kw。在调用该函数时,可以只传入必选参数:
1 | >>> person('Michael', 30) |
也可以传入任意个数的关键字参数:
1 | >>> person('Bob', 35, city='Beijing') |
写法:
1 | >>> extra = {'city': 'Beijing', 'job': 'Engineer'} |
extra表示把extra这个dict的所有key-value用关键字参数传入到函数的kw参数,kw将获得一个dict,注意kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra。
- 高级特性
- 切片
1 | >>> L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack'] |
取前3个元素,用一行代码就可以完成切片:
1 | >>> L[0:3] |
从索引1开始,取出2个元素出来:
1 | >>> L[1:3] |
类似的,既然Python支持L[-1]取倒数第一个元素,那么它同样支持倒数切片,试试:
1 | >>> L[-2:] |
先创建一个0-99的数列:
1 | >>> L = list(range(100)) |
前10个数,每两个取一个:
1 | >>> L[:10:2] |
所有数,每5个取一个:
1 | >>> L[::5] |
字符串’xxx’也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串:
1 | >>> 'ABCDEFG'[:3] |
- 迭代
默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同时迭代key和value,可以用for k, v in d.items()。
1 | >>> d = {'a': 1, 'b': 2, 'c': 3} |
如何判断一个对象是可迭代对象呢?方法是通过collections模块的Iterable类型判断:
1 | >>> from collections import Iterable |
Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:
1 | >>> for i, value in enumerate(['A', 'B', 'C']): |
上面的for循环里,同时引用了两个变量,在Python里是很常见的,比如下面的代码:
1 | >>> for x, y in [(1, 1), (2, 4), (3, 9)]: |
- 列表生成式
1 | >>> [x * x for x in range(1, 11)] |
写列表生成式时,把要生成的元素x * x放到前面,后面跟for循环,就可以把list创建出来,十分有用,多写几次,很快就可以熟悉这种语法。
for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:
1 | >>> [x * x for x in range(1, 11) if x % 2 == 0] |
还可以使用两层循环,可以生成全排列:
1 | >>> [m + n for m in 'ABC' for n in 'XYZ'] |
例如,列出当前目录下的所有文件和目录名,可以通过一行代码实现:
1 | >>> import os # 导入os模块,模块的概念后面讲到 |
- 生成器
generator是非常强大的工具,在Python中,可以简单地把列表生成式改成generator,也可以通过函数实现复杂逻辑的generator。
要理解generator的工作原理,它是在for循环的过程中不断计算出下一个元素,并在适当的条件结束for循环。对于函数改成的generator来说,遇到return语句或者执行到函数体最后一行语句,就是结束generator的指令,for循环随之结束。
请注意区分普通函数和generator函数,普通函数调用直接返回结果:
1 | >>> r = abs(6) |
generator函数的“调用”实际返回一个generator对象:
1 | >>> g = fib(6) |
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:
1 | >>> L = [x * x for x in range(10)] |
可以通过next()函数获得generator的下一个返回值:
1 | >>> next(g) |
使用for循环
1 | >>> g = (x * x for x in range(10)) |
定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:
1 | def fib(max): |
generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
举个简单的例子,定义一个generator,依次返回数字1,3,5:
1 | def odd(): |
调用该generator时,首先要生成一个generator对象,然后用next()函数不断获得下一个返回值:
1 | >>> o = odd() |
可以看到,odd不是普通函数,而是generator,在执行过程中,遇到yield就中断,下次又继续执行。执行3次yield后,已经没有yield可以执行了,所以,第4次调用next(o)就报错。
回到fib的例子,我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。
用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:
1 | >>> g = fib(6) |
函数式编程
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。
- 高阶函数
变量可以指向函数
求绝对值的函数abs()为例,调用该函数用以下代码:
1 | >>> abs(-10) |
如果把函数本身赋值给变量呢?
1 | >>> f = abs |
结论:函数本身也可以赋值给变量,即:变量可以指向函数。
- 传入函数
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
一个最简单的高阶函数:
1 | def add(x, y, f): |
当我们调用add(-5, 6, abs)时,参数x,y和f分别接收-5,6和abs
- map/reduce
- 我们先看map
map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
举例说明,比如我们有一个函数f(x)=x2,要把这个函数作用在一个list [1, 2, 3, 4, 5, 6, 7, 8, 9]上,就可以用map()实现如下:
1 | f(x) = x * x |
现在,我们用Python代码实现:
1 | >>> def f(x): |
- 再看reduce的用法
reduce把一个函数作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:
1 | reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4) |
比方说对一个序列求和,就可以用reduce实现:
1 | >>> from functools import reduce |
- filter
filter()函数用于过滤序列。
和map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。
例如,在一个list中,删掉偶数,只保留奇数,可以这么写:
1 | def is_odd(n): |
把一个序列中的空字符串删掉,可以这么写:
1 | def not_empty(s): |
filter()的作用是从一个序列中筛出符合条件的元素。由于filter()使用了惰性计算,所以只有在取filter()结果的时候,才会真正筛选并每次返回下一个筛出的元素。
- sorted
sorted()函数就可以对list进行排序:
1 | >>> sorted([36, 5, -12, 9, -21]) |
sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序:
1 | >>> sorted([36, 5, -12, 9, -21], key=abs) |
要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True:
1 | >>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True) |
1 | students = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)] |
- 返回函数
- 函数作为返回值
可以不返回求和的结果,而是返回求和的函数:
1 | def lazy_sum(*args): |
当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:
1 | >>> f = lazy_sum(1, 3, 5, 7, 9) |
调用函数f时,才真正计算求和的结果:
1 | >>> f() |
- 闭包
注意到返回的函数在其定义内部引用了局部变量args,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。
另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。我们来看一个例子:
1 | def count(): |
在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。
你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果是:
1 | >>> f1() |
全部都是9!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9。
返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
1 | def count(): |
再看看结果:
1 | >>> f1, f2, f3 = count() |
缺点是代码较长,可利用lambda函数缩短代码。
- 匿名函数 lambda
以map()函数为例,计算f(x)=x2时,除了定义一个f(x)的函数外,还可以直接传入匿名函数:
1 | >>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])) |
匿名函数lambda x: x * x实际上就是:
1 | def f(x): |
关键字lambda表示匿名函数,冒号前面的x表示函数参数。
匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。
- 装饰器
1 | >>> def now(): |
函数对象有一个__name__属性,可以拿到函数的名字:
1 | >>> now.__name__ |
- 假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:
1 | def log(func): |
观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:
1 | @log |
调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:
1 | >>> now() |
把@log放到now()函数的定义处,相当于执行了语句:
1 | now = log(now) |
- 偏函数
当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。
假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:
1 | def int2(x, base=2): |
这样,我们转换二进制就非常方便了:
1 | >>> int2('1000000') |
functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:
1 | >>> import functools |
模块
- 作用域
在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过_前缀来实现的。
正常的函数和变量名是公开的(public),可以被直接引用,比如:abc,x123,PI等;
类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author__,__name__就是特殊变量,hello模块定义的文档注释也可以用特殊变量__doc__访问,我们自己的变量一般不要用这种变量名;
类似_xxx和__xxx这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc,__abc等;
之所以我们说,private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量。
- 安装第三方模块
一般来说,第三方库都会在Python官方的pypi.python.org网站注册,要安装一个第三方库,必须先知道该库的名称,可以在官网或者pypi上搜索,比如Pillow的名称叫Pillow,因此,安装Pillow的命令就是:
1 | pip install Pillow |
耐心等待下载并安装后,就可以使用Pillow了
- 安装常用模块
推荐直接使用Anaconda,这是一个基于Python的数据处理和科学计算平台,它已经内置了许多非常有用的第三方库,我们装上Anaconda,就相当于把数十个第三方模块自动安装好了,非常简单易用。
面向对象编程
- 类和实例
定义类是通过class关键字:
1 | class Student(object): |
class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的
- 继承和多态
1 | class Animal(object): |
- 获取对象信息
- 判断对象类型,使用type()函数:
1 | >>> type(123) |
返回对应的Class类型。如果我们要在if语句中判断,就需要比较两个变量的type类型是否相同:
1 | >>> type(123)==type(456) |
判断基本数据类型可以直接写int,str等,但如果要判断一个对象是否是函数怎么办?可以使用types模块中定义的常量:
1 | >>> import types |
- 判断class的类型,可以使用isinstance()函数。
我们回顾上次的例子,如果继承关系是:
1 | object -> Animal -> Dog -> Husky |
那么,isinstance()就可以告诉我们,一个对象是否是某种类型。先创建3种类型的对象:
1 | >>> a = Animal() |
- 获得一个对象的所有属性和方法,可以使用dir()函数
它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:
1 | >>> dir('ABC') |
- 配合getattr()、setattr()以及hasattr(),我们可以直接操作一个对象的状态:
1 | >>> class MyObject(object): |
紧接着,可以测试该对象的属性:
1 | >>> hasattr(obj, 'x') # 有属性'x'吗? |
元类
- metaclass
除了使用type()动态创建类以外,要控制类的创建行为,还可以使用metaclass。
metaclass,直译为元类,简单的解释就是:
当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。
但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。
连接起来就是:先定义metaclass,就可以创建类,最后创建实例。
所以,metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。
metaclass是Python面向对象里最难理解,也是最难使用的魔术代码。正常情况下,你不会碰到需要使用metaclass的情况
常用库
机器学习三剑客:Numpy、Pandas、Matplotlib
- Numpy
NumPy是Python语言的一个扩充程序库。支持高级大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库。Numpy内部解除了Python的PIL(全局解释器锁),运算效率极好,是大量机器学习框架的基础库!
Numpy简单创建数组
1 | import numpy as np |
- Numpy查看数组属性
数组元素个数
1 | b.size |
数组形状
1 | b.shape |
数组维度
1 | b.ndim |
数组元素类型
1 | b.dtype |
- 快速创建N维数组的api函数
创建10行10列的数值为浮点1的矩阵
1 | array_one = np.ones([10, 10]) |
创建10行10列的数值为浮点0的矩阵
1 | array_zero = np.zeros([10, 10]) |
从现有的数据创建数组
1 | array(深拷贝) |
- Numpy创建随机数组np.random
均匀分布
1 | np.random.rand(10, 10)创建指定形状(示例为10行10列)的数组(范围在0至1之间) |
正态分布
1 | 给定均值/标准差/维度的正态分布np.random.normal(1.75, 0.1, (2, 3)) |
数组的索引, 切片
1 | # 正态生成4行5列的二维数组 |
改变数组形状
1 | print("reshape函数的使用!") |
- Numpy计算(重要)
条件判断
1 | import numpy as np |
三目运算
1 | import numpy as np |
- 统计运算
指定轴最大值amax(参数1: 数组; 参数2: axis=0/1; 0表示列1表示行)求最大值
1 | stus_score = np.array([[80, 88], [82, 81], [84, 75], [86, 83], [75, 81]]) |
指定轴最小值amin求最小值
1 | stus_score = np.array([[80, 88], [82, 81], [84, 75], [86, 83], [75, 81]]) |
指定轴平均值mean求平均值
1 | stus_score = np.array([[80, 88], [82, 81], [84, 75], [86, 83], [75, 81]]) |
方差std求方差 S^2= ∑(X- p) ^2 / N (X为变量,p为样本均值,N为样本数量)
1 | stus_score = np.array([[80, 88], [82, 81], [84, 75], [86, 83], [75, 81]]) |
- 数组与数的运算
加法
1 | stus_score = np.array([[80, 88], [82, 81], [84, 75], [86, 83], [75, 81]]) |
乘法
1 | stus_score = np.array([[80, 88], [82, 81], [84, 75], [86, 83], [75, 81]]) |
数组间也支持加减乘除运算,但基本用不到
1 | a = np.array([1, 2, 3, 4]) |
- 矩阵运算np.dot()(非常重要)
根据权重计算成绩,计算规则(M行, N列) * (N行, Z列) = (M行, Z列)
矩阵计算总成绩
1 | stus_score = np.array([[80, 88], [82, 81], [84, 75], [86, 83], [75, 81]]) |
矩阵拼接
1 | print("v1为:") |
矩阵水平拼接
1 | print("v1为:") |
==增加维度详见==
https://blog.csdn.net/csdn15698845876/article/details/73380803
1 | import numpy as np |
- Pandas
https://www.jianshu.com/p/7414364992e4
- Matplotlab
https://www.jianshu.com/p/f2782e741a75
实战
Python 代码调试技巧
- 使用 pdb 进行调试
pdb 是 python 自带的一个包,为 python 程序提供了一种交互的源代码调试功能,主要特性包括设置断点、单步调试、进入函数调试、查看当前代码、查看栈片段、动态改变变量的值等。pdb 提供了一些常用的调试命令
1 | 命令 解释 |
问题汇总
- python 读取文件时报错UnicodeDecodeError: ‘gbk’ codec can’t decode byte 0x80 in position 205: illegal multibyte sequence
解决办法1.
1 | FILE_OBJECT= open('order.log','r', encoding='UTF-8') |
解决办法2.
1 | FILE_OBJECT= open('order.log','rb') |