Python的基本语法
如何在Terminal打开Python?
如果你做Java或C++开发,你会在IDE里面完全编写好,在通过编译链接等变成可执行文件,比如Java变成class文件,然后通过JVM语言去运行。但是Python不一样,它是解释型的脚本语言,它的优点是可以做交互式的编程。如果你已经预装Anaconda,打开命令提示符之后,可以输入一个ipython,会进入一个命令提示符界面。C++里面我们需要编写完整的代码,而这里输入一行代码,就可以看到执行结果。
变量定义
// 变量定义:无需写变量类型,直接定义变量
In [2]: a = 12
In [3]: print(a)
12
// 变换类型:直接赋值即可由int - > float,变量可以被重复赋值,而且实际类型也是可以改变的。
In [4]: a = 12.5
In [5]: print(a)
12.5
// type():查看变量实际类型
In [6]: type(a)
Out[6]: float
// bool类型表示
In [8]: a = True
In [9]: type(a)
Out[9]: bool
四则运算
// 写法和正常的没有什么区别
In [10]: a = 12 + 5 * 4
In [11]: a
Out[11]: 32
// 除法:不同之处:整型除以5,在Java或C++里,会截断向下取整,而Python里,换算为浮点数
In [12]: a / 5
Out[12]: 6.4
// 向下取整:Python 两个除号为向下取整
In [13]: a // 5
Out[13]: 6
// 取余数
In [15]: a % 5
Out[15]: 2
// 浮点数:除法、除法取整、取余:Python里面运算规则非常灵活,无论整数、浮点数运算规则都是一样的,即浮点数也会向下取整、取余
In [3]: a = 32.5
In [4]: a / 5
Out[4]: 6.5
In [5]: a // 5
Out[5]: 6.0
In [6]: a % 5
Out[6]: 2.5
布尔运算
// 布尔运算:注意取反用not
In [7]: True
Out[7]: True
In [8]: False
Out[8]: False
In [9]: not False
Out[9]: True
// ‘与’、’或’运算
In [12]: True and False
Out[12]: False
In [13]: True or False
Out[13]: True
// 数字布尔运算:0代表False,非0代表True
In [14]: 2 and 0
Out[14]: 0
In [15]: 2 or 0
Out[15]: 2
// 两个数都为真,会取第二个数
In [16]: 2 and 3
Out[16]: 3
// 复杂布尔运算
In [20]: (2 != 1) and (3 > 2)
Out[20]: True
空表示
// 常量None和Java里的null类似,和C++ 11里面的nullptr 或C++ 11 之前的NULL是一样的。代表空,什么都不是,我可以把它赋值给任何变量,它的类型是NoneType
In [21]: None
In [22]: a = None
In [23]: type(a)
Out[23]: NoneType
判断相等
// 判断数字相等用双等号'==',判断None相等用'is'
In [25]: a = 2
In [26]: a == 2
Out[26]: True
In [27]: a = None
In [28]: a is None
Out[28]: True
变量a为什么可以不断的变换类型?
大家看完上面的代码,一定有一个问题很奇怪,就是在Python里,我变量的类型是可以不断转换的,为什么呢?对于变量a,我可以时而是布尔类型、时而是整型、时而是浮点类型,甚至最后我可以变成一个NoneType,那为什么Python变量可以有这种特性呢?变量a其实它是一个引用,类似于Java里的引用或C++里的指针,a的值存储在另一块内存空间中。
比如:我定义了a = 2,此时 a 的引用指向内存int 2。
当我再次定义a = False的时候,a就指向了bool型的False。
因为在Python里面它是没有栈这样的概念的,在Python里面所有的值它全部都是一个独立的对象。然后我的变量是一个引用或一个指针,变量的作用就是指向我需要指向的那个数据对象。在Python里面,所有的类型都继承自object类型。
isinstance() 函数
Python里有一个变量非常特殊,叫isinstance,它可以判断某一个变量是不是另一个类型的实例。
In [34]: a = 12
In [35]: a
Out[35]: 12
In [36]: isinstance(a, int)
Out[36]: True
// False会被隐式转换为int
In [37]: isinstance(False, int)
Out[37]: True
In [38]: isinstance(12.0, int)
Out[38]: False
In [39]: isinstance(12.0, float)
Out[39]: True
object 类型
同时Python里面有一个基本类型object,你会看到以下的值都是object的实例。
In [40]: isinstance(12.0, object)
Out[40]: True
In [41]: isinstance(1, object)
Out[41]: True
In [42]: isinstance(False, object)
Out[42]: True
Python 对象层次结构
Long是Python 2里面的类型,在Python 3里面就没有了,在Python 3里面Long和整型其实是同一个类型,还有,你会发现Boolean也是整型,这就是为什么刚刚 isinstance(False, int)的时候它是True。大家可以查看网上资料或Python教程了解这些类型。
刚刚我们讲了Python的基本类型,很重要的一点就是Python里面所有的变量都是引用。所有的值都是一个对象。而我的变量只是指向某个对象的引用。
接下来我讲一下Python里面的字符串String。
字符串
字符串定义
有三种方式:单引号、双引号、三个单引号。
In [44]: a = 'dependency'
In [45]: a
Out[45]: 'dependency'
In [46]: a = "dependency"
In [47]: a
Out[47]: 'dependency'
In [48]: a = '''dependency'''
In [49]: a
Out[49]: ‘dependency'
三种定义字符串方式的区别
三个单引号:原始字符串,不会做格式化的。
In [50]: a = '''dependency
...:
...:
...: '''
In [51]: a
Out[51]: 'dependency\n\n\n’
我打了三个换行,它就输出三个换行,保留原始文本信息,不会做转义。这和其它类型的字符串是不一样的。
三个单引号定义字符串的作用:
我们常常用这种字符串写帮助文档、做一些简单注释,或在代码里面嵌入sql语句、HTML代码,都是可以这样做的。
和Java里面一样,可以取字符串,只不过它没有字符char的概念,最小的单位是str,a[0]为取数组第一个字符串,组成一个新的字符串,长度为1.这是需要特别注意的地方,和C++的行为完全不一样的地方。
In [2]: a[0]
Out[2]: ‘d'
// python里没有字符char类型
In [3]: type(a)
Out[3]: str
切片
在Python里面最强大的地方就是中括号,中括号在Python里有个术语叫切片。切片是非常强大的表达方式。
// 字符串截取:取前三个字符串,0、3代表数组索引,区间左闭右开。
In [4]: a[0:3]
Out[4]: ‘dep'
# 取0到倒数第一个字符
// 第一种取法:数完下标直接写
In [6]: a[0:9]
Out[6]: ‘dependenc'
// 第二种取法:求长度
In [7]: len(a)
Out[7]: 10
In [8]: a[0:len(a)]
Out[8]: 'dependency'
In [9]: a[0:len(a)-1]
Out[9]: ‘dependenc'
// 第三种取法:用-1,代表倒数第一个元素
In [10]: a[0:-1]
Out[10]: 'dependenc'
In [11]: a[0:-3]
Out[11]: 'depende'
In [12]: a[-5:-3]
Out[12]: ‘de'
// 直接用负数取元素
In [14]: a[-3]
Out[14]: ‘n
// 冒号后为空,代表取到最后一个元素,冒号前为空,代表从第一个元素开始取。这个其实我们就完成了一个字符串的拷贝。后面讲list的时候会来讲这个问题。
In [18]: a[1:]
Out[18]: 'ependency'
In [16]: a[:3]
Out[16]: 'dep'
In [17]: a[:]
Out[17]: ‘dependency'
之前我们取的字符都是连续的,如果我想跳跃,怎么取?
// 2代表每次索引每次+2,
In [20]: a[0:6]
Out[20]: ‘depend'
In [19]: a[0:6:2]
Out[19]: ‘dpn'
// -1代表取出后,倒序排列
In [23]: a[6:0:-1]
Out[23]: ‘ednepe’
// -2代表倒叙排列且跳一位
In [21]: a[6:0:-2]
Out[21]: ‘enp'
切片语法在我们后面处理Numpy的多维数组的时候,会非常常用。希望大家能熟练掌握切片语法。
字符串拼接
和Java或C++里面基本是一样的。
In [27]: 'abc' + 'bef'
Out[27]: 'abcbef'
In [28]: name = "Lorne"
In [29]: age = 30
In [32]: 'My name is ' + name +' My age is ' + str(age)
Out[32]: 'My name is Lorne My age is 30’
格式化输出
以上的写法非常麻烦,我们可以用格式化字符串,类似于Java、C++ 格式化输出printf。
// %代表格式化字符串,():里面是元组
In [5]: 'My name is %s; my age is %d' % ('Lorne',30)
Out[5]: 'My name is Lorne; my age is 30'
字典表示
现在使用字典表示,好处是可读性更强,我一般会使用这种。
In [12]: 'My name is %(name)s; my age is %(age)d' % {
...: 'name' : 'Lorne',
...: 'age' : 30}
Out[12]: 'My name is Lorne; my age is 30’
新的格式化字符串的方法
以上介绍的是Python 2.6 之前的格式化字符串,在Python 2.6 之后,有一种新的格式化字符串的方法。
In [14]: 'My name is {}, my age is {}' .format('Lorne',30)
Out[14]: 'My name is Lorne, my age is 30’
// 参数可以对调
In [15]: 'My name is {}, my age is {}' .format(30,'Lorne')
Out[15]: 'My name is 30, my age is Lorne'
In [16]: 'My name is {0}, my age is {1}' .format('Lorne',30)
Out[16]: 'My name is Lorne, my age is 30'
In [17]: 'My name is {1}, my age is {0}' .format('Lorne',30)
Out[17]: 'My name is 30, my age is Lorne’
// 参数可以复用
In [19]: 'My name is {1}, my age is {0} {1}' .format('Lorne',30)
Out[19]: 'My name is 30, my age is Lorne 30’
可读性最好的方式
指定name,age,用key = value的方式,这是一种可读性最好的方式,比%的形式更方便;
在Python 3 会倾向于这种方式做格式化,如果你想兼容Python 2 ,还需要用% 的写法。
In [20]: 'My name is {name}, my age is {age}' . format(name = 'Loren',age = 30)
Out[20]: 'My name is Loren, my age is 30’
In [24]: '''My name is {name}, my age is {age}''' . format(age = 30,name = 'Lorn
...: e')
Out[24]: 'My name is Lorne, my age is 30’
除此之外,大括号的写法还支持一些非常复杂的格式化,建议大家去网上查看相关文档,我们甚至可以控制它的显示类型,显示格式。控制数字保留位数。
讲完字符串,该讲其它集合类型了,刚刚的字符串、list都属于集合类型。
集合类型
List 列表
我第二个讲的类型叫List,大家可以理解为Java或C++里面的数组。
// 定义List
In [26]: l = [1,2,3,4,5]
// 空列表
In [27]: l = []
// 取长度,所有的集合类型都可以用len()取长度
In [28]: len(l)
Out[28]: 0
In [29]: l = [1,2,3,4,5]
In [31]: len(l)
Out[31]: 5
增删改查
作为列表可以动态增加、删除元素的,很像C++里面的Vecter以及Java里的ArrayList,它是一个动态数组。
// add element
In [32]: l
Out[32]: [1, 2, 3, 4, 5]
In [33]: l.append(8)
In [34]: l
Out[34]: [1, 2, 3, 4, 5, 8]
// delete element , ‘.pop()’: find last element in list and delete it
In [36]: l
Out[36]: [1, 2, 3, 4, 5, 8]
In [37]: l.pop()
Out[37]: 8
In [38]: l
Out[38]: [1, 2, 3, 4, 5]
// delete element by index.
In [39]: l.pop(1)
Out[39]: 2
In [40]: l
Out[40]: [1, 3, 4, 5]
// insert element : ‘1’:index ,’2’:value
In [48]: l.insert(1,2)
In [49]: l
Out[49]: [1, 2, 3, 4, 5]
// other delete method:del
In [55]: del l[4]
In [56]: l
Out[56]: [1, 2, 3, 4]
// add operation with List
In [57]: a = 10
In [58]: b = 20
In [59]: a + b
Out[59]: 30
In [60]: a = 'hello'
In [61]: b = 'world'
In [62]: a + b
Out[62]: 'helloworld'
In [63]: a = [1,2,3]
In [64]: b = [4,5,6]
// List a and List b 的值都未改变
In [65]: a + b
Out[65]: [1, 2, 3, 4, 5, 6]
// ‘extend()’:insert list b to list a, a is changed.
In [67]: a.extend(b)
In [68]: a
Out[68]: [1, 2, 3, 4, 5, 6]
访问越界及错误捕获
// ‘extend()’:insert list b to list a, a is changed.
In [67]: a.extend(b)
In [68]: a
Out[68]: [1, 2, 3, 4, 5, 6]
// IndexError exception:list index out of boundary ,like as Java or C++.
In [70]: a[5]
Out[70]: 6
In [71]: a[6]
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-71-08730b0063ad> in <module>()
----> 1 a[6]
IndexError: list index out of range
// catch exception: to prevent stack back .
// 我捕捉IndexError异常,并把这个异常命名为e,把异常抓出来做异常处理,和Java或C++里面一样。
In [73]: try:
...: print(a[6])
...: except IndexError as e:
...: print('Error ',e)
...:
Error list index out of range
// note: 在Python里,try Catch 不像C++ or Java,没有大括号,是用缩进indent来表示的。
切片语法
我的列表是一个集合,它是支持我刚刚讲到的分片语法的,和字符串是一样的,刚刚讲的字符串里面的所有切片语法都可以用到我的列表里面来,大家可以自己试一下。
In [1]: l = [1,2,3,4,5,6]
In [2]: l
Out[2]: [1, 2, 3, 4, 5, 6]
// 取前三个元素
In [4]: l[0:3]
Out[4]: [1, 2, 3]
// 可以省略零
In [5]: l[:3]
Out[5]: [1, 2, 3]
In [6]: l[1:]
Out[6]: [2, 3, 4, 5, 6]
In [7]: l[1:-1]
Out[7]: [2, 3, 4, 5]
In [11]: l[1:6:2]
Out[11]: [2, 4, 6]
包含元素的判断
‘in’ 判断某一个元素是否在列表里。
In [12]: 1 in l
Out[12]: True
In [13]: 8 in l
Out[13]: False
Tuple 元组
另外一种集合类型元组Tuple,切片语法同字符串。
In [15]: t = (0,1,2,3,4,5,6)
In [16]: t
Out[16]: (0, 1, 2, 3, 4, 5, 6)
In [19]: t[1:4]
Out[19]: (1, 2, 3)
In [20]: t[1:4:2]
Out[20]: (1, 3)
In [21]: t[4:1:-1]
Out[21]: (4, 3, 2)
In [22]: t[0]
Out[22]: 0
为什么有List,还需要Tuple呢,它俩的区别
Tuple是一个不可变类型,这样的话我可以在很多情况下做一个性能优化。比如C++里面,为了做到性能优化,我可能把变量定义成const,也可能会把成员函数定义成const ,声明常量的作用是为了让编译器对我们的代码进行优化,Tuple也是这样的。 因为他是不可变的,所以可以做很多运行上的性能优化。一般的原则是能用元组就用元组,当数据结构真的需要修改的时候,再用List。字符串可以取抓一个字符,但是却不可以给它赋值,所以在Python里,字符串也是不可变类型。所以我想通过一个字符串构造另外一个字符串,只能通过连接或者格式化来完成。不能修改字符串内部的任何数据。这个字符串的性质和Java非常像,所以学过Java的人很容易理解这个问题。
‘l’:支持变量赋值,‘’t’:’报错。
In [23]: l
Out[23]: [1, 2, 3, 4, 5, 6]
In [24]: l[5] = 7
In [25]: l
Out[25]: [1, 2, 3, 4, 5, 7]
In [26]: t
Out[26]: (0, 1, 2, 3, 4, 5, 6)
In [27]: t[6] = 7
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-27-c8d6a111e744> in <module>()
----> 1 t[6] = 7
TypeError: 'tuple' object does not support item assignment
字符串抓取元素,赋值报错,字符串也是不可变类型。
In [28]: a = 'abc'
In [29]: a
Out[29]: 'abc'
In [30]: a[0]
Out[30]: 'a'
In [33]: a[0]= 'd'
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-33-09aae13f21aa> in <module>()
----> 1 a[0]= 'd'
TypeError: 'str' object does not support item assignment
字典
定义与取值
还有一个集合概念是字典,字典是大括号括起来的东西,字典是键值对,非常类似于我Java或C++里面的map,映射表。
它把我们列表里面的数字下标变成字符串,以字符串作为一个key,以任意一个类型作为一个value。
字典可以嵌套,字典里面可以是列表,同样,列表里可以嵌套元组,就我任何一个类型都是可以嵌套的。
// 定义
In [34]: d = {'name':'Lorne','age':30}
In [35]: d
Out[35]: {'age': 30, 'name': 'Lorne’}
// 取值
In [37]: d['age']
Out[37]: 30
In [38]: d['name']
Out[38]: 'Lorne'
In [39]: d['gender’]
// 取不存在的元素,报错:KeyError
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-39-3ffc93c7900a> in <module>()
----> 1 d['gender']
KeyError: ‘gender'
取key报错与处理
访问字典里不存在的key会报错,这样会很不爽,我要去字典里面的一个值,我还要通过一个异常里做判断,这样就会很麻烦。所以,我可能会这样写:
// 先判断一下
In [40]: 'gender' in d
Out[40]: False
常用写法
一种更好的写法,存在返回一个值,不存在返回None,我们通常用这这种写法。
In [41]: v = d.get('gender')
In [42]: v
In [43]: print(v)
None
In [44]: v = d.get('age')
In [45]: v
Out[45]: 30
如果key不存在,我们可以指定默认值False。
In [46]: v = d.get('gender',False)
In [47]: v
Out[47]: False
这在配置文件中会很有用。比如配置iMac,配置文件一般是key、value的形式,如果端口配置不存在,我们返回22端口,否则,返回端口对应的选项。 所以我们经常在配置文件解析的时候使用这种特性。会非常方便,如果以if的方式写,会非常冗长。
In [51]: port = d.get('port',22)
In [52]: port
Out[52]: 22
获取所有的keys 和values
In [56]: d.keys()
Out[56]: dict_keys(['name', 'age'])
In [57]: d.values()
Out[57]: dict_values(['Lorne', 30])
返回所有项:返回一个列表,列表里面所有项是一个元组,元组里面是key value。
In [59]: d.items()
Out[59]: dict_items([('name', 'Lorne'), ('age', 30)])
Set集合
定义:用大括号,里面只有value,具体操作方式大家可以查看 文档。
特点:无序,value不可重复,重复元素会被剔除掉。
In [60]: s = {'Lorne',30}
In [61]: s
Out[61]: {'Lorne', 30}
In [62]: s = {'Lorne',30,30}
In [63]: s
Out[63]: {'Lorne', 30}
In [64]: s = {30,'Lorne'}
In [65]: s
Out[65]: {'Lorne', 30}
刚刚我们把Python的基础类型、集合类型的基础知识全部都讲一遍 ,接下来讲Python里面的控制结构、函数、面向对象编程。
control 控制结构
先将一下Python里的控制结构,讲控制结构的时候就要将Python里的最重要的特点:它的语句块。
if 语句
没有大括号,和Java、C++不同,结尾用冒号,用缩进作为语句块的标识,也不需要什么结尾,语句块结束就结束了。
缩进好处:这样做的好处是所有代码看起来都是一致的,而C++里面有大括号就是语句块了。
缩进的问题:有的编辑器打制表符的时候是四个空格,而有的编辑器它可能是一个制表符键,就是它会插入制表符字符,他会因编辑器而异。这就导致一个人修改过的代码在另一个人那里可能就不能运行了。可能就出现在代码缩进上,所以如果用Python开发的话,所有人的编辑器设定一定要是一致的。
a = 20
b = 30
if a + b > 40:
print('ok') # Prints 'ok'
print('hello') # Prints 'hello'
print('this is') # Prints 'this is'
elif a + b < 20:
pass
else:
print('else')
pass小技巧:用pass预留语句块,可能还没想好写什么,这是一种常用的技巧,大家可以注意一下。
在Java or C++ ,我们会有Switch,在Python,它认为Switch是一种不好的语句,因为情况比较少的话可以用if,条件比较多,就会用映射表法or跳转表法来处理这种情况, Python是一个脚本语言,你执行Python文件的时候,它就会从头到尾一路执行下来,它没有入口函数main()的概念,和shell脚本,批处理文件很像。
for语句
接下来我们讲一下for,它和C++、Java 不太一样。
如果你学过C++ 11 or Java,你就会发现它很像C++ 11 和Java里面的for each 语句。
for i in [1,2,3,4,5,6]:
print(i) # Prints '1 2 3 4 5 6'
for语句执行逻辑:
每一次在in后面取一个值,赋值给i,再执行后面的语句,这样的话比较特别的地方是:in后面一定要放一个list or Tuple ,后面会讲可迭代的数据结构都可以,当然列表或元组是一个最简单的表现形式,它就会遍历这个列表or元组,依次处理 。
range() 函数
那这就有个问题了,假如我的列表是小于1000,依次写出来就比较麻烦。 所以Python里面提供了一个函数rang(),你可以认为它会返回一个列表,你指定一下列表的起止数字。它是一个左闭右开区间, 它和C++里面的(i = 0; i < 10; i++)是一样的。range()也是一个很灵活的函数,不仅可以设置起始值,还可以 设置中间跳几格,就和切片一样。
for i in range(1,6):
print(i) # Prints '1 2 3 4 5'
'相当于 i = 0; i < 6; i + 2 的循环遍历'
for i in range (1, 6, 2):
print(i) # Prints '1 3 5'
倒叙
'''
倒叙:
i = 6; i > 1; i--
如果不写第三个参数,函数不会执行,一开始就会终止
因为第三个参数默认为1
'''
for i in range(6, 1, -1):
print(i) # Prints '6 5 4 3 2'
字典的遍历
如果我们有个字典,如何遍历呢。
普通的遍历
d = {
'name': 'Lorne',
'age': 30
}
for key in d:
value = d[key]
print(value) # Prints 'Lorne 30'
items() 遍历
这样获取key之后,再取value,就会非常麻烦,大家记不记得刚刚我字典里有个方法叫做items(),它会返回一个键值对的列表,当然严格意义上讲它不是列表,但看上去是返回一个键值对的列表。那我们遍历list的时候,是不是每次就可以取出一个键值对。 这个语法就很像C++ 11 里面for each。
'pair: 是一个键值对的元组,它的第一项是key,第二项是value'
for pair in d.items():
key = pair[0]
value = pair[1]
print(key,value)
# Prints
# 'name Lorne
# age 30'
解包遍历
但这样其实还是很麻烦,这和我们刚刚遍历key,取value没有什么太大差异,那我们有什么简单的方法可以做这件事情呢.我们可以这样写,大家如果学过函数式语言,会知道函数式语言里面有一种语法叫做模式匹配,在Python里叫unpack,解包。
'The output is the same as above'
for pair in d.items():
key, value = pair
print(key,value)
'as like : a = 1, b = 2'
# principle: 左边组成元组(a,b),右边组成元组(1,2),左边匹配右边,而小括号Python会帮我们自动加上,所以我们可以去掉,即写成:1,2
a,b = (1,2)
print(a,b)
# shortcut
for key, value in d.items():
print(key,value)
获取字典的index
# get index for collections
l = ['hello','world','this','is']
index = 0
for i in l:
print(i,index)
index += 1
'''
Prints '
hello 0
world 1
this 2
is 3 '
'''
以上的做法比较原始,Python为我们提供了函数enumerate(),这个函数的作用是把我的列表变成元组的列表,遍历这个列表的时候,每次取出的是一个元组,通过元组的解包,把0赋值index,把值赋值给value。
”’
如同这种写法:获得值得同时获得index
l = [(0,’hello’),(1,’world’),(2,’this’),(3,’is’)]
for index, value in l:
print(index,value)
enumerate(): generate the above list.
”’
for index, value in enumerate(l):
print(index, value)
# Prints '
# 0 hello
# 1 world
# 2 this
# 3 is
# '
同样,我们可以在字典里面用这个函数,这是比较Python的写法,Python里面我们鼓励使用这些函数来简化循环和判断的写法。Python的唯一目的就是让大家少写点代码。
print(d.items())
# Prints 'dict_items([('name', 'Lorne'), ('age', 30)])'
for index, (key, value) in enumerate(d.items()):
print(index, key, value)
# Prints '
# 0 name Lorne
# 1 age 30
# '
while语句不讲了,大家可以自己看一下,和平时用的没有什么太大区别。我们来讲一下函数,Python里面函数有许多不同的地方。
Function 函数
Function define
# keyword:def
# 使用冒号而不是大括号
# 参数的定义和变量的定义一样,没有类型
def add(a,b):
return a + b
print(add(1,2))
多个返回值
在Java or C++ 一个函数只有一个返回值,在Python里,一个函数可以有多个返回值。
'''
写一个列表加法
同时返回两个值:一个是两个列表内容是否相同;一个是列表的连接结果,用逗号分开
'''
def listAdd(la, lb):
isSame = la == lb
result = la + lb
return isSame,result
'call it:get return value,seperated by commas'
isSame, result = listAdd([1,2,3],[4,5,6])
print(isSame,result)
# Prints ' False [1, 2, 3, 4, 5, 6] '
# 获取返回值时,本质上会把在两个返回值拼成元组返回给你,再做一个模式匹配(解包),所以说以下效果同上
# 就是说Python可以通过元组解包来实现返回多个值
results = listAdd([1,2,3],[4,5,6])
print(results) # Prints '(False, [1, 2, 3, 4, 5, 6])'
默认参数
# 如果你学过C++,你就知道,我这边是给b赋了一个默认参数1
def add(a, b = 1):
return a + b
print(add(2, 3)) # Prints '5'
'this is: a = 2,b = 1 ,b is default parameter'
print(add(2)) # Prints '3'
compare ‘is’ and ‘==‘
- == : 代表两者的内容是否完全一致,如果你学过Java,可以类比一下;
- is : 两者是否指向同一块内存空间,即是否是同一个对象;
所以刚刚我们比较某一个东西是否为None的时候,是用‘is’,而不要用‘==’ 。
la = [1,2,3]
lb = [1,2,3]
lc = la
print(la == lb) # Prints 'True'
print(la is lb) # Prints 'False'
print(la is lc) # Prints 'True'
关键字参数
Python还支持一种非常好的函数调用方式,如果你做过Windows里面API开发,就知道里面的参数非常长,以至于每次都要翻MSDN,或依靠代码自动补全。一个一个看参数什么意思,关键是它写出来的代码里面一堆0,一堆null,你看不懂这些参数是干嘛的。,它要写一堆注释。 这个时候就会使你的代码可读性非常低。
那么;Python 想了什么方法呢,Python在调用参数的时候,可以用一种叫做命名传参的方法。比如我现在可以指定我不仅按顺序传参,我还可以指定 a = 5,b = 10,它算出来a + b 就是15.这样的话,我无论参数顺序怎么掉,它都是一样的。我不需要死记我的参数顺序,而且我的代码可读性是非常好的。我们我们平常参数很多的时候;就会使用这种关键字参数调用方法。
print(add(a = 5, b = 10)) # Prints '15'
print(add(b = 10, a = 5)) # Prints '15'
可变参数
可变参数:Python参数另外一个特性。
'需求:输入任意多参数,求和,类似于C++里printf()参数'
'''
*args:会把我传进去的参数变为元组,所以我只需要遍历元组求和即可
Python写这个会比较简单,因为它是动态语言
'''
def mySum(*args):
print(args)
temp = 0
for i in args:
temp += i
return temp
sum = mySum(1,2,4,10,50)
print(sum) # Prints '67'
note
‘*’ 在不同地方代表不同意义
‘*’:在函数定义中,代表我的参数是不定参数
‘*’:在函数调用中,代表将参数传给函数
inArgs = (1,2,4,10,60)
print(mySum(*inArgs)) # Prints '77'
可变关键字参数
可变关键字参数: Python还支持一种非常强大的叫可变关键字参数。
def format(**kwargs):
print(kwargs) # Prints '{'name': 'Lorne', 'age': 30}'
for key, value in kwargs.items():
print(key,value)
format(name = 'Lorne', age = 30)
note
双星号的意思是它会把你所有传入的key=value形式的参数包装成字典,’**’用来表示这个不定参数是一个不定关键字参数,变为类似之前字符串中format()函数的key、value 形式;
kwargs:key word arguments ,是一种命名惯例,你当然可以用任何名字。
'usage: 无参数值,就用默认的,否则,用传入的参数'
def initConf(**kwargs):
host = kwargs.get('host','127.0.0.3')
port = kwargs.get('port', 80)
print(host,port)
initConf(host='127.0.0.1') # Prints '127.0.0.1 80'
'同样可以用,**调用,结果一样'
inArgs = {
'host':'127.0.0.1'
}
initConf(**inArgs) # Prints '127.0.0.1 80'
变量的作用域
变量的作用域问题(很重要的问题)。
x = 20
def setX():
x = 30
print(x)
print(x) # Prints '20'
setX() # Prints '30'
print(x) # Prints '20'
note
在Python里,只有两种作用域,全局作用域、函数作用域,没有块作用域这个概念。
详见下一个用例compare()。
def compare():
a = 30
b = 20
if a > b:
result = 0
else:
result = 50
print(result)
compare() # Prints '0'
note
compare()函数中,我并没有定义result,它会不会报错呢,提示我没有定义result, 其实是不会的,因为Python中,没有块作用域的概念,它会将result提升为整个函数的作用域,也就是你在一个逻辑块里定义变量,它其实相当于你在这个函数里定义变量,那我就可以在函数后面任意一个位置去引用变量。
全局变量的引用
def setGlobalX():
global x
x = 30
'the global instance x is changed'
setGlobalX()
print(x) # Prints '30'
函数嵌套
Python里的函数,还有个更强大的地方,我们可以在一个函数内部去定义一个函数。
函数是一种对象
在Python里面,函数的地位和在C++、Java里面不一样,在Python里,函数是一个对象;
用一种通俗的讲法来讲,它是一等公民。也就是Python 里的函数地位和其他任何变量是一模一样的。
def fun():
return 10
'我可以把函数赋值给任何变量,这使得函数在Python里非常灵活'
f = fun()
print(f) # Prints '10'
f = fun
print(f()) # Prints '10'
闭包
def createPrinter(x):
def printer():
print(x)
return printer
myPrinter = createPrinter(30)
'可以认为它类似C++里的函数指针,因为返回的是函数printer,当然实际上这里面是反回一个函数对象'
print(myPrinter) # Prints '<function createPrinter.<locals>.printer at 0x10cedc730>'
'function object called'
myPrinter() # Prints '30'
createPrinter()函数内部的x会绑定函数外层的x变量,这种行为在函数式编程里我们叫做闭包。
lambda 表达式— 匿名函数
在Python里面,还有一种特殊的函数叫lambda表达式。
def add(a,b):
return a + b
print(add(1,2)) # Prints '3'
大家注意到,如果上面的函数在我这个文件里只用一次,没必要单独定义出一个函数来。那我可以把函数直接定义在语句里面。
lambdaAdd = lambda a ,b : a + b
print(lambdaAdd(1,2)) # Prints '3'
可以看作匿名函数
参数是a,b
冒号之后是函数体:a + b
效果和上面的函数相同,只不过语法更加简洁,不用单独定义一个函数了。
高阶函数
高阶函数:去调用另外一个函数的函数。
def forEach(l, func):
for index,item in enumerate(l):
func(item, index)
def printer(item, index):
print(item,index)
l = ['hello','world']
forEach(l,printer)
# Prints '
# hello 0
# world 1'
在Python里,一些常用的预定义高阶函数。
map()
以下函数意义:取出列表里的每一个元素加1后,再返回一个新的列表。
lMap = map(lambda x:x + 1,[1,2,3])
for i in lMap:
print(i) # Prints '2 3 4'
自定义lambda表达式
def myMap(func, l):
result = []
for item in l:
result.append(func(item))
return result
customMap = myMap(lambda x: x + 1,[1,2,3])
for item in customMap:
print(item) # the result the same as above ,Prints '2 3 4'
filter()
filter:将不满足条件的元素过滤掉,返回满足条件的元素列表。
f = filter(lambda x: x > 5,[1,6,10])
for i in f:
print(i) # Prints '6 10'
自定义filter()
'注意:1.if的判断不用XX == True,2.返回值的缩排要与result的定义对齐'
def myFilter(func,l):
result = []
for item in l:
if func(item) :
result.append(item)
return result
'列表[1,6 10]也可以写成元组(1,6,10),结果一样的'
items = myFilter(lambda x : x > 5,[1, 6, 10])
for i in items:
print(i) # Prints '6 10'
这个高阶函数看起来比较复杂,所以现在Python推崇另外一种表达方法,叫列表推导式,它能实现和上面的表达式相同的效果。
列表推导式
l = [1,2,3]
newList = [ x + 1 for x in l]
print(newList) # Prints '[2,3,4]'
当你看到list里有for的时候,你就知道这是一个列表推导式。
它的作用:遍历l里的元素,将每一个元素取出,调用for前面的表达式,然后返回新的列表。
best experience
:for in 内的变量x要和for前面的表达式的x用同一个变量,否则不能打出正确的结果。
另外一种更复杂的写法。
newList = [x * 2 for x in l if x > 2]
print(newList) # Prints '[6]'
意思
遍历l,取出元素,用if做判断,如果符合条件,那么我们调用for前面的表达式。
作用:取出列表里面大于2的元素,讲它乘以2,将新的列表返回。
意义
高效–这种表达式的性能要优于上面的高阶函数或自定义函数,再往list里面.append()的形式,所以我们一般倾向于这种写法。
面向对象—二叉树绘制
面向对象:我们讲了Python里的基础语法和函数,最后我们看一下Python里的面向对象。我们结合一个二叉树的实例来展示:
tree.py
第一二叉树及进行数据预测
'''
define class:
use keyword 'class'
object: class's supperclass. in python,all classes inherit from the object.
'''
class TreeNode(object):
'''
designed fucntion:
in python,many of internal function are expressed in double-underlined words before and after keywords.
self:as like pointer in Java or C++, it point to created objcet itself.
as each node is a label in my tree. so we added two parameters called label and children.
'''
def __init__(self, label, children = {}):
print('construct tree node')
# define preperty in here, 别在其他地方定义
self.label = label
self.children = children
# state 叶子节点
# 在Python class里,第一个参数要是self,类似C++/Java里的this,只不过在Python里,他认为所有的东西都应该显示的写出来。
# 如果你学过C++实现原理或Java虚拟机,就会知道其实,this都是作为函数引用传进去的,都是在堆栈里面的。 所以Python只是把这个
# 参数暴露出来了而已。
'如何判断是子节点:children的长度等于0'
def isLeaf(self):
return len(self.children) == 0
def getHeight(self):
# 如果是叶子结点,高度为1
if self.isLeaf():
return 1
# 如果是非叶子节点,高度为子树最大高度+1
return 1 + max([child.getHeight() for child in self.children.values()])
def getWidth(self):
# 如果是叶子节点,宽度为1
if self.isLeaf():
return 1
# 如果是非叶子节点,高度为最大宽度
return sum([child.getWidth() for child in self.children.values()])
'''
call class
不同于Java or C++,python里创建类对象不用new,
直接用名字加括号,它返回这个对象的引用
'''
# node = TreeNode() # Prints 'construct tree node',说明我的构造函数被调用
node = TreeNode(label= 'age',children={})
'we can access to label and children preperty in here.so There is no concept of private attributes in tython.'
'''
note
我们在内部定义的变量在外部是可以直接访问的,所以,Python理论上不存在私有属性这个概念,
就是我们外部可以访问这个对象所有属性, 所以,
正常变量我们不能把它保护起来,我们只能和外部约定你不能修改我的内部变量,
你只用我给你提供的接口,我们在Python里,往往用这种约定的方式来做。
'''
print(node.label)
print(node.children)
'''
note
in state,我们需要加self,在调用时,不用加,Python会帮我们自动加上去的,
我们只需要写其他参数就好。
'''
print(node.isLeaf()) # True
# 再定义一个类
class Tree(object):
def __init__(self,rootNode = None):
self.rootNode = rootNode
def getHeight(self):
if self.rootNode is None:
return 0
return self.rootNode.getHeight()
def getWight(self):
if self.rootNode is None:
return 0
return self.rootNode.getWidth()
class DecisionTree(Tree):
def __init__(self,**kwargs):
# call superclass init function
# 新式类鼓励使用这种方法
super(DecisionTree,self).__init__(**kwargs)
# 预测一组数据
def predict(self, items):
result = [self.predictOne(item) for item in items]
return result
# predict a single data
def predictOne(self, item, **kwargs):
# if curentNode is leaf node,it has category label name.
currentNode = kwargs.get('node',self.rootNode)
if currentNode.isLeaf():
return currentNode.label
# if not,need to node name
featureName = currentNode.label
# 如果数据项中没有对应的特征抛出异常
featureValue = item.get(featureName,None)
if featureValue is None:
raise KeyError('''Item don't have the key '%s'.''' %featureName)
# 如果节点没有对应的分支抛出异常
nextNode = currentNode.children.get(featureValue,None)
if nextNode is None:
raise KeyError('''TreeNode don't have the Branch '%s'.''' %featureValue)
# 递归预测
return self.predictOne(item, node = nextNode)
treeplot.py
绘图模块,显示二叉树
import matplotlib.pyplot as plt
# 箭头样式
ARROW_ARGS = dict(arrowstyle = "<-")
# 坐标轴起点
START_AXIS = 0.1
# coordinate axis end
END_AXIS = 0.9
# 坐标轴有效距离
AXIS_DISTANCE = END_AXIS - START_AXIS
# 决策节点样式
# boxstyle:边框样式 ,fc:背景颜色
DECISION_NODE = dict(boxstyle = 'sawtooth', fc = '#cab3f0')
# 叶子节点样式
LEAF_NODE = dict(boxstyle = 'round4', fc = '#c8dcc8')
# 类的作用是画出这颗树
class TreePlot:
def __init__(self):
# 创建子图,1X1,占用第一格
self.ax1 = plt.subplot(1,1,1, frameon=False)
pass
# 绘制决策树
def plotTree(self,tree):
if tree.rootNode is None:
return
# 决策树的起点Y轴
startY = END_AXIS
# 决策树的高度
treeHeight= tree.getHeight()
# 计算每一个节点之间的距离
distanceY = AXIS_DISTANCE
if treeHeight > 1:
distanceY = AXIS_DISTANCE / (tree.getHeight() - 1)
# 决策树的X轴绘制范围
startX= START_AXIS
endX = END_AXIS
# 绘制根节点
self.plotTreeNode(tree.rootNode, startX, endX, startY,distanceY)
plt.show()
# 递归绘制决策树节点
def plotTreeNode(self,treeNode,startX,endX,startY,distanceY, **kwargs):
# 根据是否是叶子节点判断绘制样式
nodeStyle = LEAF_NODE if treeNode.isLeaf() else DECISION_NODE
# 当前节点的坐标
centerPt = ((endX - startX) / 2 + startX,startY)
# 获取父节点的坐标,如果无父节点,说明是父节点本身
parentPt = kwargs.get('parentPt',centerPt)
edgeLabel = kwargs.get('edgeLabel', None)
# 绘制当前节点
self.plotNode(treeNode.label,centerPt,parentPt,nodeStyle)
# 绘制判定属性值
if edgeLabel is not None:
self.plotText(edgeLabel,centerPt,parentPt)
childrenCount = len(treeNode.children)
# 如果只有一个子节点,则绘制在正下方,否则需要根据子节点宽度划分区域
if childrenCount == 1:
distanceX = 0
startX = endX = centerPt[0]
else:
distanceX = endX - startX
startY -= distanceY
# 获取当前节点宽度
totalWith = treeNode.getWidth()
for childEdgeLabel, childNode in treeNode.children.items():
# 根据叶子节点宽度与当前节点宽度划分区域大小
childWith = childNode.getWidth()
endX = startX + distanceX * childWith / totalWith
# 递归绘制子节点
self.plotTreeNode(childNode, startX,endX, startY, distanceY, parentPt = centerPt, edgeLabel = childEdgeLabel)
startX = endX
# 绘制节点
def plotNode(self, nodeTxt, centerPt, parenPt, nodeType):
self.ax1.annotate(nodeTxt,xy = parenPt,xycoords = 'axes fraction',
xytext = centerPt, textcoords = 'axes fraction',
va = 'center',ha = 'center',bbox = nodeType,arrowprops = ARROW_ARGS)
# 绘制文字
def plotText(self, text, centerPt, parentPt):
xMid = (parentPt[0] + centerPt[0]) / 2.0
yMid = (parentPt[1] + centerPt[1]) / 2.0
self.ax1.text(xMid,yMid,text)
main.py
主函数入口:进行二叉树的绘制和数据预测。
# 如何导入另一个模块
# import tree
# 'tree module 里的所有全局变量都是暴露出去的'
# Tree = tree.Tree
# TreeNode = tree.TreeNode
from tree import TreeNode, DecisionTree
from treeplot import TreePlot
# 直接从另一个模块里导入全局变量,它是一种语法糖
# from tree import Tree,TreeNode
def main():
# 我们现在根据子节点和根节点的关系构造这颗树
rootNode = TreeNode(label='age', children={
'<30' : TreeNode(label='beautiful',children={
'yes': TreeNode(label='temperament',children={
'good':TreeNode(label='accept'),
'bad': TreeNode(label='deny')
}),
'no': TreeNode(label='deny')
}),
'>=30':TreeNode(label= 'deny')
})
tree = DecisionTree(rootNode = rootNode)
treePlot = TreePlot()
treePlot.plotTree(tree)
# 测试数据
testData = [{
'age':'>=30'
},{
'age': '<30',
'beautiful':'yes',
'temperament':'good'
},{
'age': '<30',
'beautiful': 'yes',
'temperament': 'bad'
},{
'age': '<30',
'beautiful': 'no'
}]
# 预测结果
result = tree.predict(testData)
print(result)
# 如果模块名称是__main__
# 则执行主函数
if __name__ == '__main__':
main()
程序运行结果(二叉树绘制图):