Python基础01 Hello World!
简单的‘Hello World!’
1. 直接运行python
假设你已经安装好了python, 那么在Linux命令行输入: $python
将直接进入python。然后在命令行提示符>>>后面输入: >>>print 'Hello World!' 可以看到,python随后在屏幕上输出:
Hello World!
print是一个常用的python关键字(keyword),其功能就是输出。
(在Python 3.x中,print的语法会有所变化,作为一个函数使用, 所以上面应写成print('Hello World!'),以此类推 )
2. 写一段小程序
另一个使用Python的方法,用文本编辑器写一个.py结尾的文件,比如说hello.py 在hello.py中写入如下,并保存:
print 'Hello World!'
退出文本编辑器,然后在命令行输入: $python hello.py
来运行hello.py。可以看到python随后输出
Hello World!
总结: print
命令行模式: 运行python,在命令行输入命令并执行。 程序模式: 写一段python程序并运行。
Python基础02 基本数据类型
简单的数据类型以及赋值 1)变量不需要声明
Python的变量不需要声明,你可以直接输入: >>>a = 10
那么你的内存里就有了一个变量a, 它的值是10,它的类型是integer (整数)。 在此之前你不需要做什么特别的声明,而数据类型是Python自动决定的。 >>>print a
>>>print type(a) 那么会有如下输出:
10
这里,我们学到一个内置函数type(), 用以查询变量的类型。 2)回收变量名 如果你想让a存储不同的数据,你不需要删除原有变量就可以直接赋值。 >>>a = 1.3 >>>print a,type(a) 会有如下输出 1.3 我们看到print的另一个用法,也就是print后跟多个输出,以逗号分隔。 3)基本数据类型 a=10 # int 整数 a=1.3 # float 浮点数 a=True # 真值 (True/False) a='Hello!' # 字符串 以上是最常用的数据类型,对于字符串来说,也可以用双引号。 (此外还有分数,字符,复数等其他数据类型,有兴趣的可以学习一下) 总结: 变量不需要声明,不需要删除,可以直接回收适用。 type(): 查询数据类型 整数,浮点数,真值,字符串 Python基础03 序列 数据类型:sequence (序列) 1. sequence(序列)是一组有顺序的元素的集合 (严格的说,是对象的集合,但鉴于我们还没有引入“对象”概念,暂时说元素) 序列可以包含一个或多个元素,也可以是一个没有任何元素的空序列 元素可以是我们之前所说的基本数据类型,可以是另一个序列,还可以是我们以后介绍的其他对象。 序列有两种:tuple(定值表; 也有翻译为元组) 和 list (表) >>>s1 = (2, 1.3, 'love', 5.6, 9, 12, False) # s1是一个tuple >>>s2 = [True, 5, 'smile'] # s2是一个list >>>print s1,type(s1) >>>print s2,type(s2) tuple和list的主要区别在于,一旦建立,tuple的各个元素不可再变更,而list的各个元素可以再变更。 一个序列作为另一个序列的元素 >>>s3 = [1,[3,4,5]] 空序列 >>>s4 = [] 2. 序列元素的引用: 序列元素的下标从0开始: >>>print s1[0] >>>print s2[2] >>>print s3[1][2] 由于list的元素可变更,你可以对list的某个元素赋值: >>>s2[1] = 3.0 >>>print s2 如果你对tuple做这样的操作,会得到错误提示。 所以,可以看到,序列的引用通过s[ 范围引用: 基本样式[下限:上限:步长] >>>print s1[:5] # 从开始到下标4 (下标5的元素 不包括在内) >>>print s1[2:] # 从下标2到最后 >>>print s1[0:5:2] # 从下标0到下标4 (下标5不包括在内),每隔2取一个元素 (下标为0,2,4的元素) >>>print s1[2:0:-1] # 从下标2到下标1 从上面可以看到,在范围引用的时候,如果写明上限,那么这个上限本身不包括在内。 尾部元素引用 >>>print s1[-1] # 序列最后一个元素 >>>print s1[-3] # 序列倒数第三个元素 同样,如果s1[0:-1], 那么最后一个元素不会被引用 (再一次,不包括上限元素本身) 4) 字符串是一种tuple 因此也就可以做tuple可以进行的操作 >>>str = 'abcdef' >>>print str[2:4] 总结: tuple元素不可变,list元素可变 序列的引用 s[2], s[1:8:2] 字符串是一种tuple Python基础04 运算 Python的运算符和其他语言类似 (我们暂时只了解这些运算符的基本用法,方便我们展开后面的内容,高级应用暂时不介绍) 1. 数学运算 >>>print 1+9 # 加法 >>>print 1.3-4 # 减法 >>>print 3*5 # 乘法 >>>print 4.5/1.5 # 除法 >>>print 3**2 # 乘方 >>>print 10%3 # 求余数 2. 判断 判断是真还是假,返回True/False >>>print 5==6 # =, 相等 >>>print 8.0!=8.0 # !=, 不等 >>>print 3<3, 3<=3 # <, 小于; <=, 小于等于 >>>print 4>5, 4>=0 # >, 大于; >=, 大于等于 >>>print 5 in [1,3,5] # 5是list [1,3,5]的一个元素 (还有is, is not等, 暂时不深入) 3. 逻辑运算 True/False之间的运算 >>>print True and True, True and False # and, “与”运算, 两者都为真才是真 >>>print True or False # or, \"或\"运算, 其中之一为真即为真 >>>print not True # not, “非”运算, 取反 可以和上一部分结合做一些练习,比如: >>>print 5==6 or 3>=3 总结: 数学 +, -, *, /, **, % 判断 ==, !=, >, >=, <, <=, in 逻辑 and, or, not Python基础05 缩进和选择 python最具特色的就是用缩进来写模块。我们下面以if选择结构来举例。 先看C语言的表达方式(注意,这是C,不是Python!) if ( i > 0 ) { x = 1; y = 2; } 这个语句是说,如果i>1的话,我们将进行括号中所包括的两个赋值操作。 括号中包含的就是块操作,它表明了其中的语句隶属于 if 在python中,同样的目的,这段话是这样的 if i > 0: x = 1 y = 2 在python中, 去除了i > 0周围的括号,去除了每个语句句尾的分号,还去除了表示块的花括号。 多出来了if ...之后的:(冒号), 还有就是x = 1 和 y =2前面有四个空格的缩进。通过这些缩进,python可以识别这两个语句是隶属于if的。 (python这样设计的理由是:程序会看起来很好看) 我们写一个完整的程序,命名为ifDemo.py i = 1 x = 1 if i > 0: x = x+1 print x $python ifDemo.py # 运行 这个程序在顺序运行到if的时候,检测到真值(True),执行x = x+1, 在此之后,print x语句没有缩进,那么就是if之外。 如果将第一句改成i = -1,那么if遇到假值 (False), 由于x = x+1隶属于if, 这一句就跳过。 print x没有缩进,所以是if之外,不会跳过,继续执行。 这种以四个空格的缩进来表示隶属关系的书写方式,我们以后还会经常看到。Python很强调程序的可读性,这种强制的缩进要求实际上是在帮程序员写出整洁的程序。 复杂一些的选择的例子: i = 1 if i > 0: print 'positive i' i = i + 1 elif i == 0: print 'i is 0' i = i * 10 else: print 'negative i' i = i - 1 print 'new i:',i 这里有三个块,分别以if, elif, else引领。 python顺序检测所跟随的条件,如果发现为假,那么跳过后面紧跟的块,检测下一个条件; 如果发现为 真,那么执行后面紧跟的块,跳过剩下的块 (else等同于elif True) 整个if可以做为一句语句放在另一个if语句的块中 i = 5 if i > 1: print 'i bigger than 1' print 'good' if i > 2: print 'i bigger than 2' print 'even better' 我们可以看到, if i > 2 后面的块相对于该if缩进了四个空格,以表明其隶属于该if 总结: if语句之后的冒号 以四个空格的缩进来表示隶属关系, python中不能随意缩进 if <条件1>: statement elif <条件2>: statement elif <条件3>: statement else: statement Python基础06 循环 从上一讲的选择结构,我们已经看到了如何用缩进来表示隶属关系。循环也会用到类似的表示方法。 1. for 循环 for循环需要预先设定好循环的次数(n),然后执行隶属于for的语句n次。 基本构造是 for 元素 in 序列: statement 举例来说,我们编辑一个叫forDemo.py的文件 for a in [3,4.4,'life']: print a 这个循环就是每次从表[3,4.4,'life'] 中取出一个元素(回忆:表是一种序列),然后将这个元素赋值给a,之后执行隶属于for的操作(print)。 介绍一个新的python函数range(),来帮助你建立表。 idx = range(5) print idx 可以看到idx是[0,1,2,3,4] 这个函数的功能是新建一个表。这个表的元素都是整数,从0开始,下一个元素比前一个大1, 直到函数中所写的上限 (不包括该上限本身) (关于range(),还有丰富用法,有兴趣可以查阅, python 3中, range()用法有变化,见评论区) 举例 for a in range(10): print a**2 2. while循环 while的用法是 while 条件: statement while会不停地循环执行隶属于它的语句,直到条件为假(False) 举例 while i < 10: print i i = i + 1 3. 中断循环 (定义一个环的说法。循环是相同的一组操作重复多次,我们把其中的一组操作叫做一环) continue # 在同一循环的某一环,如果遇到continue, 那么跳过这一环,进行下一次环的操作 break # 停止执行整个循环 for i in range(10): if i == 2: continue print i 当循环执行到i = 2的时候,if条件成立,触发continue, 跳过本环(不执行print),继续进行下一环(i = 3) for i in range(10): if i == 2: break print i 当循环执行到i = 2的时候,if条件成立,触发break, 循环停止执行。 总结 range() for 元素 in 序列: while 条件: continue break Python基础07 函数 函数最重要的目的是方便我们重复使用相同的一段程序。 将一些操作隶属于一个函数,以后你想实现相同的操作的时候,只用调用函数名就可以,而不需要重复敲所有的语句。 1. 函数的定义 首先,我们要定义一个函数, 以说明这个函数的功能。 def square_sum(a,b): c = a**2 + b**2 return c 这个函数的功能是求两个数的平方和。 首先,def,这个关键字通知python:我在定义一个函数。square_sum是函数名。 括号中的a, b是函数的参数,是对函数的输入。参数可以有多个,也可以完全没有(但括号要保留)。 我们已经在循环和选择中见过冒号和缩进来表示的隶属关系。 c = a**2 + b**2 # 这一句是函数内部进行的运算 return c # 返回c的值,也就是输出的功能。Python的函数允许不返回值,也就是不用return。 return可以返回多个值,以逗号分隔。相当于返回一个tuple(定值表)。 return a,b,c # 相当于 return (a,b,c) 在Python中,当程序执行到return的时候,程序将停止执行函数内余下的语句。return并不是必须的,当没有return, 或者return后面没有返回值时,函数将自动返回None。None是Python中的一个特别的数据类型,用来表示什么都没有,相当于C中的NULL。None多用于关键字参数传递的默认值。 2. 函数的调用和参数传递 定义过函数后,就可以在后面程序中使用这一函数 print square_sum(3,4) Python通过位置,知道3对应的是函数定义中的第一个参数a, 4对应第二个参数b,然后把参数传递给函数square_sum。 (Python有丰富的参数传递方式,还有关键字传递、表传递、字典传递等,基础教程将只涉及位置传递) 函数经过运算,返回值25, 这个25被print打印出来。 我们再看下面两个例子 a = 1 def change_integer(a): a = a + 1 return a print change_integer(a) print a #===(Python中 \"#\" 后面跟的内容是注释,不执行 ) b = [1,2,3] def change_list(b): b[0] = b[0] + 1 return b print change_list(b) print b 第一个例子,我们将一个整数变量传递给函数,函数对它进行操作,但原整数变量a不发生变化。 第二个例子,我们将一个表传递给函数,函数进行操作,原来的表b发生变化。 对于基本数据类型的变量,变量传递给函数后,函数会在内存中复制一个新的变量,从而不影响原来的变量。(我们称此为值传递) 但是对于表来说,表传递给函数的是一个指针,指针指向序列在内存中的位置,在函数中对表的操作将在原有内存中进行,从而影响原有变量。 (我们称此为指针传递) 总结: def function_name(a,b,c): statement return something # return不是必须的 函数的目的: 提高程序的重复可用性。 return None 通过位置,传递参数。 基本数据类型的参数:值传递 表作为参数:指针传递 练习: 写一个判断闰年的函数,参数为年、月、日。若是是闰年,返回True Python基础08 面向对象的基本概念 (面向对象并不难,不要被“面向对象”吓跑) Python中通过使用类(class)和对象(object)来实现面向对象(object-oriented programming,简称OOP)的编程。 面向对象编程的最主要目的是提高程序的重复使用性,这和函数的目的相类似。 我们这么早切入面向对象编程的原因是,Python的整个概念是基于对象的。了解OOP对于我们深入了解Python很关键。 下面是我对面向对象的理解。 1. 类是属性相近的对象的归类 在人类认知中,会根据属性相近把东西归类,并且给类别命名。比如说,鸟类的共同属性是有羽毛,通过产卵生育后代。任何一只特别的鸟都在鸟类的原型基础上的。 面向对象就是模拟了以上人类认知过程。在Python语言,为了听起来酷,我们把上面说的“东西”称为对象(object)。 先定义鸟类 class Bird(object): have_feather = True way_of_reproduction = 'egg' 我们定义了一个类别(class),就是鸟(Bird)。在隶属于这个类比的语句块中,我们定义了两个变量,一个是有羽毛(have_feather),一个是生殖方式(way_of_reproduction),这两个变量对应我们刚才说的属性(attribute)。我们暂时先不说明括号以及其中的内容,记为问题1。 假设我养了一只小鸡,叫summer。它是个对象,属于鸟类。使用前面定义的类。 summer = Bird() print summer.way_of_reproduction 通过第一句创建对象,并说明summer是类别鸟中的一个对象,summer就有了鸟的类属性,对属性的引用是通过 对象.属性(object.attribute) 的形式实现的。 (可怜的summer,你就是个有毛产蛋的东西,好不精致) 2. 属性可以是变量,也可以是动作(方法)。 在人类日常认知中,我们在通过属性识别类别的时候,有时候会根据这个东西能做什么事情来区分类别。比如说,鸟会移动 (这样就和房屋的类别区分开了)。而这些动作又会带来一定的结果,通过移动会带来位置的变化。 为了酷起见,我们叫这样的一些属性为方法(method)。Python中通过在类的内部定义函数,来说明方法。 class Bird(object): have_feather = True way_of_reproduction = 'egg' def move(self, dx, dy): position = [0,0] position[0] = position[0] + dx position[1] = position[1] + dy return position summer = Bird() print 'after move:',summer.move(5,8) 我们重新定义了鸟这个类别。 鸟新增一个方法属性,就是移动(函数move)。(我承认这个方法很傻,你可以在看过下一讲之后定义个有趣些的方法) (它的参数中有一个self,它是为了方便我们引用对象自身。方法的第一个参数必须是self,无论是否用到。有关self的内容会在下一讲展开) 另外两个参数,dx, dy表示在x、y两个方向移动的距离。move方法会最终返回运算过的position。 在最后调用move方法的时候,我们只传递了dx和dy两个参数,不需要传递self参数(因为self只是为了内部使用)。 (我的summer现在可以跑一下了) 3. 类别本身还可以进一步细分成子类 比如说,鸟类可以进一步分成鸡,大雁,黄鹂。 在OOP中,我们通过继承(inheritance)来表达上述概念。 class Chicken(Bird): way_of_move = 'walk' possible_in_KFC = True class Oriole(Bird): way_of_move = 'fly' possible_in_KFC = False summer = Chicken() print summer.have_feather print summer.move(5,8) 我们新定义的鸡(Chicken)类的,新增加了两个属性,移动方式(way_of_move)和可能在KFC找到(possible_in_KFC) 在类定义时,括号里改为了Bird,用来说明,Chicken是属于鸟类(Bird)的一个子类(酷点的说法,Chicken继承自Bird),而自然而然,Bird就是Chicken的父类。通过这个说明,Python就知道,Chicken具有Bird的所有属性。我们可以看到,尽管我只声明了summer是鸡类,它依然具有鸟类的属性(无论是变量属性have_feather还是方法属性move) 另外定义黄鹂(Oriole)类,同样继承自鸟类。这样,我们在有一个属于黄鹂的对象时,也会自动拥有鸟类的属性。 通过继承制度,我们可以避免程序中的重复信息和重复语句。如果我们分别定义两个类,而不继承自鸟类,那么我们就必须把鸟类的属性分别敲到鸡类和黄鹂类的定义中,累啊。 (回到问题1, 括号中的object,当括号中为object时,说明这个类没有父类(到头了)) 所以说,面向对象提高了程序的可重复使用性。 我们可以看到,面向对象实际上基于人类认知时的习惯,将各种各样的东西分类,从而了解世界。我们从祖先开始可能已经练习了这个认知过程有几百万年,所以面向对象是很符合人类思维习惯的编程方法。所谓面向过程(也就是执行完一个语句再执行下一个)实际上是机器思维。通过面向对象的编程,我们实际上是更贴近我们自然的思维方式,也更方便和其他人交流我们程序里所包含的想法,甚至于那个人并不是程序员。 总结: 将东西根据属性归类 ( 将object归为class ) 方法是一种属性,表示动作 用继承来说明父类-子类关系。子类自动具有父类的所有属性。 self代表了根据该类定义而创建的对象。 定义类: class class_name(parent_class): a = ... b = ... def method1(): ... def method2(): ... 建立对一个对象: 对象名 = 类名() 引用对象的属性: object.attribute Python基础09 面向对象的进一步拓展 上一讲我们熟悉了对象和类的基本概念。这一讲我们将进一步拓展,以便我们真正能实际运用对象和类。 1. 在方法内调用类属性(变量以及其它方法): 上一讲我们已经提到,在定义方法时,必须有self这一参数,这个参数指的是对象。由于对象拥有类的所有性质,那么我们就可以在方法内部通过self来调用类的其它属性。 class Human(object): laugh = 'hahahaha' def show_laugh(self): print self.laugh def laugh_100th(self): for i in range(100): self.show_laugh() li_lei = Human() # 李雷 li_lei.laugh_100th() 我们这里有一个变量属性laugh,在方法show_laugh()中通过self.laugh使用该属性的值。方法show_laugh则在laugh_100th中通过self.show_laugh()被调用。 (通过对象来修改类属性是危险的,这样可能会影响根据这个类定义的所有对象的这一属性!!) 2. __init__()方法 __init__()是一个特殊方法(special method)。Python里会有一些特殊方法,Python会以特别的方式处理它们。特殊方法的名字的特点是前后都有两个下划线。 __init__()方法的特殊在于,如果你在类中定义了这个方法,一旦你根据这个类建立对象,Python就会自动调用这个方法(这个过程也叫初始化)。(在上一讲中,我们手动调用了move()方法) class happyBird(Bird): def __init__(self,more_words): print 'We are happy birds.',more_words summer = happyBird('Happy,Happy!') (Bird类的定义见上一讲) 屏幕上打印出: We are happy birds.Happy,Happy! 我们看到,尽管我们只是创建了summer对象,但__init__()方法被自动调用了。最后一行的语句(summer = happyBird...)先创建了对象,然后执行: summer.__init__(more_words) 'Happy,Happy!' 被传递给了__init__()的参数more_words 3. 对象的性质 上一讲我们讲了变量属性和方法属性。要注意,这些属性是类的属性。所有属于一个类的对象都会共享这些属性。比如说,鸟都有羽毛,鸡都不会飞。 在一些情况下,我们需要用到对象的性质。比如说,人是一个类别,我们知道,性别是人类的一个性质,但并不是所有的人类都是男性或者所有的人类都是女性。这个性质的值会随着对象的不同而不同。(李雷是人类的一个对象,性别是男;韩美美也是人类的一个对象,性别是女)。 从上一讲中,我们已经知道了,当定义类的方法时,必须要传递一个self的参数。这个参数指代的就是类的一个对象。当然,这是一个很模糊的一个概念。但一旦我们用类来新建一个对象(比如说我们下面例子中的li_lei), 那么li_lei就是self所代表的东西。我们已经知道了,li_lei会拥有Human类的属性。进一步,我们通过赋值给self.attribute,给li_lei这一对象增加一些性质(比如说性别)。 由于self强制传递给各个方法,方法可以通过引用self.attribute很方便地查询到这些性质,并进行进一步的操作。 这样,我们在类的属性统一的基础上,又给每个对象增添了各自特色的性质,从而能描述多样的世界。 class Human(object): def __init__(self, input_gender): self.gender = input_gender def printGender(self): print self.gender li_lei = Human('male') # 这里,'male'作为参数传递给__init__()方法的input_gender变量。 print li_lei.gender li_lei.printGender() 首先,在初始化中,将参数input_gender赋值给对象li_lei的性质gender。(上一讲,我们已经提到,self指示的是对象, 也就是li_lei) 我们发现,li_lei拥有了属性gender。在类human的定义中,并没有这样一个变量属性。Python是在建立了li_lei这一对象之后,专门为li_lei建立的属性。我们称此为对象的性质。(也有人以类属性,对象属性来区分)。 对象的性质也可以被其它方法调用,正如我们在printGender方法中所看到的那样。 总结: 通过self调用类属性 __init__(): 在建立对象时自动执行 类属性和对象的性质的区别 Python基础10 反过头来看看 我们已经从最初的“Hello World”,走到前两讲的面向对象,是该回过头来看看,在我们的快速教程中,是否遗漏些什么。 我们之前提到一句话,\"Everything is Object\". 那么我们就深入体验一下这句话。 首先,我们要介绍两个内置函数,dir()和help() dir()用来查询一个类或者对象所包含的属性(变量属性和方法属性)。你可以尝试一下 >>>print dir(list) help()用来查询的说明文档。你可以尝试一下 >>>print help(list) (list是Python内置的一个类,对应于我们之前讲解过的表) 1. list是一个类 在上面以及看到,表是Python已经定义好的一个类。当我们新建一个表时,比如: >>>nl = [1,2,5,3,5] 实际上,nl是类list的一个对象。 我们先实验一些list的方法 >>>print nl.count(5) # 计数,看总共有多少个5 >>>print nl.index(3) # 查询 nl 的第一个3的下标 >>>nl.append(6) # 在 nl 的最后增添一个新元素6 >>>nl.sort() # 对nl的元素排序 >>>print nl.pop() # 从nl中去除最后一个元素,并将该元素返回。 >>>nl.remove(2) # 从nl中去除第一个2 >>>nl.insert(0,9) # 在下标为0的位置插入9 列举了这么多,就是让大家对list作为一个类有一个概念。 (我在课程的最后会给出一个常用方法的附录,有兴趣的可以现在就查查看,网上有无比丰富的资源) 2. 通过list了解:运算符是特殊方法 当你使用dir(list)的时候,能看到一个属性,是__add__。从形式上看是特殊方法(下划线,下划线),那么它特殊在哪里呢? 这个方法定义\"+\"运算符对于list的意义,也就是,当你将一个list的对象和另一个list对象相加时所会进行的操作。 >>>print [1,2,3] + [5,6,9] 运算符,比如+, -, >, <, 以及下标引用[start:end]等等,从根本上都是定义在类内部的方法。 我们先尝试一下 >>>print [1,2,3] - [3,4] 会有错误信息,说明该运算符“-”没有定义。 现在我们继承list类,添加对\"-\"的定义 class superList(list): def __sub__(self, b): a = self[:] # 这里,self是supeList的对象。由于superList继承于list,它可以利用和list[:]相同的引用方法来表示整个对象。 b = b[:] while len(b) > 0: element_b = b.pop() if element_b in a: a.remove(element_b) return a print superList([1,2,3]) - superList([3,4]) (说明:内置函数len()用来返回list所包含的元素的总数) 内置函数__sub__()定义了“-”的操作:从第一个表中去掉第二个表中出现的元素。 (教程最后也会给出一个特殊方法的清单) (如果__sub__()已经在父类中定义,你又在子类中定义了,那么子类的对象会参考子类的定义,而不会载入父类的定义。任何其他的属性也是这样。) 定义运算符对于复杂的对象非常有用。举例来说,人类有多个属性,比如姓名,年龄和身高。我们可以把人类的比较(>, <, =)定义成只看年龄。这样就可以根据自己的目的,将原本不存在的运算增加在对象上了。 3. 现在,你大概已经对Python已经有了一个基本概念。这也是这个快速教程的基础部分的目的所在。你很可能跃跃欲试,想写一些程序练习一下。相信这样做会对你很有好处。但有一点请注意。Python的强大很大一部分原因在于,它提供有很多已经写好的,可以现成用的对象。我们已经看到了内置的比如说list,还有tuple等等。它们用起来很方便。在Python的标准库里,还有大量可以用于操作系统互动,Internet开发,多线程,文本处理的对象。而在所有的这些的这些的基础上,又有很多外部的库包,定义了更丰富的对象,比如numpy, tkinter, django等用于科学计算,GUI开发,web开发的库,定义了各种各样的对象。对于一般用户来说,使用这些库,要比自己去从头开始容易得多(所谓巨人的肩膀上)。 我会尽量在后面涉及各个库。欢迎你的继续关注。 欢迎你,来到Python的世界。 总结: len() dir() help() 我们之前所说的数据结构list(表)实际上是一个对象,它有多个属性。 运算符是方法 站在巨人的肩膀上 Python进阶 Python进阶01 词典 通过我们的基础教程,我们已经对Python建立了基本概念,也对对象和类有一个相对明确的认识。 我们的进阶教程就是对基础教程的进一步拓展,进一步了解Python的细节。希望在进阶教程之后,你可以对Python的基本语法有一个相对全面的认识。 之前我们说了,表是Python里的一个类。一个特定的表,比如说nl = [1,3,8],就是这个类的一个对象。我们可以调用这个对象的一些方法,比如 nl.append(15)。 现在,我们要介绍一个新的类,就是词典 (dictionary)。与表相类似,词典也可以储存多个元素。这种可以用来储存多个元素的对象统称为容器(container)。 1. 基本概念 常见的创建词典的方法: >>>dic = {'tom':11, 'sam':57,'lily':100} >>>print type(dic) 词典和表类似的地方,是包含有多个元素,每个元素以逗号分隔。但词典的元素包含有两部分,键和值,常见的是以字符串来表示键,也可以使用数字或者真值来表示键(不可变的对象可以作为键)。值可以是任意对象。键和值两者一一对应。 (实际上,表的元素也可以是任意对象) 比如上面的例子中,‘tom’对应11,'sam对应57,'lily'对应100 与表不同的是,词典的元素没有顺序。你不能通过下标引用元素。词典是通过键来引用。 >>>print dic['tom'] >>>dic['tom'] = 30 >>>print dic 可以构建一个新的空的词典: >>>dic = {} >>>print dic 在词典中增添一个新元素的方法: >>>dic['lilei'] = 99 >>>print dic (引用一个新的键,赋予对应的值) 2. 对dictionary的元素进行循环调用: dic = {'lilei': 90, 'lily': 100, 'sam': 57, 'tom': 90} for key in dic: print dic[key] 可以看到,在循环中,dict的一个键会提取出来赋予给key变量。 通过print的结果,我们可以再次确认,dic中的元素是没有顺序的。 3. 词典的其它常用方法 >>>print dic.keys() # 返回dic所有的键 >>>print dic.values() # 返回dic所有的值 >>>print dic.items() # 返回dic所有的元素(键值对) >>>dic.clear() # 清空dic,dict变为{} 另外有一个很常用的用法: >>>del dic['tom'] # 删除 dic 的‘tom’元素 del是Python中保留的关键字,用于删除对象。 与表类似,你可以用len()查询词典中的元素总数。 >>>print(len(dic)) 总结: 词典的每个元素是键值对。元素没有顺序。 dic = {'tom':11, 'sam':57,'lily':100} dic['tom'] = 99 for key in dic: ... del, len() Python进阶02 文本文件的输入输出 Python具有基本的文本文件读写功能。Python的标准库提供有更丰富的读写功能。 文本文件的读写主要通过open()所构建的文件对象来实现。 1. 打开文件,创建文件对象。 f = open(文件名,模式) 最常用的模式有: \"r\" # 只读 “w” # 写入 2. 文件对象的方法: 读取方法: content = f.read(N) # 读取N bytes的数据 content = f.readline() # 读取一行 content = f.readlines() # 读取所有行,储存在表中,每个元素是一行。 写入方法: f.write('I like apple') # 将'I like apple'写入文件 关闭文件: f.close() 3. 循环读入文件: (该方法已经从Python 3中删除,请尽量避免使用) for line in file(文件名): print line 利用file()函数,我们创建了一个循环对象。在循环中,文件的每一行依次被读取,赋予给line变量。 总结: f = open(name, \"r\") line = f.readline() f.write('abc') f.close() Python进阶03 模块 我们之前看到了函数和对象。从本质上来说,它们都是为了更好的组织已经有的程序,以方便重复利用。 模块(module)也是为了同样的目的。在Python中,一个.py文件就构成一个模块。通过模块,你可以调用其它文件中的程序。 1. 引入(import)和使用模块 我们先写一个first.py文件,内容如下: def laugh(): print 'HaHaHaHa' 再写一个second.py import first for i in range(10): first.laugh() 在second.py中,我们并没有定义laugh函数,但通过从first中引入(import),我们就可以直接使用first.py中的laugh函数了。 从上面可以看到,引入模块后,我们可以通过 模块.对象 的方式来调用所想要使用的对象。上面例子中,first为引入的模块,laugh()是我们所引入的对象。 此外,还有其它的引入方式, import a as b, from a import *, 都是处于方便书写的原因,本质上没有差别。 2. 搜索路径 Python会在以下路径中搜索它想要寻找的模块: 1. 程序所在的文件夹 2. 标准库的安装路径 3. 操作系统环境变量PYTHONPATH所包含的路径 如果你有自定义的模块,或者下载的模块,可以根据情况放在相应的路径,以便Python可以找到。 3. 模块包 可以将功能相似的模块放在同一个文件夹(比如说dir)中,通过 import dir.module 的方式引入。 注意,该文件夹中必须包含一个__init__.py的文件,以便提醒python知道该文件夹为一个模块包。__init__.py可以是一个空文件。 总结 import module module.object __init__.py Python进阶04 函数的参数对应 我们已经接触过函数(function)的参数(arguments)传递, 当时我们提到,参数是根据位置对应传递的。这一次,我们准备接触更多的参数对应方式。 首先,回忆一下根据位置传递: def f(a,b,c): print a,b,c f(1,2,3) 在调用f时,1,2,3根据位置分别传递给了a,b,c。 1. 关键字(keyword)参数传递 有时候,我们发现,在写程序时用位置传递会感觉比较死板。关键字传递就是根据每个参数的名字传递值,而不用遵守固定的位置。依然沿用上面f的定义,更改调用方式: f(c=3,b=2,a=1) 关键字传递可以和位置传递混用,但位置传递的参数必须在关键字传递的参数之前。 f(1,c=3,b=2) 2. 参数默认值(default) 在定义函数的时候,使用形如a=19的方式,可以给参数赋予默认值。如果该参数最终没有被传递值,将使用该默认值。 def f(a,b,c=10): print a,b,c f(3,2) f(3,2,1) 在第一次调用函数f时, 我们并没有足够的值,c没有被赋值,c将使用默认值10. 第二次调用函数的时候,c被赋值为1,不再使用默认值。 3. 包裹(packing)位置传递和包裹关键字传递: 在定义函数时,我们有时候并不知道调用的时候会传递多少个参数。这时候,使用包裹位置传递和包裹关键字传递会非常有用。 下面是包裹位置传递的例子: def func(*name): print type(name) print name func(1,4,6) func(5,6,7,1,2,3) 上面的两次调用,尽管参数的个数不同,依然可以用同一个func定义。原因在于,在func的参数表中,所有的参数被name收集,根据位置合并成一个定值表(tuple),这就是包裹位置传递。为了提醒python参数name是包裹位置传递所用的定值表名,在定义func时,在name前加*号。 下面是包裹关键字传递的例子: def func(**dict): print type(dict) print dict func(a=1,b=9) func(m=2,n=1,c=11) 与上面一个例子类似,dict是一个字典,收集所有的关键字,传递给函数func。为了提醒python参数dict是包裹关键字传递所用的字典,在dict前加**。 我们看到,包裹位置传递和包裹关键字传递的关键在于定义函数时,在相应定值表或字典前加*或**。 4. 解包裹 *和**在调用的时候的应用,主要是解包裹(unpacking), 下面为例: def func(a,b,c): print a,b,c args = (1,3,4) func(*args) 在这个例子中,所谓的解包裹,就是在传递tuple时,让tuple的每一个元素对应一个位置参数。在这里,通过在调用func时使用*,我们提醒python,我想要把args拆成分散的三个元素,分别传递给a,b,c。(设想一下在调用func时,args前面没有*会是什么后果?) 相应的,也存在对词典的解包裹,使用相同的func定义,然后: dict = {'a':1,'b':2,'c':3} func(**dict) 在传递词典dict时,让词典的每个键值对作为一个关键字传递给func。 5. 混合定义与混合调用 参数的几种传递方式可以混合定义和混合调用。但在过程中要小心前后顺序。基本原则是,先位置,再关键字,再包裹位置,再包裹关键字,并且根据上面所说的原理细细分辨。 注意:本讲的内容,请注意定义时使用和调用时使用的区分。包裹和解包裹并不是相反操作,而是两个相对独立的过程。 总结: 关键字,默认值, 包裹位置,包裹关键字 解包裹 Python进阶05 循环设计 之前在“循环”一节,我们已经讨论了Python最基本的循环语法。这一节,我们将接触更加灵活的循环方式。 1. 利用range(), 得到下标 在Python中,for循环后的in跟随一个序列的话,循环每次使用的序列元素,而不是序列的下标。 之前我们已经使用过range来控制for循环。现在,我们继续开发range的功能,以实现下标对循环的控制: S = 'abcdefghijk' for i in range(0,len(S),2): print S[i] 在该例子中,我们利用len()函数和range()函数,用i作为S序列的下标来控制循环。在range函数中,分别定义上限,下限和每次循环的步长。这就和C语言中的for循环相类似了。 2. 利用enumerate(), 同时得到下标和元素 利用enumerate()函数,可以在每次循环中同时得到下标和元素: S = 'abcdefghijk' for (index,char) in enumerate(S): print index print char 实际上,enumerate()在每次循环中,返回的是一个包含两个元素的定值表(tuple),两个元素分别赋予index和char 3. 利用zip(), 实现并行循环 如果你多个等长的序列,然后想要每次循环时从各个序列分别取出一个元素,可以利用zip()方便地实现: ta = [1,2,3] tb = [9,8,7] tc = ['a','b','c'] for (a,b,c) in zip(ta,tb,tc): print(a,b,c) 每次循环时,从各个序列分别从左到右取出一个元素,合并成一个tuple,然后tuple的元素赋予给a,b,c zip()函数的功能,就是从多个列表中,依次各取出一个元素。每次取出的(来自不同列表的)元素合成一个元组,合并成的元组放入zip()返回的列表中。 zip()函数起到了聚合列表的功能。我们还可以分解该聚合后的列表,如下: ta = [1,2,3] tb = [9,8,7] # cluster zipped = zip(ta,tb) print(zipped) # decompose na, nb = zip(*zipped) print(na, nb) 总结: range() enumerate() zip() Python进阶06 循环对象 通过上面一讲,我们再次熟悉了Python里的循环控制。现在,我们将接触循环对象(iterable object)。 这一讲的主要目的是为了大家在读Python程序的时候对循环对象有一个基本概念。 循环对象的并不是随着Python的诞生就存在的,但它的发展迅速,特别是Python 3x的时代,从zip()或者map()的改变来看,循环对象正在成为循环的标准形式。 1. 什么是循环对象 循环对象是这样一个对象,它包含有一个next()方法(__next__()方法,在python 3x中), 这个方法的目的是进行到下一个结果,而在结束一系列结果之后,举出StopIteration错误。 当一个循环结构(比如for)调用循环对象时,它就会每次循环的时候调用next()方法,直到StopIteration出现,for循环接收到,就知道循环已经结束,停止调用next()。 假设我们有一个test.txt的文件: 1234 abcd efg 我们运行一下python命令行: >>> f = open('test.txt') >>> f.next() >>> f.next() ... 不断地输入f.next(),直到最后出现StopIteration open()返回的实际上是一个循环对象,包含有next()方法。而该next()方法每次返回的就是新的一行的内容,到达文件结尾时举出StopIteration。这样,我们相当于手工进行了循环。 自动进行的话,就是: for line in open('test.txt'): print line 在这里,for结构自动调用next()方法,将该方法的返回值赋予给line。循环知道出现StopIteration的时候结束。 相对于序列,用循环对象来控制循环的好处在于:可以不用在循环还没有开始的时候,就生成每次要使用的元素。所使用的元素在循环过程中逐次生成。这样,就节省了空间,提高了效率,并提高编程的灵活性。 2. iter()函数和循环器(iterator) 从技术上来说,循环对象和for循环调用之间还有一个中间层,就是要将循环对象转换成循环器(iterator)。这一转换是通过使用iter()函数实现的。但从逻辑层面上,常常可以忽略这一层,所以循环对象和循环器常常相互指代对方。 3. 生成器(generator) 生成器的主要目的是构成一个用户自定义的循环对象。 生成器的编写方法和函数定义类似,只是在return的地方改为yield。生成器中可以有多个yield。当生成器遇到一个yield时,会暂停运行生成器,返回yield后面的值。当再次调用生成器的时候,会从刚才暂停的地方继续运行,直到下一个yield。生成器自身又构成一个循环器,每次循环使用一个yield返回的值。 下面是一个生成器: def gen(): a = 100 yield a a = a*8 yield a yield 1000 该生成器共有三个yield, 如果用作循环器时,会进行三次循环。 for i in gen(): print i 再考虑如下一个生成器: def gen(): for i in range(4): yield i 它又可以写成生成器表达式(Generator Expression): G = (x for x in range(4)) 生成器表达式是生成器的一种简便的编写方式。读者可进一步查阅。 4. 表推导(list comprehension) 表推导是快速生成表的方法。假设我们生成表L: L = [] for x in range(10): L.append(x**2) 以上产生了表L,但实际上有快捷的写法,也就是表推导的方式: L = [x**2 for x in range(10)] 这与生成器表达式类似,只不过用的是中括号。 (表推导的机制实际上是利用循环对象,有兴趣可以查阅。) 考虑下面的表推导会生成什么? xl = [1,3,5] yl = [9,12,13] L = [ x**2 for (x,y) in zip(xl,yl) if y > 10] 总结: 循环对象 生成器 表推导 Python进阶07 函数对象 秉承着一切皆对象的理念,我们再次回头来看函数(function)这一结构。函数实际上也是一个对象。既然是一个对象,它也具有属性(可以使用dir()查询)。作为对象,它还可以赋值给其它变量名,或者作为参数传递给其它函数使用。 1. lambda 在展开之前,我们先提一下lambda。lambda是一种简便的,在同一行中定义函数的方法,其功能可以完全由def定义实现。lambda例子如下: func = lambda x,y: x + y print func(3,4) lambda以及之后的内容实际上生成一个函数对象(也就是函数)。该函数参数为x,y,返回值为x+y。该函数对象赋值给函数名func。func的调用与正常函数无异。 以上定义完全可以写成以下形式: def func(x, y): return x + y 2. 函数可以作为参数传递 函数可以作为一个对象进行参数传递。函数名(比如func)即指向该对象,不需要括号。比如说: def test(f, a, b): print 'test' print f(a, b) test(func, 3, 5) 我们可以看到,test函数的第一个参数f就是一个函数对象。我们将func传递给f,那么test中的f()所做的实际上就是func()所实现的功能。 这样,我们就大大提高了程序的灵活性。假设我们有另一个函数取代func,就可以使用相同的test函数了。如下: test((lambda x,y: x**2 + y), 6, 9) 思考这句程序的含义。 3. map函数 map()是Python的内置函数,它的第一个参数是一个函数对象。 re = map((lambda x: x+3),[1,3,5,6]) 这里,map()有两个参数,一个是lambda所定义的函数对象,一个是包含有多个元素的表。map()的功能是将函数对象依次作用于表的每一个元素,每次作用的结果储存于返回的表re中。map通过读入的函数(这里是lambda函数)来操作数据(这里“数据”是表中的每一个元素,“操作”是对每个数据加3)。 (注意,在Python 3.X中,map()将每次作用结果yield出来,形成一个循环对象。可以利用list()函数,将该循环对象转换成表) 如果作为参数的函数对象有多个参数,可如下例: re = map((lambda x,y: x+y),[1,2,3],[6,7,9]) map()将每次从两个表中分别取出一个元素,带入lambda所定义的函数。 (本小节所使用的lambda也完全可以是def定义的更复杂的函数) 4. filter函数 filter函数与map函数类似,也是将作为参数的函数对象作用于表的各个元素。如果函数对象返回的是True,则该次的元素被储存于返回的表中。filter通过读入的函数来筛选数据。(同样,在Python 3.X中,filter返回的不是表,而是循环对象。) filter函数的使用如下例: def func(a): if a > 100: return True else: return False print filter(func,[10,56,101,500]) 5. reduce函数 reduce函数的第一个参数也是函数,但有一个要求,就是这个函数自身能接收两个参数。reduce可以累进地将函数作用于各个参数。如下例: print reduce((lambda x,y: x+y),[1,2,5,7,9]) reduce的第一个参数是lambda函数,它接收两个参数x,y, 返回x+y。 reduce将表中的前两个元素(1和2)传递给lambda函数,得到3。该返回值(3)将作为lambda函数的第一个参数,而表中的下一个元素(5)作为lambda函数的第二个参数,进行下一次的对lambda函数的调用,得到8。依次调用lambda函数,每次lambda函数的第一个参数是上一次运算结果,而第二个参数为表中的下一个元素,直到表中没有剩余元素。 上面例子,相当于(((1+2)+5)+7)+9 (根据mmufhy的提醒: reduce()函数在3.0里面不能直接用的,它被定义在了functools包里面,需要引入包,见评论区) 总结: 函数是一个对象 用lambda定义函数 map() filter() reduce() Python进阶08 异常处理 在项目开发中,异常处理是不可或缺的。异常处理帮助人们debug,通过更加丰富的信息,让人们更容易找到bug的所在。异常处理还可以提高程序的容错性。 我们之前在讲循环对象的时候,曾提到一个StopIteration的异常,该异常是在循环对象穷尽所有元素时的报错。 我们以它为例,来说明基本的异常处理。 一个包含异常的程序: re = iter(range(5)) for i in range(100): print re.next() print 'HaHaHaHa' 首先,我们定义了一个循环对象re,该循环对象将进行5次循环,每次使用序列的一个元素。 在随后的for循环中,我们手工调用next()函数。当循环进行到第6次的时候,re.next()不会再返回元素,而是抛出(raise)StopIteration的异常。整个程序将会中断。 我们可以修改以上异常程序,直到完美的没有bug。但另一方面,如果我们在写程序的时候,知道这里可能犯错以及可能的犯错类型,我们可以针对该异常类型定义好”应急预案“。 re = iter(range(5)) try: for i in range(100): print re.next() except StopIteration: print 'here is end ',i print 'HaHaHaHa' 在try程序段中,我们放入容易犯错的部分。我们可以跟上except,来说明如果在try部分的语句发生StopIteration时,程序该做的事情。如果没有发生异常,则except部分被跳过。 随后,程序将继续运行,而不是彻底中断。 完整的语法结构如下: try: ... except exception1: ... except exception2: ... except: ... else: ... finally: ... 如果try中有异常发生时,将执行异常的归属,执行except。异常层层比较,看是否是exception1, exception2...,直到找到其归属,执行相应的except中的语句。如果except后面没有任何参数,那么表示所有的exception都交给这段程序处理。比如: try: print(a*2) except TypeError: print(\"TypeError\") except: print(\"Not Type Error & Error noted\") 由于a没有定义,所以是NameError。异常最终被except:部分的程序捕捉。 如果无法将异常交给合适的对象,异常将继续向上层抛出,直到被捕捉或者造成主程序报错。比如下面的程序 def test_func(): try: m = 1/0 except NameError: print(\"Catch NameError in the sub-function\") try: test_func() except ZeroDivisionError: print(\"Catch error in the main program\") 子程序的try...except...结构无法处理相应的除以0的错误,所以错误被抛给上层的主程序。 如果try中没有异常,那么except部分将跳过,执行else中的语句。 finally是无论是否有异常,最后都要做的一些事情。 流程如下, try->异常->except->finally try->无异常->else->finally 我们也可以自己写一个抛出异常的例子: print 'Lalala' raise StopIteration print 'Hahaha' (注意,这个例子不具备任何实际意义。读者可探索更多有意义的例子。) StopIteration是一个类。当我们raise它的时候,有一个中间环节,就是Python利用 StopIteration生成一个该类的一个对象。Python实际上举出的,是这一个对象。当然,也可以直接写成: raise StopIteration() 总结: try: ... except exception: ... else: ... finally: ... raise exception Python进阶09 动态类型 动态类型(dynamic typing)是Python另一个重要的核心概念。我们之前说过,Python的变量(variable)不需要声明,而在赋值时,变量可以重新赋值为任意值。这些都与动态类型的概念相关。 1. 动态类型 在我们接触的对象中,有一类特殊的对象,是用于存储数据的。常见的该类对象包括各种数字,字符串,表,词典。在C语言中,我们称这样一些数据结构为变量。而在Python中,这些是对象。 对象是储存在内存中的实体。而我们的变量,实际上只是指向这一对象的引用(reference),类似于C语言的指针。 (在C语言中,变量自身就是存储于内存中的实体) 引用和它所指的对象的分离,就是动态类型的核心。由于引用只类似于一个指针,所以它可以随时指向一个新的对象,即使这个新的对象的类型发生了变化。 a = 3 a = 'at' 第一个语句中,3是储存在内存中的一个整数对象。通过赋值,我们将在内存中建立这一对象,并将引用a指向改对象。 第二个语句中,我们在内存中建立对象‘at’, 其类型是字符串(string)。引用a在此又指向了'at'。而此时,对象3不再有引用指向它,Python会自动将没有引用指向的对象销毁(destruct),从而释放相应内存。 (对于小的整数和短字符串,实际上Python会缓存这些对象,而不是针对每次赋值而分别建立和销毁。但从逻辑层面上来说,上面的说法并没有问题。我们将忽略这一细节) a = 5 b = a a = a + 2 再看这个例子。通过前两个句子,我们让a,b指向同一个整数对象5(b = a的含义是让引用b指向引用a所指的那一个对象)。但第三个句子实际上对引用a重新赋值,让a指向一个新的对象7。此时a,b分别指向不同的对象。我们看到,即使是多个引用指向同一个对象,如果一个引用值发生变化,那么实际上是让这个引用指向一个新的引用,并不影响其他的引用的指向。从效果上看,就是各个引用各自独立,互不影响。 不止是整数如此,其它数据对象也是如此: L1 = [1,2,3] L2 = L1 L1 = 1 但注意以下情况 L1 = [1,2,3] L2 = L1 L1[0] = 10 print L2 在该情况下,我们不再对L1这一引用赋值,而是对L1所指向的表的元素赋值。结果是,L2也同时发生变化。 原因何在呢?因为L1,L2的指向没有发生变化,依然指向那个表。表实际上是包含了多个引用的对象(每个引用是一个元素,比如L1[0],L1[1]..., 每个引用指向一个对象,比如1,2,3), 。而L1[0] = 10这一赋值操作,并不是改变L1的指向,而是对L1[0], 也就是表对象的一部份(一个元素),进行操作,所以所有指向该对象的引用都受到影响。 (与之形成对比的是,我们之前的赋值操作都没有对对象自身发生作用,只是改变引用指向。) 像表这样,可以通过引用元素,改变内存中的对象自身(in-place change)的对象类型,称为可变数据对象(mutable object),词典也是这样的数据类型。 而像之前的数字和字符串,不能改变对象本身,只能改变引用的指向,称为不可变数据对象(immutable object)。我们之前学的定值表(tuple),尽管可以引用引用元素,但不可以通过赋值改变元素,也因此不能对对象本身进行改变,也是immutable object. 2. 从动态类型看函数的参数传递 函数的参数传递,实际上是让函数的各个参数作为引用,指向对象。比如说: def f(x): x = 100 print x a = 1 f(a) print a 在调用函数f()时,实际上函数让参数作为一个引用,指向a所指的对象。如果参数是不可变(immutable)的对象,那么如上面所讲,各个引用之间相当于相互独立。参数传递类似于C语言中的值传递。 如果是参数是可变(mutable)的对象,那么存在有改变对象自身的可能性,所有指向该对象的引用(无论是函数中的参数,还是主程序中的引用)都会受影响,编程的时候要对此问题留心。比如说: def f(x): x[0] = 100 print x a = [1,2,3] f(a) print a 动态类型是Python的核心机制之一。可以在应用中慢慢熟悉。 总结: 引用和对象的分离,对象是内存中储存数据的实体,引用指向对象。 可变对象,不可变对象 函数值传递 因篇幅问题不能全部显示,请点此查看更多更全内容