python3进阶教程
第1章 Python面向对象编程
1 什么是面向对象编程
2 Python类的定义与实例化
Python中,通过class关键字定义一个类,比如我们需要定义一个人的类。按照 Python 的编程习惯,类名以大写字母开头。因此可以这样定义:
1、class Person: pass
2、class Person(): pass
3、class Person(object): pass
这三种情况有什么区别呢?在Python3中,是没有区别的。
在Python2中,对于第一种定义的方法,Person类只有有限的几个内建函数’doc’, ‘module’, ‘name’,而对于第二种、第三种定义的方法,则会继承Python object对象的更多的内建函数。
定义了类之后,就可以对类进行实例化了
创建实例使用类名+(),类似函数调用的形式创建:
class Person(object):
pass #pass无论是前后都会执行相应的代码,单纯只有pass最后是没有输出出来,但也不会报错
#可以说在要执行else的时候,还不知道接下来做什么一个操作的时候,可以用pass来代替
xiaohong = Person()
xiaoming = Person()
3 Python实例属性的定义
通过以下的方式赋予实例这些属性,并且把这些属性打印出来。
xiaohong.name = 'xiaohong'
xiaohong.sex = 'girl'
xiaohong.age = 13
print(xiaohong.name)
print(xiaohong.sex)
print(xiaohong.age)
4 Python实例属性的初始化
在定义 Person 类时,可以为Person类添加一个特殊的__init__()方法,当创建实例时,init()方法被自动调用,我们就能在此为每个实例都统一加上以下属性:
class Person(object):
def __init__(self, name, sex, age):
self.name = name
self.sex = sex
self.age = age
需要注意的是,init() 方法的第一个参数必须是 self(也可以用别的名字,但建议使用习惯用法),后续参数则可以自由指定,和定义函数没有任何区别。
定义类后,就可以相应的实例化对象了,需要注意的是,在实例化的时候,需要提供除self以外的所有参数。
xiaoming = Person('Xiao Ming', 'boy', 13)
xiaohong = Person('Xiao Hong', 'girl', 14)
在定义__init__()方法常常忘记了 self 参数,这时候继续实例化就会报错;
实例化之后,当访问不存在的属性时,依然会报错
补充:Python的世界中,object是父子关系的顶端,所有的数据类型的父类都是它;type是类型实例关系的顶端,所有对象都是它的实例的。
5 Python类属性
含义:类和实例对象是有区别的,类是抽象,是模板,而实例则是根据类创建的对象,比如类:动物,只是一个抽象,并没有动物的详细信息,而猫、狗等,则是具体的动物,是类的对象。
定义类属性可以直接在 class 中定义,比如在前面的Animal类中,加入地域的类属性:
class Animal(object):
localtion = 'Asia'
def __init__(self, name, age):
self.name = name
self.age = age
在上面的代码中,localtion就是属于Animal这个类的类属性,此后,
通过Animal()实例化的所有对象,都可以访问到localtion,并且得到唯一的结果。
dog = Animal('wangwang', 1)
cat = Animal('mimi', 3)
print(dog.localtion) # ==> Asia
print(cat.localtion) # ==> Asia
#类属性,也可以通过类名直接访问
print(Animal.localtion) # ==> Asia
注意:
类属性也是可以动态添加和修改的,需要注意的是,因为类属性只有一份,所以改变了,所有实例可以访问到的类属性都会变更.
6 Python类属性和实例属性的优先级
如果类属性和实例属性名字相同时,会怎么样,这就涉及Python中类属性和实例属性的优先级的问题了。
class Animal(object):
localtion = 'Asia'
def __init__(self, name, age, localtion):
self.name = name
self.age = age
self.localtion = localtion
#接着我们初始化两个实例,并把localtion打印出来。
dog = Animal('wangwang', 1, 'GuangDong')
cat = Animal('mimi', 3, 'ChongQing')
print(dog.localtion) # ==> GuangDong
print(cat.localtion) # ==> ChongQing
print(Animal.localtion) # ==> Asia
可见,在类属性和实例属性同时存在的情况下,实例属性的优先级是要高于类属性的,在操作实例的时候,优先是操作实例的属性。
另外,通过实例,可不可以修改类属性呢?我们来尝试一下:
以上面代码例子:
localtion = 'Asia'
cat.localtion = 'Africa'
print(Animal.localtion) # ==> Asia
这里依然打印了Asia,可见通过实例是无法修改类的属性的,事实上,
通过实例方法修改类属性,只是给实例绑定了一个对应的实例属性:
# 增加的实例属性
print(cat.localtion) # ==> Africa
因此,需要特别注意,尽量不要通过实例来修改类属性,否则很容易引发意想不到的错误。
如果存在类属性,而不存在实例属性,你想通过实力修改类属性,编译时就会报错,
会让你意识到自己操作有问题,不会导致太大的问题;
如果类属性和实例属性名字相同,你想通过实例修改类属性,实际改的是实例属性,
就造成了意想不到的错误;
7 Python中的访问限制
并不是所有的属性都可以被外部访问的,这种不能被外部访问的属性称为私有属性。私有属性是以双下划线’__'开头的属性。
# 类私有属性
class Animal(object):
__localtion = 'Asia'
print(Animal.__localtion)
报错
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'Animal' has no attribute '__localtion'
实例私有属性:
class Animal(object):
def __init__(self, name, age, localtion):
self.name = name
self.age = age
self.__localtion = localtion
dog = Animal('wangwang', 1, 'GuangDong')
print(dog.name) # ==> wangwang
print(dog.age) # ==> 1
print(dog.__localtion)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Animal' object has no attribute '__localtion'
在外部访问私有属性将会抛出异常,提示没有这个属性。
虽然私有属性无法从外部访问,但是,从类的内部是可以访问的。私有属性是为了保护类或实例属性不被外部污染而设计的。
8 Python定义实例方法
私有属性没有办法从外部访问,只能在类的内部操作;那如果外部需要操作私有属性怎么办?
这个时候可以通过定义类或者实例的方法来操作私有属性
先来介绍实例方法。
实例的方法指的就是在类中定义的函数,实例方法的第一个参数永远都是self,self是一个引用,指向调用该方法的实例对象本身,除此以外,其他参数和普通函数是完全一样的。
class Person(object):
def __init__(self, name):
self.__name = name
def get_name(self):
return self.__name
在上面的定义,name是实例的私有属性,从外部是无法访问的,而get_name(self) 就是一个实例方法,在实例方法里面是可以操作私有属性的,注意,它的第一个参数是self。
另外,__ init__(self, name)其实也可看做是一个特殊的实例方法。
通过定义get_name(self)方法,在外部就可以通过这个方法访问私有属性了。
例如:
p = Person('Alice')
print(p.get_name()) # ==> Alice
当然,实例方法并不仅仅是为私有属性服务的,我们可以把和类的实例有关的操作都抽象成实例方法,比如:打印实例的详细信息等等。
class Animal(object):
def __init__(self, name, age, localtion):
self.name = name
self.age = age
self.localtion = localtion
def get_info(self):
return 'name = {}, age = {}, localtion = {}'.format(self.name, self.age, self.localtion)
dog = Animal('wangwang', 1, 'GuangDong')
print(dog.get_info())
任务
把Animal类的age、name、localtion定义成私有属性,并定义对应的方法修改和获取他们的值。
class Animal(object):
def __init__(self, name, age, localtion):
self.__name = name
self.__age = age
self.__localtion = localtion
def set_name(self, name):
self.__name = name
#定义set方法为了修改私有属性的值。也可以在get_name()中添加相应的代码实现。
def get_name(self):#get方法获取
return self.__name
def set_age(self, age):
self.__age = age
def get_age(self):
return self.__age
def set_localtion(self, localtion):
self.__localtion =localtion
def get_localtion(self):
return self.__localtion
9 Python定义类方法
为了操作实例对象的私有属性,我们定义了实例方法;同样的,如果需要需要操作类的私有属性,则应该定义类的方法。
默认的,在class中定义的全部是实例方法,实例方法第一个参数 self 是实例本身。
要在class中定义类方法,需要这么写:
class Animal(object):
__localtion = 'Asia'
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def set_localtion(cls, localtion):
cls.__localtion = localtion
@classmethod
def get_localtion(cls):
return cls.__localtion
print(Animal.get_localtion()) # ==> Asia
Animal.set_localtion('Afica')
print(Animal.get_localtion()) # ==> Africa
注意:
类方法需要使用@classmethod来标记为类方法,否则定义的还是实例方法
类方法的第一个参数将传入类本身,通常将参数名命名为 cls,
上面的 cls.__localtion 实际上相当于Animal.__localtion。
因为是在类上调用,而非实例上调用,因此类方法无法获得任何实例变量,只能获得类的引用。
第2章 Python类的继承
1 什么是类的继承
2 Python继承类
对人类的抽象可以定义为Person类,而学生、老师等,也都是人类,所以,在Python当中,如果定义学生Student的类,可以继承Person类。
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
接着定义Student类,在定义Student类的时候,由于继承了Person类,所以Student类自动拥有name、gender属性,因此,在定义Student类的时候,只需要把额外的属性加上即可。
class Student(Person):
def __init__(self, name, gender, score):
super(Student, self).__init__(name, gender)
self.score = score
student = Student('Alice', 'girl', 100)
print(student.name) # ==> Alice
print(student.gender) # ==> girl
print(student.score) # ==> 100
在定义继承类的时候,需要注意的:
class Student()定义的时候,需要在括号内写明继承的类Person
在__init__()方法,需要调用super(Student, self).__init__(name, gender),
来初始化从父类继承过来的属性
3 Python判断类型
p = Person('Tim', 'Male')
s = Student('Bob', 'Male', 88)
t = Teacher('Alice', 'Female', 'English')
当我们拿到变量 p、s、t 时,可以使用 isinstance 判断类型:
>>> isinstance(p, Person)
True # p是Person类型
>>> isinstance(p, Student)
False # p不是Student类型
>>> isinstance(p, Teacher)
False # p不是Teacher类型
我们再考察 s:
>>> isinstance(s, Person)
True # s是Person类型
>>> isinstance(s, Student)
True # s是Student类型
>>> isinstance(s, Teacher)
False # s不是Teacher类型
s 是Student类型,不是Teacher类型,这很容易理解。但是,s 也是Person类型,
因为Student继承自Person,虽然它比Person多了一些属性和方法,但是,
把 s 看成Person的实例也是可以的。
这说明在一条继承链上,一个实例可以看成它本身的类型,也可以看成它父类的类型。
isinstance也可以用于Python自有数据类型的判断。
s = 'this is a string.'
n = 10
isinstance(s, int) # ==> False
isinstance(n, str) # ==> False
4 Python中的多态
如果我们从 Person 派生出 Student和Teacher ,并都写了一个who() 方法:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def who(self):
return 'I am a Person, my name is %s' % self.name
class Student(Person):
def __init__(self, name, gender, score):
super(Student, self).__init__(name, gender)
self.score = score
def who(self):
return 'I am a Student, my name is %s' % self.name
class Teacher(Person):
def __init__(self, name, gender, course):
super(Teacher, self).__init__(name, gender)
self.course = course
def who(self):
return 'I am a Teacher, my name is %s' % self.name
接着,我们分别把不同类型的who()函数结果打印出来:
p = Person('Tim', 'Male')
s = Student('Bob', 'Male', 88)
t = Teacher('Alice', 'Female', 'English')
运行结果:
I am a Person, my name is Tim
I am a Student, my name is Bob
I am a Teacher, my name is Alice
这种行为称为多态。从定义上来讲,Student和Teacher都拥有来自父类Person继承的who()方法,以及自己定义的who()方法。但是在实际调用的时候,会首先查找自身的定义,如果自身有定义,则优先使用自己定义的函数;如果没有定义,则顺着继承链向上找。
class Boss(Person):
def __init__(self, name, gender,company):
super(Boss, self).__init__(name, gender)
self.company = company
b = Boss('Bob', 'Male', 'Alibaba')
b.who() # ==> I am a Person, my name is Bob
在Boss的定义类,没有定义who方法,所以会顺着继承链向上找到父类的who方法并且调用。
Python中的多重继承
除了从一个父类继承外,Python允许从多个父类继承,称为多重继承。多重继承和单继承没有特别大的差异,只是在括号内加入多个需要继承的类的名字即可。
class A(object):
def __init__(self, a):
print ('init A...')
self.a = a
class B(A):
def __init__(self, a):
super(B, self).__init__(a)
print ('init B...')
class C(A):
def __init__(self, a):
super(C, self).__init__(a)
print ('init C...')
class D(B, C):
def __init__(self, a):
super(D, self).__init__(a)
print ('init D...')
多重继承的继承链就不是一棵树了,它像这样:
从上图可知,A类被继承了连词,那么A的__init__()方法,是否会被调用两次呢?
>>> d = D('d')
init A...
init C...
init B...
init D...
实践证明,在多重继承里,A虽然被继承了两次,但是__init__()的方法只调用一次。
多重继承的目的是从两种继承树中分别选择并继承出子类,以便组合功能使用。
5 Python获取对象信息
通过type()函数,可以获得变量的类型。
n = 1
s = 'this is a string'
type(n) # ==> <class 'int'>
type(s) # ==> <class 'str'>
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
class Student(Person):
def __init__(self, name, gender, score):
super(Student, self).__init__(name, gender)
self.score = score
p = Person('Alice', 'Female')
s = Student('Bob', 'Male', 100)
type(p) # ==> <class '__main__.Person'>
type(s) # ==> <class '__main__.Student'>
通过dir()方法,可以获取变量的所有属性:
>>> n = 1
>>> dir(n)
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', ...]
>>> s = 'this is a string'
>>> dir(s)
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', ...]
在dir列出的属性中,有很多是以下划线开头和结尾的,这些都是特殊的方法,称为内建方法。
对于自定义对象:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def who(self):
return 'I am a Person, my name is {}'.format(self.name)
p = Person('Alice', 'Female')
dir(p)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'gender', 'name', 'who']
对于实例变量,dir()返回所有实例属性,包括__class__这类有特殊意义的属性。注意到方法who也是p的一个属性。
dir()返回的属性是字符串列表,如果已知一个属性名称,要获取或者设置对象的属性,就需要用 getattr() 和 setattr( )函数了。
>>> getattr(p, 'name') # 获取name属性
'Alice'
>>> setattr(p, 'name', 'Adam') # 设置新的name属性
>>> s.name
'Adam'
>>> getattr(s, 'age') # 获取age属性,但是属性不存在,报错:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Person' object has no attribute 'age'
>>> getattr(s, 'age', 20) # 获取age属性,如果属性不存在,就返回默认值20:
20
第3章 Python类的特殊方法
1 什么是类的特殊方法
双下划线开头
双下划线结束
例如:__str__()
2 Python类的__str__ 和 __repr__方法
Python的内建对象,比如int、dict、list等,通过str()方法,可以转换成字符串输出。
num = 12
str(num) # ==> '12'
d = {1: 1, 2: 2}
str(d) # ==> '{1: 1, 2: 2}'
l = [1,2,3,4,5]
str(l) # ==> '[1, 2, 3, 4, 5]'
自定义的对象,通过str()方法,同样可以得到对象所对应的字符串结果,只不过结果会有些难理解。
class Person:
pass
bob = Person()
str(bob) # ==> '<__main__.Person object at 0x7fc77b859c50>'
<__main__.Person object at 0x7fc77b859c50>这个结果其实是Animal的实例cat在内存中的地址,
这是相当难以理解的
这其实是对象的内建方法__str__返回的。
str()函数似乎没有在自定义类Person中生效,这是为什么呢?
这是因为 Python 定义了__str()和__repr()两种方法,str()用于显示给用户,而__repr()用于显示给开发人员。
**==当使用str()时,实际调用的是 str ()方法,而直接输入变量,调用的是 repr __()方法。==**
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __str__(self):
return 'name: {}, gender: {}'.format(self.name, self.gender)
def __repr__(self):
return 'name: {}, gender: {}'.format(self.name, self.gender)
bob = Person('Bob', 'Male')
str(bob) # ==> 'name: Bob, gender: Male'
>>> bob
'name: Bob, gender: Male'
3 Python类的__len__方法
如果一个类表现得像一个list,想使用len()函数来获取元素个数时,则需要实现__len__()方法。
class Class:
def __init__(self, students):
self.students = students
def __len__(self):
return len(self.students)
students = ['Alice', 'Bob', 'Candy']
class_ = Class(students)
len(class_) # ==> 3
通过自定义__len__()方法,可以让len()函数返回相关的结果,如果没有定义__len__()方法的类使用len()函数获取长度时,将会引起异常。
4 Python类的数学运算
ython很多的操作都是通过内建函数来实现的,比如最熟悉的加减乘除,都是通过内建函数来实现的,分别是__add__、sub、mul、truediv。
5 Python类的__slots__方法
例如,Student类只允许添加 name、gender和score 这3个属性,就可以利用Python的一个特殊的__slots__来实现。
class Student(object):
__slots__ = ('name', 'gender', 'score')
def __init__(self, name, gender, score):
self.name = name
self.gender = gender
self.score = score
使用__slots__ = (‘name’, ‘gender’, ‘score’) 限定Student类的属性,这个时候在外部再次添加动态属性age,将会报错。
6 Python类的__call__方法
在Python中,函数其实是一个对象,我们可以将一个函数赋值给一个变量,而不改变函数的功能。
如果把一个类实例也变成一个可调用对象,可以实现一个特殊的方法__call__()。
例如,我们把Person类变成一个可调用对象:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __call__(self, friend):
print('My name is {}...'.format(self.name))
print('My friend is {}...'.format(friend))
接着我们初始化一个Person对象,并对这个对象通过函数的方式调用:
>>> p = Person('Bob', 'Male')
>>> p('Alice') # ==> 用函数的方式调用Person类的实例p
My name is Bob...
My friend is Alice...
第4章 Python的模块
1 Python模块和包
#main.py //自身模块
import tools //导入自定义tools.py模块
或
from network import tools //导入network包的tools.py模块
print(tools.say_hello())//调用tools.py模块函数
python包的文件夹必须有__init__.py文件
2 Python定义模块
# tools.py
def say_hello():
print('hello')
def say_goodbye():
print('goodbye')
这样就定义了一个叫tools的模块,接着就可以使用这个模块了
3 Python导入模块
Python使用import语句导入一个模块,Python官方提供很多有用的模块,比如:os模块、sys模块、time模块、math模块等等。
导入官方模块,不需要考虑路径的问题,例如,导入系统自带的模块 math,直接导入即可。如果是导入自定义模块,则需要考虑路径问题。
如果希望导入模块的指定部分属性或函数,那么使用from…import…语句。
>>> from math import pi
>>> print(pi)
3.141592653589793
由于pow()函数没有导入,所以是不能使用的
如果希望导入模块里面的所有内容,那么使用from …import *语句。
>>> from math import *
>>> print(pi)
3.141592653589793
>>> pow(2, 3)
8.0
有时候导入模块函数名字重复会发生冲突,两种解决办法:
第一种是直接导入模块,不指定导入模块里面的具体内容;第二种方法就是使用from … import as …语句,as类似重命名,可以把导入的函数或属性重命名为别的名字。
>>> from math import pow as mathpow
>>> mathpow(2, 3)
8.0
4 Python模块导入的路径
如果需要导入自定义模块,则需要了解Python导入模块搜索的路径。
通过sys模块,可以知道导入模块的路径。
>>> import sys
>>> sys.path
['', '/data/miniconda3/lib/python3.8', '/data/miniconda3/lib/python3.8/site-packages']
它返回的是一个列表,表示的是在搜索Python模块时,会搜索的路径,在示例中,返回了四个路径。我们分析一些关键路径:
第一个路径是'',它是一个空字符串,表达的是当前路径的意思。
第二个路径是/data/miniconda3/lib/python3.8,它是Python默认模块的存放的路径,在这个路径下,可以发现有os、sys等模块的代码。
第三个路径是/data/miniconda3/lib/python3.8/site-packages,它是第三方模块代码的存放路径,在这个路径下,存放的是需要安装的第三方模块。
那如何使用我们前面定义的tools.py模块呢?
我们在tools.py同级目录,创建main.py文件:
# main.py
import tools # 导入模块
tools.say_hello() # 调用模块里面的say_hello()函数
tools.say_goodbye() # 调用模块里面的say_goodbye()函数
在同一个目录内的tools.py模块,可以被搜索到,所以能够import进来。
Python的sys.path返回的是一个路径列表,因此可以操作列表里面的元素,
通过sys.path增加路径’…/’,使得在运行时,可以导入当前目录上级目录的包。
import sys
sys.path.append('../')
5 Python安装第三方模块的方法
尽管Python的官方模块已经提供了非常强大的能力,但是仍有大量热心开发者提供了非常好用的第三方库,在实际开发中,也会经常使用,比如Web开发框架,Django、Flask,异步任务框架:Celery等。
在安装Python的时候,Python环境提供了安装第三方模块的工具:pip,通过这个工具,可以非常快捷的安装第三方模块。
安装Django模块:
pip install django
卸载Django模块:
pip uninstall django
第5章 Python读写文件
1 向Python程序输入内容
使用input()函数可以接收外部的输入。
注意:input输入的是字符串,需要转型为数字类型。
num = input('please input number: ')
num = int(num)
result = 1
for i in range(1, num):
result = result * i
print(result)
eval()函数可以把字符串转换为等值的结果,比如eval(‘1+1’),得到结果为2。
2 Python打开文本文件
open()函数,可以打开一个文件,得到一个文件file对象,而file对象提供相关的方法对文件内容进行读写等操作。
文件路径:指定需要打开的文件的文件路径
打开模式:针对不同文件(二进制文件、文本文件)以及不同操作(读操作、写操作),会有不同的打开模式
编码:设定打开文件的默认编码
常用的打开模式如下:
模式 | 描述 |
---|---|
t | 文本模式(默认) |
x | 写模式,新建一个文件 |
b | 二进制模式,打开二进制文件 |
+ | 更新一个文件(可读可写) |
r | 以只读模式打开一个文件 |
rb | 以二进制格式只读模式打开一个文件 |
w | 打开一个文件进行写入,如果文件内容已存在,会清除原有的内容 |
wb | 以二进制格式只写模式打开一个文件,会清除原有的内容 |
a | 打开一个文件并追加内容,会往文件尾部添加内容 |
ab | 以二进制格式打开一个文件并追加内容,会往文件尾部添加内容 |
w+ | 打开一个文件进行读写,如果文件内容已存在,会清除原有的内容 |
a+ | 打开一个文件并使用追加进行读写 |
文件使用完毕后,需要使用close()函数正确关闭。
3 Python打开二进制文件
非文本文件,比如图片、压缩文件、视频文件、音乐文件等等,这种文件统称为二进制文件。
用只读方式打开二进制文件:
f = open('test.jpg', 'rb')
f.close()
4 Python读取文件内容
read()方法,可以读取文件中的若干个字符,它提供一个参数size,可以指定读取字符的数量。
比如打开了一个文件:
f = open('test.txt', 'r') # 打开test.txt文件
f.close() # 关闭文件
test.txt文件有以下内容
Hello World.
Hello Python.
Hello Imooc.
使用read读取一下
s = f.read(5)
print(s) # ==> Hello
当read()之后,访问文件的游标就会移动到第六个字符前面,
此时,继续read,将得到Hello后面的结果。
s = f.read(6)
print(s) # ==> ' World'
读取一行
readline()方法,和read()方法类似,可以读取文件中的若干个字符,它也提供一个参数size,可以指定读取字符的数量,不过和read()方法不同的是,readline()方法遇到一行结束的时候,就会返回。
f.close()
f = open('test.txt', 'r') # 重新打开文件
s = f.readline(20)
print(s) # ==> 'Hello World.\n'
可以看到,打印的内容并没有20个字符,readline最多返回一行的所有字符。
读取多行
readlines()方法,可以读取多行字符,返回一个列表。它提供一个hint参数,表示指定读取的行数,没有指定则默认以列表的形式返回文件所有的字符串。
f.close()
f.open('test.txt', 'r')
s = f.readlines()
print(s) # ==> ['Hello World.\n', 'Hello Python.\n', 'Hello Imooc.\n']
5 Python把字符串写入文件
写入若干字符
write方法向文件内写入若干字符,它接受一个字符串参数,表示需要写入的字符串。
f = open('test.txt', 'w')
f.write('Hello World\n')
f.close()
写入若干行
writelines()方法向文件内容写入多行数据,它接受一个列表,表示需要写入的字符串列表。
lines = ['Hello World\n', 'Hello Python\n', 'Hello Imooc\n']
f = open('test.txt', 'w')
f.writelines(lines)
f.close()
reverse()数据反转:
是python中列表的一个内置方法(也就是说,在字典,字符串或者元组中,是没有这个内置方法的),用于列表中数据的反转;
lista = [1, 2, 3, 4]
lista.reverse()
print(lista)
打印结果:
[4, 3, 2, 1]
6 Python往文件追加内容
通过w的打开方式打开文件,会清空文件的内容,这在很多场景下是不合适的,比如写系统日志的时候,需要累积随时间推移的所有数据。
使用a的打开方式打开文件,文件游标默认是在文件的尾部,因此,可以便捷的往文件尾部添加内容,除此以外,文件对象还提供seek()方法,可以移动文件的游标位置,它接受一个参数,表示文件的位置,0:文件首部,1:当前位置,2:文件尾部,通过seek()可以把文件游标移动到文件首部但不删除文件的内容。
f = open('test.txt', 'a+')
content = f.readlines()
print(content) # ==> []
f.seek(0)
content = f.readlines()
print(content) # ==> ['Hello World\n', 'Hello Python\n', 'Hello Imooc\n']
第一次print(content)的时候,由于文件游标在文件的尾部,所以readlines()读取不到任何数据,打印了空的结果,第二次print(content)的时候,由于通过seek(0),文件游标移动到了文件的首部,因此readlines()就返回了文件所有的内容。
7 Python正确关闭文件
close()方法,也不是100%安全的,如果在close()文件之前,程序异常退出了,那么文件也得不到正确的关闭。
with关键字可以解决这个问题
with关键字对资源进行访问的场合,会确保不管在使用过程中是否发生异常,都会执行必要的“清理”的操作,释放资源,比如文件使用后自动关闭等等。
with的使用方法如下:
with open('test.txt', 'r') as f:
content = f.readlines()
for line in content:
print(line)
当文件使用结束后,不需要显式的调用f.close()关闭文件。
第6章 Python的网络编程
1 什么是网络编程
要进行网络通信,需要建立起通信双方的连接,连接的双方分别称为客户端和服务端。
在Python中,使用套接字socket来建立起网络连接。
套接字包含在socket模块中:
import socket
socket.socket()
2 套接字Socket与套接字编程
服务端建立需要四个步骤:新建socket、绑定IP和端口(bind)、监听连接(listen)、接受连接(accept)。
客户端建立则简单一些,仅需两个步骤:新建socket、连接服务端(connect)。
当网络连接上以后,客户端和服务端就可以进行数据通信了,套接字通过send()函数发送数据,通过recv()函数接收数据。
服务端的过程,新建一个server.py的文件:
import socket
server = socket.socket() # 1. 新建socket
server.bind(('127.0.0.1', 8999)) # 2. 绑定IP和端口(其中127.0.0.1为本机回环IP)
server.listen(5) # 3. 监听连接
s, addr = server.accept() # 4. 接受连接
print('connect addr:{}'.format(addr))
content =s.recv(1024)
print(str(content, encoding='utf-8')) # 接受来自客户端的消息,并编码打印出来
s.close()
客户端,新建一个client.py的文件:
import socket
client = socket.socket() # 1. 新建socket
client.connect(('127.0.0.1', 8999)) # 2. 连接服务端(注意,IP和端口要和服务端一致)
client.send(bytes('Hello World. Hello Socket', encoding='utf-8')) # 发送内容,注意发送的是字节字符串。
client.close()
接着在两个终端运行这两个文件,成功之后在服务端的终端,将会输出以下信息:
connect addr:('127.0.0.1', 50382)
b'Hello World. Hello Socket'
3 Python自带的HTTP服务器
4 Python发送HTTP请求
除了使用浏览器发送HTTP请求,通过代码也可以向HTTP服务器发送请求,Python提供了相关的库urllib,通过urllib包里面的request,可以向其他HTTP服务器发起请求。
from urllib import request
response = request.urlopen('https://www.imooc.com') # 向慕课网官网发出请求
print(response) # ==> <http.client.HTTPResponse object at 0x000001377D631948>
这是HTTPResponse附带的一些信息,包括服务端的服务器是什么、请求时间、内容类型、内容长度等等。
Server: openresty //服务端的服务器
Date: Thu, 20 Aug 2020 08:16:07 GMT //请求时间
Content-Type: text/html; charset=UTF-8 //内容类型
Content-Length: 376639 //内容长度
5 Python的requests库
requests库是著名的Python第三方库,使用requests库,可以定制化你的HTTP请求,包括请求方法,请求参数等等。
由于requests是第三方库,因此在使用前,需要安装。
pip install requests
安装完成后,使用requests库来请求慕课网官网。
response = requests.get('https://www.imooc.com')
# 打印状态码
print(response.status_code)
# 打印回应头
print(response.headers)
6 HTTP响应的内容
网络爬虫是典型的应用程序,它的工作原理就是通过不断的请求互联网的页面,并从回应中解析获取出有用的数据;数据积累后,可以有很多用处。
过滤出来的信息或许有些杂乱,但也包含了一些有用的数据,我们可以从过滤后的信息中找到链接相关的信息。不过我们过滤的方式比较简单,通过优化过滤的方式可以匹配到更加精准的数据。而爬虫正是这样工作的。
第7章 Python的函数式编程
1 什么是函数式编程
2 Python把函数作为参数
计算平方根可以利用math包的sqrt()函数。
import math
def add(x, y, f): //参数 x, y 和 f 都可以任意传入,如果 f 传入其他函数,就可以得到不同的返回值。
return f(x) + f(y)
print(add(25, 9, math.sqrt)) //计算25和9开方之后的和,结果为8
3 Python的map()函数
map()是 Python 内置的高阶函数,它接收一个函数 f 和一个 list,并通过把函数 f依次作用在list的每个元素上,map()函数会返回一个迭代器,可以依次迭代得到原来list的元素被函数f处理后的结果。
传入函数f(x)=x*x,就可以利用map()函数完成这个计算:
def f(x):
return x*x
for item in map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]):
print(item)
得到结果:
[1, 4, 9, 10, 25, 36, 49, 64, 81]
4 Python的reduce()函数
reduce()函数被收录到functools包内,需要引入functools才可以使用。
例如,编写一个f函数,接收x和y,返回x和y的和:
def f(x, y):
return x + y
#调用 reduce(f, [1, 3, 5, 7, 9]):
from functools import reduce
def f(x, y):
return x + y
print(reduce(f, [1,3,5,7,9])) # ==> 25
#计算过程
先计算头两个元素:f(1, 3),结果为4;
再把结果和第3个元素计算:f(4, 5),结果为9;
再把结果和第4个元素计算:f(9, 7),结果为16;
再把结果和第5个元素计算:f(16, 9),结果为25;
由于没有更多的元素了,计算结束,返回结果25。
5 Python的filter()函数
利用filter()函数,可以完成很多很有用的功能,例如,删除 None 或者空字符串:
def is_not_empty(s):
return s and len(s.strip()) > 0
for item in filter(is_not_empty, ['test', None, '', 'str', ' ', 'END']):
print(item)
结果:test, str, END
注意: s.strip()会默认删除空白字符(包括'\n', '\r', '\t', ' '),如下:
s = ' 123'
s.strip() # ==> 123
s= '\t\t123\r\n'
s.strip() # ==> 123
6 Python自定义排序函数
Python内置的 sorted()函数可对list进行排序:默认是由小到大排序
>>> sorted([36, 5, 12, 9, 21])
[5, 9, 12, 21, 36]
当list的每一个元素又是一个容器时,则会以第一个元素来排序,比如在score中,每个元素都是包含名字和成绩的一个tuple,sorted()函数则按名字首字母进行了排序并返回。
>>> score = [('Alice', 72), ('Candy', 90), ('Bob', 62)]
>>> sorted(score)
[('Alice', 72), ('Bob', 62), ('Candy', 90)]
让sorted()函数按照成绩来进行排序:
def k(item):
return item[1] # ==> 按成绩排序,成绩是第二个字段
sorted(score, key=k)
得到结果:[('Bob', 62), ('Alice', 72), ('Candy', 90)] 。
如果需要倒序,指定reverse参数即可。
sorted(score, key=k, reverse=True)
得到结果:[('Candy', 90), ('Alice', 72), ('Bob', 62)] 。
7 Python返回函数
def f():
print('call f()...')
# 定义函数g:
def g():
print('call g()...')
# 返回函数g:
return g
>>> x = f() # 调用f()
call f()...
>>> x # 变量x是f()返回的函数:
<function f.<locals>.g at 0x7f4a4936dbf8>
>>> x() # x指向函数,因此可以调用
call g()... # 调用x()就是执行g()函数定义的代码
有必要注意的是,返回函数和返回函数值的语句是非常类似的,返回函数时,不能带小括号,而返回函数值时,则需要带上小括号以调用函数。
8 Python的闭包
将g的定义移入函数 f 内部,防止其他代码调用 g:
def f():
print('f()...')
def g():
print('g()...')
return g
像这种内层函数引用了外层函数的变量(参数也算变量),然后返回内层函数的情况,称为闭包(Closure)。
闭包的特点是返回的函数还引用了外层函数的局部变量,所以,要正确使用闭包,就要确保引用的局部变量在函数返回后不能变。举例如下:
# 希望一次返回3个函数,分别计算1x1,2x2,3x3:
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果全部都是 9
正确例子:
def count():
fs = []
for i in range(1, 4):
def f(j):
def g():
return j*j
return g
r = f(i)
fs.append(r)
return fs
f1, f2, f3 = count()
print(f1(), f2(), f3())
9 Python的匿名函数
匿名函数使用lambda定义:lambda x: x * x,就可以完成原来显式定义的f(x)函数的功能,冒号前面的x表示匿名函数的参数,后面的是一个表达式,匿名函数有个限制,就是只能有一个表达式,不写return,返回值就是该表达式的结果。
result = [item for item in map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])]
print(result) # ==> [1, 4, 9, 16, 25, 36, 49, 64, 81]
10 Python编写无参数的decorator
Python的 decorator 本质上就是一个高阶函数,它接收一个函数作为参数,然后,返回一个新函数。
使用 decorator 用Python提供的 @ 语法,这样可以避免手动编写 f = decorate(f) 这样的代码。
@log 是Python特有的一种函数简写方式,相当于factorial=log(factorial),将factorial()函数作为log函数的参数进行调用
@log
def factorial(n):
return reduce(lambda x,y: x*y, range(1, n+1))
print(factorial(10))
结果:
call factorial()...
3628800
@log 写死了只含一个参数的返回函数。不是一个参数调用会报错