Python3极速入门
来源:互联网 发布:java连接数据库的框架 编辑:程序博客网 时间:2024/05/23 20:34
转载请注明出处:http://blog.csdn.net/tyhj_sf/article/details/73480499
开发环境
推荐使用pycharm 做开发IDE,比较好用。
搭建步骤:
- 先去官网下载 python 2或python3 版本的解释器,安装。
- 去官网下载pycharm最新版本,一步步安装即可。
基础示例
Python语法基础,python语法比较简单,采用缩紧方式。
# print absolute value of a integera = 100if a >= 0: print(a)else: print(-a)
可以看到,注释以#开头,python的变量不需要任何前缀,行结束不需要结束符号,非常符合我们自然语言的阅读和书写习惯。当语句以:
结尾时,缩紧的语句视为代码块。
Python是大小写敏感的,这一点需要特别注意。
输入与输出
Python可以使用input()函数读取用户的输入,使用print()进行屏幕的输出。默认情况下,输入的内容为字符数据类型。
数据类型
整数
Python可以处理任意大小的整数,在程序中的表示方法和数学上的写法一模一样,可以使用0xff00的方式表示十六进制。
Python中使用/进行除法运算,得到的结果是浮点数。使用//进行除法运算,得到的结果是整数。使用%,表示取余数。
浮点数
浮点数就是小数,可以使用数学写法,如:1.23,-9.01,也可以使用科学计数法表示,如:1.23e9,1.2e-5。
字符串
字符串是使用`或”括起来的任意文本。可以使用*对特殊字符进行转义。可以使用r”的形式,表示内部的字符串默认不进行转义。对于字符串内有换行等多行内容的,可以使用’’’…’’’的形式,多行字符前也可以加r*。
在最新的Python 3版本中,字符串是以Unicode编码的,也就是说,Python的字符串支持多语言。对于单个字符的编码,Python提供了ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符。以Unicode表示的str通过encode()方法可以编码为指定的bytes,如:>>> ‘ABC’.encode(‘ascii’)。反过来,如果我们从网络或磁盘上读取了字节流,那么读到的数据就是bytes。要把bytes变为str,就需要用decode()方法
在Python中,采用的格式化方式和C语言是一样的,如下:
'Hi, %s, you have $%d.' % ('Michael', 1000000)
布尔值
布尔值:True
、False
。也可以用布尔代数表示:3 > 2
,* 3 < 2
。布尔值的运算符号:and
、or
、not*
。
空值
空值是Python里一个特殊的值,用None
表示。
变量
Python中的变量时动态变量,即变量的属性是在赋值的时候才决定的,变量名称必须是大小写英文、数字和_的组合,且不能用数字开头。Python中没有常量的概念,通常使用全部大写的变量来表示常量。
列表 list
list 是一种有序的集合,可以随时添加和删除其中的元素。用索引来访问list中每一个位置的元素,索引是从0开始的。当索引超出了范围时,Python会报一个IndexError错误。如果要取最后一个元素,除了计算索引位置外,还可以用-1做索引,直接获取最后一个元素。
>>> classmates = [‘Michael’,’Bob’,’Tracy']>>> classmates[‘Michael’,’Bob','Tracy’]>>> len(classmates)3>>> classmates[0]‘Michael’>>> classmates.append('Adam’) #追加元素到末尾>>> classmates['Michael', 'Bob', 'Tracy', 'Adam’]>>> classmates.insert(1, 'Jack’) #追加元素到指定位置>>> classmates['Michael', 'Jack', 'Bob', 'Tracy', ‘Adam’]>>> classmates.pop() #删除末尾的元素,使用pop(i)可以删除指定位置的元素'Adam'>>> classmates['Michael', 'Jack', 'Bob', 'Tracy']
元组 tulp
元组 tulp 也是有序列表,与list的区别在于,一旦初始化就不能修改。没有append、insert等方法。
tulp的定义方式如下:
>>> classmates = (‘Michael’, ‘Bob’, ’Tracy')
tulp 本身的元素不能发生变化,但是如果元素为list,那么list中的内容是可变的。
字典 dict
dict 全称 dictionary ,在其他语言中称为 map,在PHP中其实就是 Array,使用键-值(Key-Value)的方式进行存储,具有极快的查找速度。使用范例
>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85} #dict的定义>>> d['Michael’] #dict取值的方式95>>> d['Adam'] = 67 #dict设置新值的方式>>> 'Thomas' in d #判断key是否存在False>>> d.get('Thomas’) #get方式取值,如果不存在则返回None>>> d.get('Thomas',-1)#指定不存在时的返回值-1>>> d.pop('Bob’) #删除某个key75setset和dict类似,也是一组key的集合,但不存储value。如下示例:>>> s = set([1, 2, 3]) #初始化时提供一个list作为输入集合>>> s{1, 2, 3}>>> s.add(4) #使用add方法添加元素>>> s{1, 2, 3, 4}>>> s.remove(2) #使用remove方法>>> s{1, 3, 4}
set可以看成数学意义上的无序和无重复元素的集合,因此,两个set可以做数学意义上的交集、并集等操作。
控制语句
条件判断
条件判断比较简单,主要是不要忘记写:,看看例子吧。
>>> age = 3>>> if age >= 18:... print('your age is', age)... print('adult')... else:... print('your age is', age)... print('teenager')...your age is 3teenager
更多的条件判断。
>>> age = 3>>> if age >= 18:... print('adult')... elif age >= 6:... print('teenager')... else:... print('kid')...kid
循环
Python中的循环有两种,一种是for…in循环,依次把list或tulp中的每个元素迭代出来。
>>> classmates['Michael', 'Jack', 'Bob', 'Tracy']>>> for name in classmates:... print(name)...MichaelJackBobTracy
第二种循环是while循环,只要条件满足,就不断循环,条件不满足时退出循环。
sum = 0n = 99while n > 0: sum = sum + n n = n - 2print(sum)
跳出循环,可以使用 break 跳出循环。跳过本次循环,可以使用 continue 跳过本次循环,继续下一次循环。
函数
调用函数
Python中内置了很多函数,可以直接调用。在交互模式中,可以通过help(abs)查看函数的用法。
定义函数
在Python中,定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回。如果没有return语句,函数执行完毕后也会返回结果,只是结果为None。return None可以简写为return。
示例如下:
def my_abs(x): if x >= 0: return x else: return -x
在Python交互环境中定义函数时,注意Python会出现…的提示。函数定义结束后需要按两次回车重新回到>>>提示符下。
返回多个值
在其他语言中,一般只能返回一个值或者一个数组、对象,在Python中,可以通过tulp变通的返回多个值。
import mathdef move(x, y, step, angle=0): nx = x + step * math.cos(angle) ny = y - step * math.sin(angle) return nx, ny>>> x, y = move(100, 100, 60, math.pi / 6)>>> print(x, y)
在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值,所以,Python的函数返回多值其实就是返回一个tuple,但写起来更方便。
位置参数
Python的函数定义非常简单,但灵活度却非常大。除了正常定义的必选参数外,还可以使用默认参数、可变参数和关键字参数,使得函数定义出来的接口,不但能处理复杂的参数,还可以简化调用者的代码。
def power(x): #x 就是一个位置参数 return x * xdef power(x, n): #x,n都是位置参数 s = 1 while n > 0: n = n - 1 s = s * x return sdef power(x, n=2): #x,n都是位置参数,n设置了默认值 s = 1 while n > 0: n = n - 1 s = s * x return s
有几点要注意:
- 必选参数在前,默认参数在后,否则Python的解释器会报错。
- 当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。
- 有多个默认参数时,调用的时候,既可以按顺序提供默认参数。也可以不按顺序提供部分默认参数。当不按顺序提供部分默认参数时,需要把参数名写上。
需要注意的是:默认参数必须指向不变对象!
可变参数
def calc(*numbers): sum = 0 for n in numbers: sum = sum + n * n return sum
定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*号。在函数内部,参数numbers接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数。Python允许你在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去。
关键字参数
关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。
def person(name, age, **kw): print('name:', name, 'age:', age, 'other:', kw)
关键字参数有什么用?
它可以扩展函数的功能。比如,在person函数里,我们保证能接收到name和age这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。
#可以先组装出一个dict,然后,把该dict转换为关键字参数传进去>>> extra = {'city': 'Beijing', 'job': 'Engineer'}>>> person('Jack', 24, city=extra['city'], job=extra['job'])name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
也可以更简洁:
#简化的写法>>> extra = {'city': 'Beijing', 'job': 'Engineer'}>>> person('Jack', 24, **extra)name: Jack age: 24 other: {'city': 'Beijing', 'job': ‘Engineer'}
extra表示把extra这个dict的所有key-value用关键字参数传入到函数的kw参数,kw将获得一个dict,注意kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra。
命名关键字参数
如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city和job作为关键字参数。这种方式定义的函数如下。命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名关键字参数。
def person(name, age, *, city, job): print(name, age, city, job)>>> person('Jack', 24, city='Beijing', job='Engineer')Jack 24 Beijing Engineer
如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了。
def person(name, age, *args, city, job): print(name, age, args, city, job)
参数组合
在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
面向对象编程
面向对象编程 Object Oriented Programming 简称 OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。
class Student(object): def __init__(self, name, score): self.name = name self.score = score def print_score(self): print('%s: %s' % (self.name, self.score))
类和实例
class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。
定义好了Student类,就可以根据Student类创建出Student的实例,创建实例是通过类名+()实现的。__init__
函数与其它函数有所不同,它的第一个参数永远是实例变量self,并且,调用时,不用传递该参数。
数据封装
类本身拥有数据和方法,相当于将“数据”封装起来了。对于外部来说,并不需要知道内部的逻辑。
访问限制
Class可以有属性和方法,我们可以对属性和方法进行控制,以达到允许或者不允许外部访问的目的。如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线,在Python中,实例的变量名如果以开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。
class Student(object): def __init__(self, name, score): self.__name = name self.__score = score def print_score(self): print('%s: %s' % (self.__name, self.__score)) def get_name(self): return self.__name def get_score(self): return self.__score def set_score(self, score): if 0 <= score <= 100: self.__score = score else: raise ValueError('bad score’)
在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__这样的变量名。
继承和多态
在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。
class Animal(object): def run(self): print('Animal is running…')class Dog(Animal): def run(self): print('Dog is running...')class Cat(Animal): def run(self): print('Cat is running…')
当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行。
对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在Animal、Dog、Cat还是Tortoise对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:
对扩展开放:允许新增Animal子类;
对修改封闭:不需要修改依赖Animal类型的外部函数。
鸭子类型
对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了。这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
获取对象信息
判断Python中对象的类型,可以用以下方法。
type()
基本类型都可以用type()判断,基本数据类型可以直接写int、str,判断是否函数需要使用types中定义的常量。
>>> import types>>> def fn():... pass...>>> type(fn)==types.FunctionTypeTrue>>> type(abs)==types.BuiltinFunctionTypeTrue>>> type(lambda x: x)==types.LambdaTypeTrue>>> type((x for x in range(10)))==types.GeneratorTypeTrue
isinstance()
对于类和实例,使用type()就不是很方便,可以使用isinstance()。基本数据类型也可以使用isinstance()判断。还可以判断一个变量是否是某些类型中的一种。
>>> isinstance([1, 2, 3], (list, tuple))True>>> isinstance((1, 2, 3), (list, tuple))True
dir()
如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法。
实例属性和类属性
Python类创建的实例可以任意绑定属性,如果需要对类本身绑定属性,则需要在类中定义,这就区分了类属性和实例属性。
在编写程序的时候,千万不要把实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。
Python面向对象高级编程
数据封装、继承和多态是面相对象程序设计中的三个基本概念,另外还有很多特性,包括多重继承、定制类等。
使用 slots()
在Python中,可以对类动态的增加属性和方法,这在静态语言中很难实现。
#!/usr/bin/env python3# -*- coding: utf-8 -*-class Student(object): def __init__(self): print("Instance Created")Tracy = Student()Tracy.age = 30Bob = Student()Bob.age = 41Ceaser = Student()print(Tracy.age)print(Bob.age)def set_age(self, age): self.age = agefrom types import MethodTypes = Student()s.set_age = MethodType(set_age, s)s.set_age(25)print(s.age)Student.set_age = set_ageCeaser.set_age(33)print(Ceaser.age)
但这也带来一个问题,属性和方法可以随意更改,如果我们要限制怎么办?可以使用__slots
。Python允许在定义class的时候,定义一个特殊的__slots__
变量,来限制该class实例能添加的属性。使用__slots__
要注意,__slots__
定义的属性仅对当前类实例起作用,对继承的子类是不起作用的。
class Student(object): __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
使用@Property
Python中实例的属性暴露在外面可以随便修改,这样就无法保证属性的有效性符合校验规则。虽然可以通过设置Setter和Getter来进行检查,但如果属性特别多,操作起来又比较麻烦。
还记得装饰器(decorator)可以给函数动态加上功能吗?对于类的方法,装饰器一样起作用。Python内置的@property装饰器就是负责把一个方法变成属性调用的。
class Student(object): @property def score(self): return self._score @score.setter def score(self, value): if not isinstance(value, int): raise ValueError('score must be an integer!') if value < 0 or value > 100: raise ValueError('score must between 0 ~ 100!') self._score = value
@property的实现比较复杂,我们先考察如何使用。把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作。
>>> s = Student()>>> s.score = 60 # OK,实际转化为s.set_score(60)>>> s.score # OK,实际转化为s.get_score()60>>> s.score = 9999Traceback (most recent call last): ...ValueError: score must between 0 ~ 100!
还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性。
class Student(object): @property def birth(self): return self._birth @birth.setter def birth(self, value): self._birth = value @property def age(self): return 2015 - self._birth
多重继承
继承是面向对象编程的一个重要的方式,因为通过继承,子类就可以扩展父类的功能。举例来说对于动物的对象设计,可以按照“哺乳动物”、“鸟类”来设计分类对象,按照不同的维度,也可以按照“能跑的”、“能飞的”或者“宠物”、“非宠物”设计分类,如果按照单一继承的方式,类的设计就像下图,会变的非常复杂。
正确的办法是采用多重继承。一个子类就可以同时获得多个父类的所有功能。在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich继承自Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承Runnable。这种设计通常称之为MixIn。
class Animal(object): pass# 大类:class Mammal(Animal): passclass Bird(Animal): pass# 各种动物:class Dog(Mammal): passclass Bat(Mammal): passclass Parrot(Bird): passclass Ostrich(Bird): passclass Runnable(object): def run(self): print('Running...')class Flyable(object): def fly(self): print('Flying…’)class Dog(Mammal, Runnable): passclass Bat(Mammal, Flyable): pass
Python自带的很多库也使用了MixIn。举个例子,Python自带了TCPServer和UDPServer这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixIn和ThreadingMixIn提供。通过组合,我们就可以创造出合适的服务来。
定制类
类似于__slots__
,Python的class中还有许多这样有特殊用途的函数,可以帮助我们定制类。
str
定义print 函数调用时的返回结果。
>>> class Student(object):... def __init__(self, name):... self.name = name... def __str__(self):... return 'Student object (name: %s)' % self.name...>>> print(Student('Michael'))Student object (name: Michael)
repr
定义返回程序开发者看到的字符串,也就是在命令行状态下执行时的返回值。
class Student(object): def __init__(self, name): self.name = name def __str__(self): return 'Student object (name=%s)' % self.name __repr__ = __str__
iter
如果一个类想被用于for … in循环,类似list或tuple那样,就必须实现一个iter()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的next()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
class Fib(object): def __init__(self): self.a, self.b = 0, 1 # 初始化两个计数器a,b def __iter__(self): return self # 实例本身就是迭代对象,故返回自己 def __next__(self): self.a, self.b = self.b, self.a + self.b # 计算下一个值 if self.a > 100000: # 退出循环的条件 raise StopIteration() return self.a # 返回下一个值
getitem
Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,比如,取第5个元素。
class Fib(object): def __getitem__(self, n): if isinstance(n, int): # n是索引 a, b = 1, 1 for x in range(n): a, b = b, a + b return a if isinstance(n, slice): # n是切片 start = n.start stop = n.stop if start is None: start = 0 a, b = 1, 1 L = [] for x in range(stop): if x >= start: L.append(a) a, b = b, a + b return L
通过上面的方法,我们自己定义的类表现得和Python自带的list、tuple、dict没什么区别,这完全归功于动态语言的“鸭子类型”,不需要强制继承某个接口。
getattr
正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。要避免这个错误,除了可以加上一个属性外,Python还有另一个机制,那就是写一个__getattr__()
方法,动态返回一个属性。
class Student(object): def __init__(self): self.name = 'Michael' def __getattr__(self, attr): if attr=='score': return 99
call
任何类,只需要定义一个call()方法,就可以直接对实例进行调用。call()还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象,因为这两者之间本来就没啥根本的区别。
class Student(object): def __init__(self, name): self.name = name def __call__(self): print('My name is %s.' % self.name)
使用枚举类
Python中其实不存在常量,但是可以通过枚举类的方式来变通实现。
from enum import EnumMonth = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))for name, member in Month.__members__.items(): print(name, '=>', member, ',', member.value)from enum import Enum, unique@unique #@unique装饰器可以帮助我们检查保证没有重复值。class Weekday(Enum): Sun = 0 # Sun的value被设定为0 Mon = 1 Tue = 2 Wed = 3 Thu = 4 Fri = 5 Sat = 6
这样我们就获得了Month类型的枚举类,可以直接使用Month.Jan来引用一个常量,或者枚举它的所有成员。
使用元类
type()
type()函数可以查看一个类型或变量的类型,Hello是一个class,它的类型就是type,而h是一个实例,它的类型就是class Hello。我们说class的定义是运行时动态创建的,而创建class的方法就是使用type()函数。
type()函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过type()函数创建出Hello类,而无需通过class Hello(object)…的定义。
>>> def fn(self, name='world'): # 先定义函数... print('Hello, %s.' % name)...>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class>>> h = Hello()>>> h.hello()Hello, world.>>> print(type(Hello))<class 'type'>>>> print(type(h))<class '__main__.Hello’>
要创建一个class对象,type()函数依次传入3个参数:
1.class的名称;
2.继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
3.class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。
通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。
metaclass
metaclass,直译为元类,简单的解释就是:
当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。
但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。
# metaclass是类的模板,所以必须从`type`类型派生:class ListMetaclass(type): def __new__(cls, name, bases, attrs): attrs['add'] = lambda self, value: self.append(value) return type.__new__(cls, name, bases, attires)
异常处理、调试和测试
程序运行中,可能会遇到BUG、用户输入异常数据以及其它环境的异常,这些都需要程序猿进行处理。Python提供了一套内置的异常处理机制,供程序猿使用,同时PDB提供了调试代码的功能,除此之外,程序猿还应该掌握测试的编写,确保程序的运行符合预期。
异常处理
在一般程序处理中,可以对函数的返回值进行检查,是否返回了约定的错误码。例如系统程序调用的错误码一般都是-1,成功返回0。但是这种方式必须用大量的代码来判断是否出错,所以高级语言内置了try…except…finally的错误
try: print('try...') r = 10 / int('2') print('result:', r)except ValueError as e: print('ValueError:', e)except ZeroDivisionError as e: print('ZeroDivisionError:', e)else: print('no error!')finally: print('finally...')print(‘END’)
认为某些代码可能会出错时,就可以用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except语句块,执行完except后,如果有finally语句块,则执行finally语句块,至此,执行完毕。如果发生了不同的错误类型,可以由不同的except语句块处理,可以没有finally语句块。
Python的错误也是类,所有的错误类型都继承自BaseException,常见的错误类型和继承关系参考 官方文档
使用try…except捕获错误还有一个巨大的好处,就是可以跨越多层调用,比如函数main()调用foo(),foo()调用bar(),结果bar()出错了,这时,只要main()捕获到了,就可以处理。
调用堆栈
如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序退出。出错并不可怕,可怕的是不知道哪里出错了。解读错误信息是定位错误的关键。
记录错误
在C语言中,如果发生错误想要记录,必须自己编写错误记录的程序。Python内置的logging模块可以非常容易地记录错误信息。通过配置,logging还可以把错误记录到日志文件里,方便事后排查。
# err_logging.pyimport loggingdef foo(s): return 10 / int(s)def bar(s): return foo(s) * 2def main(): try: bar('0') except Exception as e: logging.exception(e)main()print('END')
抛出错误
抛出错误,首先需要定义一个错误 Class,选择好继承关系,然后用raise语句抛出一个错误实例。如果可以尽量使用Python内置的错误类型,仅在非常必要的时候自己定义错误类。
# err_raise.pyclass FooError(ValueError): passdef foo(s): n = int(s) if n==0: raise FooError('invalid value: %s' % s) return 10 / nfoo('0')
调试 Debug
调试最简单的办法就是print(),这个方法最简单,但是在发布的时候需要把所有的调试信息注释掉。
断言 assert
凡是用print()来辅助查看的地方,都可以用断言(assert)来替代。
def foo(s): n = int(s) assert n != 0, 'n is zero!' return 10 / ndef main(): foo('0')
assert的意思是,表达式n != 0
应该是True,否则,根据程序运行的逻辑,后面的代码肯定会出错。如果断言失败,assert语句本身就会抛出AssertionError
。
启动Python解释器时可以用-O参数来关闭assert。
打印日志 logging
使用 logging 不仅可以抛出错误,还可以输出到文件。
import logginglogging.basicConfig(level=logging.INFO)s = '0'n = int(s)logging.info('n = %d' % n)print(10 / n)
这就是logging的好处,它允许你指定记录信息的级别,有debug,info,warning,error等几个级别,当我们指定level=INFO时,logging.debug就不起作用了。同理,指定level=WARNING后,debug和info就不起作用了。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。
logging的另一个好处是通过简单的配置,一条语句可以同时输出到不同的地方,比如console和文件。
调试器 pdb
可以在命令行下使用pdb,启动Python的调试器pdb,让程序以单步方式运行,可以随时查看运行状态。
# err.pys = '0'n = int(s)print(10 / n)$ python3 -m pdb err.py> /Users/michael/Github/learn-python3/samples/debug/err.py(2)<module>()-> s = ‘0'
输入1可以查看代码,输入n可以单步执行代码。使用p来查看变量,使用q退出调试。
pdb.set_trace()
这个方法也是用pdb,但是不需要单步执行,我们只需要import pdb,然后,在可能出错的地方放一个pdb.set_trace(),就可以设置一个断点。运行代码,程序会自动在pdb.set_trace()暂停并进入pdb调试环境,可以用命令p查看变量,或者用命令c继续运行。
IO操作
IO就是Input / Output ,也就是输入和输出。IO编程中,Stream(流)是一个很重要的概念,可以把流想象成一个水管,数据就是水管里的水,但是只能单向流动。
由于计算机各个部件之间的速度不一致,所以处理IO问题时有两种办法:同步IO、异步IO。同步和异步的区别就在于是否等待IO执行的结果。
文件读写
读写文件是最常见的IO操作。Python内置了读写文件的函数,用法和C是兼容的。在磁盘上读写文件的功能都是由操作系统提供的,现代操作系统不允许普通的程序直接操作磁盘,所以,读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符),然后,通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)。
读文件
try: f = open('/path/to/file', 'r') print(f.read())finally: if f: f.close()with open('/path/to/file', 'r') as f: print(f.read())
类似于c语言,open函数默认接收一个文件名、一个打开模式参数(r、w默认对应文本文件,rb对应二进制文件)。默认打开的是UTF-8编码的文件,如果需要打开其它编码的,需要传入encoding参数,如果文本的编码不一致可能导致读取出错,可以传入错误处理参数errors。read方法一次将文件的所有内容读入内存,可以通过参数指定读入的长度read(size),也可以使用readline方法每次读入一行,使用readlines一次读入所有的行。文件使用后注意要进行关闭。
写文件
>>> f = open('/Users/michael/test.txt', 'w')>>> f.write('Hello, world!')>>> f.close()with open('/Users/michael/test.txt', 'w') as f: f.write('Hello, world!’)
写文件和读文件是一样的,唯一区别是调用open()函数时,传入标识符’w’或者’wb’表示写文本文件或写二进制文件。当我们写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。只有调用close()方法时,操作系统才保证把没有写入的数据全部写入磁盘。
StringIO 和 BytesIO
很多时候,数据读写不一定是文件,也可以在内存中读写。StringIO顾名思义就是在内存中读写str。
>>> from io import StringIO>>> f = StringIO()>>> f.write('hello')5>>> f.write(' ')1>>> f.write('world!')6>>> print(f.getvalue())hello world!>>> from io import StringIO>>> f = StringIO('Hello!\nHi!\nGoodbye!')>>> while True:... s = f.readline()... if s == '':... break... print(s.strip())...Hello!Hi!Goodbye!
StringIO操作的只能是str,如果要操作二进制数据,就需要使用BytesIO。BytesIO实现了在内存中读写bytes。
操作文件和目录
Python内置的os模块也可以直接调用操作系统提供的接口函数。import os模块后,就可以调用一些系统命令。
>>> import os>>> os.name # 操作系统类型'posix'>>> os.uname()posix.uname_result(sysname='Darwin', nodename='RousseaudeMacBook-Pro.local', release='15.6.0', version='Darwin Kernel Version 15.6.0: Mon Jan 9 23:07:29 PST 2017; root:xnu-3248.60.11.2.1~1/RELEASE_X86_64', machine='x86_64')>>> os.environenviron({'TERM_PROGRAM': 'Apple_Terminal', 'SHELL': '/bin/bash', 'TERM': 'xterm-256color', 'TMPDIR': '/var/folders/95/zrdts1md6j942mpyd7kd875h0000gn/T/', 'Apple_PubSub_Socket_Render': '/private/tmp/com.apple.launchd.fhDfjTsyk6/Render', 'TERM_PROGRAM_VERSION': '361.1', 'OLDPWD': '/Users/rousseau/Projects/python.my', 'TERM_SESSION_ID': '5A1B275C-3BE5-4673-B163-29DFF5C19C77', 'USER': 'rousseau', 'SSH_AUTH_SOCK': '/private/tmp/com.apple.launchd.mLtAPJeOFm/Listeners', '__CF_USER_TEXT_ENCODING': '0x1F5:0x0:0x0', 'PATH': '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin', 'PWD': '/Users/rousseau/Projects/python.my/mypython', 'XPC_FLAGS': '0x0', 'XPC_SERVICE_NAME': '0', 'SHLVL': '1', 'HOME': '/Users/rousseau', 'LOGNAME': 'rousseau', 'LC_CTYPE': 'UTF-8', '_': '/usr/local/bin/python3', '__PYVENV_LAUNCHER__': '/usr/local/bin/python3’})>>> os.path.abspath('.') # 查看当前目录的绝对路径:'/Users/rousseau/Projects/python.my/mypython’# 在某个目录下创建一个新目录,首先把新目录的完整路径表示出来:>>> os.path.join('/Users/michael', 'testdir')'/Users/michael/testdir'# 然后创建一个目录:>>> os.mkdir('/Users/michael/testdir')# 删掉一个目录:>>> os.rmdir('/Users/michael/testdir’)
因为Windows和Unix的路径表达方式不一样,所以在处理路径时,尽量使用Python提供的os.path.join()和os.path.split()避免处理发生问题。其它的文件处理函数os.rename、os.remove。
模块
任何语言要实现一个项目,都离不开文件组织管理。在Python中,一个.py文件就称之为一个模块(Module)。使用模块可以提高代码的可维护性,也可以避免函数名和变量名冲突。但是也要注意,尽量不要与内置函数名字冲突。为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package)。
引入了包以后,只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突。每一个包目录下面都会有一个init.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。init.py可以是空文件,也可以有Python代码,因为init.py本身就是一个模块。类似的,可以有多级目录,组成多级层次的包结构。
自己创建模块时要注意命名,不能和Python自带的模块名称冲突。例如,系统自带了sys模块,自己的模块就不可命名为sys.py,否则将无法导入系统自带的sys模块。
使用模块
看一段代码,引用了sys模块,定义了hello模块。
#!/usr/bin/env python3 #标准注释# -*- coding: utf-8 -*- #表示.py文件本身使用标准UTF-8编码' a test module '__author__ = 'Michael Liao'import sysdef test(): args = sys.argv if len(args)==1: print('Hello, world!') elif len(args)==2: print('Hello, %s!' % args[1]) else: print('Too many arguments!')if __name__=='__main__': test()
导入sys模块后,我们就有了变量sys指向该模块,利用sys这个变量,就可以访问sys模块的所有功能。
作用域
正常的函数和变量名是公开的(public),可以被直接引用,比如:abc,x123,PI等。类似xxx这样的变量是特殊变量,可以被直接引用,但是有特殊用途。类似_xxx和__xxx这样的函数或变量就是非公开的(private),不应该被直接引用。之所以我们说,private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量。
安装第三方模块
在Python中,安装第三方模块,是通过包管理工具pip完成的。在命令提示符窗口下尝试运行pip,如果Windows提示未找到命令,可以重新运行安装程序添加pip。
注意:Mac或Linux上有可能并存Python 3.x和Python 2.x,因此对应的pip命令是pip3。
一般来说,第三方库都会在Python官方的pypi.python.org网站注册,要安装一个第三方库,必须先知道该库的名称,可以在官网或者pypi上搜索,比如Pillow的名称叫Pillow,因此,安装Pillow的命令就是
pip install Pillow
Python学习资料
如果要深入学习Python推荐直接看官方文档:https://docs.python.org/3/index.html,上面有Python方方面面的资料。
- Python3极速入门
- Python3 入门
- Python3 入门(一)
- Python3 入门(二)
- python3入门之字符串
- python3入门之字典
- python3入门之函数
- python3入门之类
- python3入门学习
- Python3入门(一)
- Python3 (入门2) 数据结构
- Python3 (入门1) HelloWorld
- Python3 (入门3) 函数
- Python3 (入门4) 异常
- Python3 (入门5) 类
- python3.6 Tkinter 入门
- python3 爬虫-入门
- python3 爬虫技术入门
- Heap size check 堆大小检查
- 批量解压
- 使用卷积神经网络进行图片分类 3
- Opencv(Python) 教程-轮廓(2)轮廓特征求取
- POI操作Excel常用方法总结
- Python3极速入门
- 软件工程复习笔记
- Gerrit代码Review高阶实战
- Java中两表关系一对多,通过一表获取多表中数据并全部显示
- 几个RPC框架
- 使用卷积神经网络进行图片分类 4
- 自己摸索搭建的lamp环境步骤
- 二分查找法时间复杂度计算
- Java二叉树遍历