python基础语法

python基础语法,第1张

来源于Python入门+数据分析,供复习使用

Python介绍 诞生和发展

特点
  • 开源、易于维护
  • 可移植
  • 易于使用、简单优雅
  • 广泛的标准库、功能强大
  • 可扩展、可嵌入

缺点:运行速度慢,Python是解释型语言,运行时翻译为机器码非常耗时,而java、C语言是运行前直接编译成CPU能执行的机器码,但是大量的应用程序不需要这么快的运行素服,因为用户感觉不出来。

pycharm代码模板

设置中检索 template,找到python scripy:

# -*- coding:utf-8 -*-
#@Time : ${DATE} ${TIME}
#@Author : ysk
#@File :${NAME}.py
#@Software: ${PRODUCT_NAME}
基础语法 格式化输出
age = 10
print("我今年%d岁"%age)

age = 18
name = "hello"
print("我的姓名是%s,年龄是%d"%(name,age))

print("1234567890-------") # 会在一行显示
print("1234567890\n-------") # 一行显示1234567890,另外一行显示-------
print("aaa","bbb","ccc") # 打印输出,值之间空格隔开且不换行
print("www","baidu","com",sep=".") # 打印输出,值之间用点连接且不换行
print("hello",end="")# 打印输出,值之间不分开开且不换行
print("world",end="\t") # 打印输出,值之间用一个制表符隔开且不换行
print("python",end="\n") # 打印输出,值之间用一个换行隔开
print("end")
#结果:
#aaa bbb ccc
#www.baidu.com
#helloworld	python
#end
输入
password = input("请输入密码:")
print('您刚刚输入的密码是:', password)
print(type(password))

c = int("123")
print(type(c)) #
  • input()的小括号中放入的是,提示信息,用来在获取数据之前给用户的一个简单提示

  • input()在从键盘获取了数据以后,会存放到等号左边的变量中

  • input()函数接受的输入必须是表达式

运算符

和其他语言类似。

条件判断语句
a = 10
if a > 1:
    print("1")
    print("jjjj")  # 同一个层次下的语句缩进必须一样!
else:
    print("0")
print("end")    
score = 77
if 90 <= score <= 100:
    print('本次考试,等级为A')
elif 80 <= score < 90:
    print('本次考试,等级为B')
elif 70 <= score < 80:
    print('本次考试,等级为C')
elif score >= 60 and score < 70:
    print('本次考试,等级为D')
# elif score>=0 and score<60:
else:  # elif可以else一起使用
    print('本次考试,等级为E')
xingBie = 1 # 用1代表男生,0代表女生
danShen = 1 # 用1代表单身,0代表有男/女朋友
if xingBie == 1:
    print("是男生")
    if danShen == 1:
        print("我给你介绍一个吧?")
    else:
        print("你给我介绍一个呗?")
else:
    print("你是女生")
print("……")
import 与 from…import

在 python 用 import 或者 from…import 来导入相应的模块。

将整个模块(somemodule)导入,格式为: import somemodule

从某个模块中导入某个函数,格式为: from somemodule import somefunction

从某个模块中导入多个函数,格式为: from somemodule import firstfunc, secondfunc, thirdfunc

将某个模块中的全部函数导入,格式为: from somemodule import \*

【生成随机数】

import random #引入随机库
computer = random.randint(0,2) #随机生成0、1、2中的一个数字,赋值给变量computer
循环语句 for循环

格式:

for 临时变量 in 列表或者字符串等:
	循环满足条件时执行的代码
name = 'hello'
for item in name:
    print(item)

for i in range(5):
    print(i) #0 1 2 3 4
    
for i in range(0,11,3): #从0开始到11结束 步进值是3
    print(i)
for i in range(-10,-100,-20):
    print(i)

a = ["aa","bb","cc"]
for i in range(len(a)):
    print(i,a[i])
#0 aa
#1 bb
#2 cc  
while
i = 0
while i<5:
	print("当前是第%d次执行循环"%(i+1))
	print("i=%d"%i)
	i+=1
break,continue,pass

break,continue和其他编程语言意义相同

pass是空语句 一般用作占位语句 不做任何事情

字符串、列表、元组、字典 字符串

Python中的字符串可以使用单引号、双引号和三引号(三个单引号或三个双引号)括起来,表示一个字符串

三引号可以保留格式

# 方式一:
word = '字符串'
# 方式二:
sentence = "则会是一个句子"
# 方式三:
parapraph = """
        想没有用,要实际 *** 作!
"""
print(word)
print(sentence)
print(parapraph)

#切片
str = "hello"
print(str[0])
print(str[0:2])  #[起始位置:结束位置:步进值]
print((str[0:5:2]))
print(str[2:]) # 从2到最后
print(str[:2]) #从0到2

print(r"hello\nchengdu")  #前面有r代表后面的\不解释  直接进行输出
#字符串连接使用+号
单引号和双引号的选择
  1. 包含单引号的字符串

    假如你想定义一个字符串my_str,其值为: I’m a student,则可以采用如下方式,通过转义字符 \ 进 行定义。

    my_str = 'I\'m a student'
    

    也可以不使用转义字符,利用双引号直接进行定义。

    my_str = "I'm a student"
    
  2. 包含双引号的字符串

    假如你想定义一个字符串my_str,其值为: Jason said “I like you” ,则可以采用如下方式,通过转义 字符 \ 进行定义。

    my_str = "Jason said \"I like you\""
    

    也可以不使用转义字符,利用单引号直接进行定义。

    my_str = 'Jason said "I like you"'
    
字符串常用 *** 作
序号方法描述
1bytes.decode(encoding=“utf-8”, errors=“strict”)Python3 中没有 decode 方法,但我 们可以使用 bytes 对象的 decode() 方法来解码给定的 bytes 对象,这个 bytes 对象可以由 str.encode() 来编码返回。
2encode(encoding=‘UTF-8’,errors=‘strict’)以 encoding指定的编码格式编码字符串,如 果出错默认报一个ValueError 的异常,除非 errors 指定的是’ignore’或者’replace’
3isalnum() 如果字符串至少有一个字符并且所有字符都是字母或数字则返 回 True,否则返回 False如果字符串至少有一个字符并且所有字符都是字母或数字则返 回 True,否则返回 False
4isdigit()如果字符串只包含数字则返回 True 否则返回 False.
5isnumeric()如果字符串中只包含数字字符,则返回 True,否则返回 False
6join(seq)以指定字符串作为分隔符,将 seq 中所有的元素(的字符串表示)合并为一个新的 字符串
7len(string)返回字符串长度
8lstrip()截掉字符串左边的空格或指定字符。
9rstrip()删除字符串字符串末尾的空格
10split(str=“”, num=string.count(str)) num=string.count(str))以 str 为分隔符截取字 符串,如果 num 有指定值,则仅截取 num+1 个子字符串
列表 List
  • 列表可以完成大多数集合类的数据结构实现。
  • 列表中元素的类型可以不相同,它支持数字,字符串甚至可以包含列表(所谓嵌套)。
  • 列表是写在方括号之间、用逗号分隔开的元素列表。
  • 列表索引值以0为开始值,-1为从末尾的开始位置。列表可以使用+ *** 作符进行拼接,使用*表示重复。
列表定义与访问
  • 列表的格式

    变量A的类型为列表

    namesList = ['xiaoWang','xiaoZhang','xiaoHua']
    testList = [1, 'a']
    
  • 打印列表

    namesList = ['xiaoWang','xiaoZhang','xiaoHua']
    print(namesList[0])
    print(namesList[1])
    print(namesList[2])
    
  • 列表的循环遍历

    namesList = ['xiaoWang','xiaoZhang','xiaoHua']
    for name in namesList:
    print(name)
    
常用 *** 作
方法 *** 作名称解释 *** 作
list[index]访问列表中的元素通过下标直接访问列表中的元素
list[start:end:length]列表的切片使用[开始下标索引:结束下标索引 :步进值 ],注意范围区间是"左闭右开"
for i in list:print(i)遍历列表元素for循环
list.append(values)【增】追加数据到列表中尾部将新的元素值追加到当前列表中末尾位置
list.extend(list1)【增】列表的追加将其他列表的元素添加到当前列表中
list.insert(index,value)【增】列表数据插入根据给定索引指定位置插入元素
del list[index]
list.remove(value)
【删】列表的删除del :我们通过索引删除指定位置的元素。
remove:移除列表中指定值的第一个匹配值。如果没找到的话,会抛异常
list.pop()【删】d出列表尾部元素d出当前列表尾部元素,相当于删除元素
list[index] = 8【改】更新列表中的数据通过下标修改指定元素
value in list value not in list【查】列表成员关系in not in
value in list value not in list【查】列表成员关系in not in
list.count(value)【查】查找元素出现次数统计指定元素在当前列表中出现的次数
list3 = list1 +list2列表的加法 *** 作+
list.sort()【排】列表的排序将当前列表元素进行排序(升序)
list.reverse()【排】列表的反转将列表所有元素进行反转 a.sort(reverse=True)
len()获取列表长度获取当前列表长度
max()获取列表元素最大值获取列表元素最大值
min()获取列表元素最小值获取列表元素最小值
list()其他类型对象转换成列表其他类型对象转换成列表
列表嵌套

一个列表中的元素又是一个列表,就是列表嵌套,说白了,就是一个二维数组。

# 列表嵌套的定义:
schoolNames = [['北京大学','清华大学'],['南开大学','天津大学','天津师范大学'],['山东大学','中国海洋大学']]
# 列表嵌套取出元素
print(schoolNames[0][0])
元组 Tuple 定义与访问

tuple与list相似,不同之处在于tuple的元素写在小括号里,元素之间用逗号隔开。

  • 创建空元组

    tup1 = ()
    
  • 元组的定义

    tup1 = (50) # 不加逗号,类型为整型
    print(type(tup1)) #输出
    
    tup1 = (50,) # 加逗号,类型为元组
    print(type(tup1)) #输出
    
  • 元组的访问

    tup1 = ('Google', 'baidu', 2000, 2020)
    tup2 = (1, 2, 3, 4, 5, 6, 7 )
    print ("tup1[0]: ", tup1[0])
    print ("tup2[1:5]: ", tup2[1:5])
    
  • 元组中的元素值是不允许修改的,但我们可以对元组进行连接组合,如下实例:

    tup1 = (12, 34.56)
    tup2 = ('abc', 'xyz')
    # 以下修改元组元素 *** 作是非法的。
    # tup1[0] = 100
    # 创建一个新的元组
    tup3 = tup1 + tup2
    print (tup3)
    
  • 删除元组后,再次访问会报错

    tup = ('Google', 'baidu', 2000, 2020)
    print (tup)
    del tup
    print ("删除后的元组 tup : ")
    print (tup)
    
常用 *** 作
方法 *** 作名称解释 *** 作
tuple[index]访问元组中的元素 *** 作名称通过下标直接访问元组中的元素解释 *** 作
for i in tuple:print(i)遍历元组for循环
tuple[start:end:length]元组的切片使用[开始下标索引:结束下标索引 :步进值 ],注意范围区间是"左闭右开"
value in tuple value not in tuple【查】元组成员关系in not in
tuple.count(value)【查】查找元素出现次数统计指定元素在当前元组中出现的次数
tuple3 = tuple1+tuple2元组的加法 *** 作元组的新增方法
len()获取元组长度获取元组列表长度
max()获取元组元素最大值获取元组元素最大值
min()获取元组元素最小值获取元组元素最小值
tuple()其他类型对象转换成元组其他类型对象转换成元组
  • 定义好的元组的元素不可修改,但可以包含对象,如list
  • 删除时,是删除整个元组,不是元组中的元素
  • 定义一个只有一个元素的tuple,必须加逗号
字典 dict 字典的定义

字典是无序的对象集合,使用键值对(key-value)存储,具有极快的查找速度

变量info为字典类型:

info = {'name':'班长', 'id':100, 'sex':'f', 'address':'地球亚洲中国北京'}

说明:

  • 字典和列表一样,也能够存储多个数据
  • 列表中找某个元素时,是根据下标进行的
  • 字典中找某个元素时,是根据’名字’(就是冒号:前面的那个值,例如上面代码中 的’name’、‘id’、‘sex’)
  • 字典的每个元素由2部分组成,键:值。例如 ‘name’:‘班长’ ,'name’为键,'班长’为值
根据键访问值
info = {'name':'吴彦祖','age':18}
print(info['age']) # 获取年龄
# print(info['sex']) # 获取不存在的key,会发生异常
print(info.get('sex')) # 获取不存在的key,获取到空的内容,不会出现异常

若访问不存在的键,则会报错:

info['sex']
Traceback (most recent call last):
File "", line 1, in <module>
KeyError: 'sex'

在我们不确定字典中是否存在某个键而又想获取其值时,可以使用get方法,还可以设置默认值:

>>> age = info.get('age')
>>> age #'age'键不存在,所以age为None
>>> type(age)
<type 'NoneType'>
>>> age = info.get('age', 18) # 若info中不存在'age'这个键,就返回默认值18
>>> age
18
常用 *** 作
*** 作名称 *** 作方法举例
dict[key]访问字典中的元素通过key访问,key不存在会抛出异常
dict.get(key)访问字典中的元素通过get方法,不存在返回None,不抛出异常
for key in dict: print(key,dict[key])遍历字典通过for循环,只能获取key,values需要使用dict[key]获取
for key,value in dict.items():print(key,value)遍历字典配合items方法,获取key和value
dict.keys() dict.values() dict.items()获取所有key 获取所有value 获取所有的key-value使用keys和values方法
dict[key] = newvalue修改value直接通过key来修改value
dict[newkey] = newvalue新增键值对直接新增
del dict[key]删除字典元素通过key删除字典元素
dict.pop(key)d出字典元素通过keyd出字典元素
key in dict判断key是否存在in
dict.update(dict2)合并字典
dict(zip(list1,list2))把列表转化成字典把两个列表转为字典
dict2 = dict([[‘key1’,‘value1’]])把一个嵌套列表转为字典把一个嵌套列表转为字典
dict.clear()清除字典内的元素清除字典内的元素

代码:

info = {'name':'qzp','age':'24','sex':'男'}
# 查
print(info['name']) # 根据主键获取值
print(info['age'])
print(info['sex'])
for i in info: # 遍历只能拿到主键
    print(i)
print(info.keys()) # 得到所有的主键列表:dict_keys(['name', 'age', 'sex'])
print(info.values()) #得到所有的值列表:dict_values(['qzp', '24', '男'])
print(info.items()) #得到所有的项列表dict_items([('name', 'qzp'), ('age', '24'), ('sex', '男')])
# 增
newId = input("请输入你加入的id")
info['id'] = newId
print("_"*20) # 打印分割线
print('新增后遍历字典元素')
for i in info:
    print(i,info[i])
# 删
# del 
del info['id'] # 根据key删除相对应的value
print("删除后的遍历字典元素")
for i in info:
    print(i,info[i])
del info; # 删除字典
# clear :清除字典中的所有元素
info.clear(info);
# 改
info['name'] = 'wjw'
for i in info:
    print(i,info[i])
# 遍历
# 遍历所有的主键
for i in info.keys():
    print(i)
# 遍历所有的值
for i in info.values():
    print(i)
# 遍历所有的key-value
for key,value in info.items():
    print(key,value)

    
myList = ['a','b','c','d','e','f']
for i,x in enumerate(myList): # 使用枚举函数,同时获取列表中的下标索引和下标对应的元素
    print(i+1,x)
集合 set 定义
  • set是一组key的集合,但不存储value
  • key不可以重复
  • set是无序的,重复的元素会被自s动过滤
常用 *** 作
方法 *** 作名称解释 *** 作
for i in myset:print(i)遍历集合通过for循环遍历集合中的元素
myset.update(set1)更新集合将其他集合中的元素追加到当前集合中
myset.add(key)添加元素向当前集合中添加元素
myset.remove(key)删除元素移除当前集合中存在的元素,key不存在会报错
val = myset.pop()d出元素随机d出当前集合中的一个元素,val代表被d出的元素
myset.clear()清除元素清除当前集合的所有元素
del myset删除集合删除整个集合
len()长度获取集合长度
max()最大值获取集合最大的元素
min()最小值获取集合最小的元素
set()转换其他类型对象转换成集合
小结
是否有序是否可变类型
列表[ ]有序可变类型
元组( )有序不可变类型
字典{ }无序 key不可变 val可变
集合{ }无序可变类型(不重复)
函数 定义与调用

格式:

def 函数名():
	代码
# 定义一个函数,能够完成打印信息的功能
def printInfo():
    print '------------------------------------'
    print ' 人生苦短,我用Python'
    print '------------------------------------'

定义了函数之后,就相当于有了一个具有某些功能的代码,想要让这些代码能够执行,需要调用它 调用函数很简单的,通过 函数名() 即可完成调用

# 定义完函数后,函数是不会自动执行的,需要调用它才可以
printInfo()
函数参数 定义、调用带有参数的函数
def add2num(a, b):
    c = a+b
    print c
    
add2num(11, 22) #调用带有参数的函数时,需要在小括号中,传递数据    
函数返回值 带返回值的函数

一个函数到底有没有返回值,就看有没有return,因为只有return才可以返回数据

def add2num(a, b):
	c = a+b
	return c

#调用函数,顺便保存函数的返回值
result = add2num(100,98)
#因为result已经保存了add2num的返回值,所以接下来就可以使用了
print result
在python中返回多个值
>>> def divid(a, b):
... shang = a//b
... yushu = a%b
... return shang, yushu
...
>>> sh, yu = divid(5, 2)
>>> sh
5
>>> yu
1
#本质是利用了元组
文件 *** 作

文件,就是把一些数据存放起来,可以让程序下一次执行的时候直接使用,而不必重新制作一份,省 时省力。

文件打开与关闭 打开文件

在python,使用open函数,可以打开一个已经存在的文件,或者创建一个新文件

open(文件名,访问模式)

示例如下:

f = open('test.txt', 'w')

访问模式:

访问模式说明
r以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式
w打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件
a打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入
rb以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式
wb以二进制格式打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件
ab以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入
r+打开一个文件用于读写。文件指针将会放在文件的开头
w+打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件
a+打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写
rb+以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。
wb+以二进制格式打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
ab+以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写

注意:文件默认会新建并保留在当前项目文件目录下。

# 新建一个文件,文件名为:test.txt
f = open('test.txt', 'w')
# 关闭这个文件
f.close()
文件读写 写数据

使用write()可以完成向文件写入数据

f = open('test.txt', 'w')
f.write('hello world, i am here!')
f.close()

如果文件不存在那么创建,如果存在那么就先清空,然后写入数据

读数据(read)

使用read(num)可以从文件中读取数据,num表示要从文件中读取的数据的长度(单位是字节),如果 没有传入num,那么就表示读取文件中所有的数据

f = open('test.txt', 'r')
content = f.read(5)
print(content)
print("-"*30)
content = f.read()
print(content)
f.close()

注意:

  • 如果open是打开一个文件,那么可以不用写打开的模式,即只写 open(‘test.txt’)
  • 如果使用读了多次,那么后面读取的数据是从上次读完后的位置开始的
读数据(readlines)

就像read没有参数时一样,readlines可以按照行的方式把整个文件中的内容进行一次性读取,并且返回的是一个列表,其中每一行的数据为一个元素。

f = open('test.txt', 'r')
content = f.readlines()
print(type(content))
i=1
for temp in content:
	print("%d:%s"%(i, temp))
	i+=1
f.close()    
读数据(readline)

一次读一行 (指针一行一行的移动)

f = open('test.txt', 'r')
content = f.readline()
print("1:%s"%content)
content = f.readline()
print("2:%s"%content)
f.close()
文件的相关 *** 作

有些时候,需要对文件进行重命名、删除等一些 *** 作,python的os模块中都有这么功能

文件重命名

os模块中的rename()可以完成对文件的重命名 *** 作

rename(需要修改的文件名, 新的文件名)

import os
os.rename("毕业论文.txt", "毕业论文-最终版.txt")
删除文件

os模块中的remove()可以完成对文件的删除 *** 作

remove(待删除的文件名)

import os
os.remove("毕业论文.txt")
创建文件夹
import os
os.mkdir("张三")
获取当前目录
import os
os.getcwd()
改变默认目录
import os
os.chdir("../")
获取目录列表
import os
os.listdir("./")
删除文件夹
import os
os.rmdir("张三")
异常 简介
print '-----test--1---'
open('123.txt','r')
print '-----test--2---'

打开一个不存在的文件123.txt,当找不到123.txt 文件时,就会抛出给我们一个IOError类型的错 误,No such file or directory:123.txt (没有123.txt这样的文件或目录)

异常:

当Python检测到一个错误时,解释器就无法继续执行了,反而出现了一些错误的提示,这就是所 谓的"异常"

捕获异常 try…except…
  • 把可能出现问题的代码,放在try中
  • 把处理异常的代码,放在except中
try:
	print('-----test--1---')
	open('123.txt','r')
	print('-----test--2---')
except IOError:
	pass

说明:

  • 此程序看不到任何错误,因为用except 捕获到了IOError异常,并添加了处理的方法 pass 表示实现了相应的实现,但什么也不做;
  • 如果把pass改为print语句,那么就会输出其他信息
except捕获多个异常
#coding=utf-8
try:
    print('-----test--1---')
    open('123.txt','r') # 如果123.txt文件不存在,那么会产生 IOError 异常
    print('-----test--2---')
    print(num)# 如果num变量没有定义,那么会产生 NameError 异常
except (IOError,NameError):
	#如果想通过一次except捕获到多个异常可以用一个元组的方式

当捕获多个异常时,可以把要捕获的异常的名字,放到except 后,并使用元组的方式仅进行存储

获取异常的信息描述
try:
    print("----test1------")
    f = open("123.txt", "r")
    print("----test2--------")
    
    print(num)
except (IOError,NameError) as result: #捕获多个异常的写法,将异常信息存储在result中进行打印
    print("产生了异常")
    print(result)

#捕获所有
except Exception as result:
try…except…finally…

在程序中,如果一个段代码必须要执行,即无论异常是否产生都要执行,那么此时就需要使用finally。 比如文件关闭,释放锁,把数据库连接返还给连接池等。

import time
try:
	f = open('test.txt')
	try:
		while True:
		content = f.readline()
		if len(content) == 0:
			break
		time.sleep(2)
		print(content)
	except:
		#如果在读取文件的过程中,产生了异常,那么就会捕获到
		#比如 按下了 ctrl+c
	pass
	finally:
		f.close()
		print('关闭文件')
except:
	print("没有这个文件")

test.txt文件中每一行数据打印,但是我有意在每打印一行之前用time.sleep方法暂停2秒钟。这样 做的原因是让程序运行得慢一些。在程序运行的时候,按Ctrl+c中断(取消)程序。 我们可以观察到KeyboardInterrupt异常被触发,程序退出。但是在程序退出之前,finally从句仍 然被执行,把文件关闭。

python爬虫 任务

爬取豆瓣电影Top250的基本信息,包括电影的名称、豆瓣评分、评价数、电影概况、电影链接等。

网址:豆瓣电影 Top 250 (douban.com)

爬虫初识
  • 什么是爬虫
    网络爬虫,是一种按照一定规则,自动抓取互联网信息的程序或者脚本。由于互联网数据的多样性和资源的有限性,根据用户需求定向抓取相关网页并分析已成为如今主流的爬取策略。
  • 爬虫可以做什么
    你可以爬取图片,爬取视频等等,只要你能通过浏览器访问的数据都可以通过爬虫获取。
  • 爬虫的本质是什么
    模拟浏览器打开网页,获取网页中我们想要的那部分数据。
基本流程
  • 准备工作

    通过浏览器查看分析目标网页,学习编程基础规范。

  • 获取数据

    通过HTTP库向目标站点发起请求,请求可以包含额外的header等信息,如果服务器能正常响应,会得到一个Response,便是所要获取的页面内容。

  • 解析内容

    得到的内容可能是HTML、json等格式,可以用页面解析库、正则表达式等进行解析。

  • 保存数据

    保存形式多样,可以存为文本,也可以保存到数据库,或者保存特定格式的文件。

引入模块
  • 引入自定义的模块

    根目录下创建test文件夹,test文件夹下创建test1.py test1.py中有add方法

    若在根目录下的test2文件夹中的py文件想使用add方法 引用方式:from test import test1

  • 引入第三方模块 import

  • 引入

    from bs4 import BeautifulSoup # 网页解析,获取数据
    import re # 正则(自带)
    import urllib.request,urllib.error # 指定url  获取网页数据(自带)
    import xlwt # 进行excel *** 作
    import sqlite3  # 进行sqllite *** 作(自带)
    
基本思路
def main():
    baseUrl = "https://movie.douban.com/top250?start="
    # 1.爬取网页
    dataList = getData(baseUrl)
    savePath = ".\豆瓣电影Top250.xls"  # 表示存在文件系统下
    # savePath = "./……" 存在此py文件下
    # 2.保存数据
    saveData(savePath)

def getData(baseUrl):
    dataList = []
    return  dataList;


def saveData(savePath):
    pass
urllib介绍

是python内置的一个http请求库,不需要额外的安装。只需要关注请求的链接,参数,提供了强大的 解析。

模拟请求:

  • 简单的一个get请求

    import urllib.request
    
    response = urllib.request.urlopen("http://www.baidu.com")
    print(response.read().decode('utf-8')) #对获取到的网页资源进行utf-8进行解码
    
  • 简单的post请求

    import urllib.parse
    import urllib.request
    data = bytes(urllib.parse.urlencode({'hello':'world'}),encoding='utf-8')
    reponse = urllib.request.urlopen('http://httpbin.org/post',data=data)
    print(reponse.read().decode("utf-8"))
    
  • 超时处理

    import urllib.request
    response = urllib.request.urlopen('http://httpbin.org/get',timeout=1)
    print(response.read())
    
    import urllib.request
    import socket
    import urllib.error
    try:
    response = urllib.request.urlopen('http://httpbin.org/get',timeout=0.01)
    except urllib.error.URLError as e:
    if isinstance(e.reason,socket.timeout):#判断错误原因
    print('time out!')
    
  • 打印出响应类型,状态码,响应头

    import urllib.request
    response=urllib.request.urlopen('http://www.baidu.com')
    print(type(response))
    
    import urllib.request
    response = urllib.request.urlopen('http://www.baidu.com')
    print(response.status) # 状态码 判断请求是否成功
    print(response.getheaders()) # 响应头 得到的一个元组组成的列表
    print(response.getheader('Server')) #得到特定的响应头
    print(response.read().decode('utf-8')) #获取响应体的内容,字节流的数据,需要转成utf-8格式
    

    由于使用urlopen无法传入参数,我们需要解决这个问题 我们需要声明一个request对象,通过这个对象来添加参数

    import urllib.request
    request = urllib.request.Request('https://python.org') #由于urlopen无法传参数,声明一个Request对象
    response = urllib.request.urlopen(request)
    print(response.read().decode('utf-8'))
    
  • 我们还可以分别创建字符串、字典等等来带入到request对象里面

    from urllib import request,parse
    url='http://httpbin.org/post'
    headers={
    'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36
    (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36',
    'Host':'httpbin.org'
    }
    dict={
    'name':'jay'
    }
    data = bytes(parse.urlencode(dict),encoding='utf-8')
    req=request.Request(url=url,data=data,headers=headers,method='POST')
    response=request.urlopen(req)
    print(response.read().decode('utf-8'))
    
beautiful soup

Beautiful Soup是一个库,提供一些简单的、python式的用来处理导航、搜索、修改分析树等功能,通过解析文档为用户提供需要抓取的数据。我们需要的每个电影都在一个的标签中,且每个div标签都有一个属性class= "item”。

代码示例:

from bs4 import BeautifulSoup
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html,"html.parser") # 缩进格式
print(bs.prettify()) # 获取title标签的所有内容
print(bs.title) # 获取title标签的名称
print(bs.title.name) # 获取title标签的文本内容
print(bs.title.string) # 获取head标签的所有内容
print(bs.head) # 获取第一个div标签中的所有内容
print(bs.div) # 获取第一个div标签的id的值
print(bs.div["id"]) # 获取第一个a标签中的所有内容
print(bs.a) # 获取所有的a标签中的所有内容
print(bs.find_all("a")) # 获取id="u1"
BeautifulSoup4四大对象种类

BeautifulSoup4将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以 归纳为4种:

  • Tag

    Tag通俗点讲就是HTML中的一个个标签,例如:

    from bs4 import BeautifulSoup
    file = open('./aa.html', 'rb')
    html = file.read()
    bs = BeautifulSoup(html,"html.parser")
    # 获取title标签的所有内容
    print(bs.title)
    # 获取head标签的所有内容
    print(bs.head)
    # 获取第一个a标签的所有内容
    print(bs.a)
    # 类型
    print(type(bs.a))
    

    我们可以利用 soup 加标签名轻松地获取这些标签的内容,这些对象的类型是bs4.element.Tag。但是 注意,它查找的是在所有内容中的第一个符合要求的标签。

    对于 Tag,它有两个重要的属性,是 name 和 attrs:

    from bs4 import BeautifulSoup
    file = open('./aa.html', 'rb')
    html = file.read()
    bs = BeautifulSoup(html,"html.parser")
    # [document] #bs 对象本身比较特殊,它的 name 即为 [document]
    print(bs.name)
    # head #对于其他内部标签,输出的值便为标签本身的名称
    print(bs.head.name)
    # 在这里,我们把 a 标签的所有属性打印输出了出来,得到的类型是一个字典。
    print(bs.a.attrs)
    #还可以利用get方法,传入属性的名称,二者是等价的
    print(bs.a['class']) # 等价 bs.a.get('class')
    # 可以对这些属性和内容等等进行修改
    bs.a['class'] = "newClass"
    print(bs.a)
    # 还可以对这个属性进行删除
    del bs.a['class']
    print(bs.a)
    
    
  • NavigableString

    既然我们已经得到了标签的内容,那么问题来了,我们要想获取标签内部的文字怎么办呢?很简单,用 .string 即可,例如:

    from bs4 import BeautifulSoup
    file = open('./aa.html', 'rb')
    html = file.read()
    bs = BeautifulSoup(html,"html.parser")
    print(bs.title.string)
    print(type(bs.title.string))
    
  • BeautifulSoup

BeautifulSoup对象表示的是一个文档的内容。大部分时候,可以把它当作 Tag 对象,是一个特殊的 Tag,我们可以分别获取它的类型,名称,以及属性,例如:

from bs4 import BeautifulSoup
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html,"html.parser")
print(type(bs.name))
print(bs.name)
print(bs.attrs)
  • Comment

Comment 对象是一个特殊类型的 NavigableString 对象,其输出的内容不包括注释符号。

from bs4 import BeautifulSoup
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html,"html.parser")
print(bs.a)
# 此时不能出现空格和换行符,a标签如下:
# 
print(bs.a.string) # 新闻
print(type(bs.a.string)) # 
遍历文档树

contents:获取Tag的所有子节点,返回一个list

# tag的.content 属性可以将tag的子节点以列表的方式输出
print(bs.head.contents)
# 用列表索引来获取它的某一个元素
print(bs.head.contents[1])

children:获取Tag的所有子节点,返回一个生成器

for child in bs.body.children:
	print(child)
搜索文档树 find_all(name, attrs, recursive, text, **kwargs)

在上面的例子中我们简单介绍了find_all的使用,接下来介绍一下find_all的更多用法-过滤器。这些过滤 器贯穿整个搜索API,过滤器可以被用在tag的name中,节点的属性等。

  1. name参数

    字符串过滤:会查找与字符串完全匹配的内容

    a_list = bs.find_all("a")
    print(a_list)
    

    正则表达式过滤:如果传入的是正则表达式,那么BeautifulSoup4会通过search()来匹配内容

    from bs4 import BeautifulSoup
    import re
    file = open('./aa.html', 'rb')
    html = file.read()
    bs = BeautifulSoup(html,"html.parser")
    t_list = bs.find_all(re.compile("a"))
    for item in t_list:
    	print(item)
    

    列表:如果传入一个列表,BeautifulSoup4将会与列表中的任一元素匹配到的节点返回

    t_list = bs.find_all(["meta","link"])
    for item in t_list:
    	print(item)
    

    方法:传入一个方法,根据方法来匹配

    from bs4 import BeautifulSoup
    file = open('./aa.html', 'rb')
    html = file.read()
    bs = BeautifulSoup(html,"html.parser")
    def name_is_exists(tag):
    	return tag.has_attr("name")
    t_list = bs.find_all(name_is_exists)
    for item in t_list:
    	print(item)
    
  2. kwargs参数:

    from bs4 import BeautifulSoup
    import re
    file = open('./aa.html', 'rb')
    html = file.read()
    bs = BeautifulSoup(html,"html.parser")
    # 查询id=head的Tag
    t_list = bs.find_all(id="head")
    print(t_list)
    # 查询href属性包含ss1.bdstatic.com的Tag
    t_list = bs.find_all(href=re.compile("http://news.baidu.com"))
    print(t_list)
    # 查询所有包含class的Tag(注意:class在Python中属于关键字,所以加_以示区别)
    t_list = bs.find_all(class_=True)
    for item in t_list:
    	print(item)
    
  3. attrs参数:

    并不是所有的属性都可以使用上面这种方式进行搜索,比如HTML的data-*属性:

    t_list = bs.find_all(data-foo="value")
    

    如果执行这段代码,将会报错。我们可以使用attrs参数,定义一个字典来搜索包含特殊属性的tag:

    t_list = bs.find_all(attrs={"data-foo":"value"})
    for item in t_list:
    	print(item)
    
  4. text参数:

    通过text参数可以搜索文档中的字符串内容,与name参数的可选值一样,text参数接受 字符串,正则 表达式,列表

    from bs4 import BeautifulSoup
    import re
    file = open('./aa.html', 'rb')
    html = file.read()
    bs = BeautifulSoup(html, "html.parser")
    t_list = bs.find_all(attrs={"data-foo": "value"})
    for item in t_list:
    	print(item)
    t_list = bs.find_all(text="hao123")
    for item in t_list:
    	print(item)
    t_list = bs.find_all(text=["hao123", "地图", "贴吧"])
    for item in t_list:
    	print(item)
    t_list = bs.find_all(text=re.compile("\d"))
    for item in t_list:
    	print(item)
    

    当我们搜索text中的一些特殊属性时,同样也可以传入一个方法来达到我们的目的:

    def length_is_two(text):
    	return text and len(text) == 2
    t_list = bs.find_all(text=length_is_two)
    for item in t_list:
    	print(item)
    
  5. limit参数:

    可以传入一个limit参数来限制返回的数量,当搜索出的数据量为5,而设置了limit=2时,此时只会返回 前2个数据

    from bs4 import BeautifulSoup
    import re
    file = open('./aa.html', 'rb')
    html = file.read()
    bs = BeautifulSoup(html, "html.parser")
    t_list = bs.find_all("a",limit=2)
    for item in t_list:
    	print(item)
    

    find_all除了上面一些常规的写法,还可以对其进行一些简写:

    # 两者是相等的
    # t_list = bs.find_all("a") => t_list = bs("a")
    t_list = bs("a") # 两者是相等的
    # t_list = bs.a.find_all(text="新闻") => t_list = bs.a(text="新闻")
    t_list = bs.a(text="新闻")
    
  6. find()

    find()将返回符合条件的第一个Tag,有时我们只需要或一个Tag时,我们就可以用到find()方法了。当然 了,也可以使用find_all()方法,传入一个limit=1,然后再取出第一个值也是可以的,不过未免繁琐。

    from bs4 import BeautifulSoup
    import re
    file = open('./aa.html', 'rb')
    html = file.read()
    bs = BeautifulSoup(html, "html.parser")
    # 返回只有一个结果的列表
    t_list = bs.find_all("title",limit=1)
    print(t_list)
    # 返回唯一值
    t = bs.find("title")
    print(t)
    # 如果没有找到,则返回None
    t = bs.find("abc") print(t)
    

    从结果可以看出find_all,尽管传入了limit=1,但是返回值仍然为一个列表,当我们只需要取一个值 时,远不如find方法方便。但是如果未搜索到值时,将返回一个None 在上面介绍BeautifulSoup4的时候,我们知道可以通过bs.div来获取第一个div标签,如果我们需要获取 第一个div下的第一个div, 我们可以这样:

    t = bs.div.div
    # 等价于
    t = bs.find("div").find("div")
    
  7. CSS选择器

    BeautifulSoup支持大部分的CSS选择器,在Tag获取BeautifulSoup对象的.select()方法中传入字符串参 数,即可使用CSS选择器的语法找到Tag:

    • 通过标签名查找

      print(bs.select('title'))
      print(bs.select('a'))
      
    • 通过类名查找

      print(bs.select('.mnav'))
      
    • 通过id查找

      print(bs.select('#u1'))
      
    • 组合查找

      print(bs.select('div .bri'))
      
    • 属性查找

      print(bs.select('a[]'))
      print(bs.select('a[href="http://tieba.baidu.com"]'))
      
    • 直接子标签查找

      t_list = bs.select("head > title")
      print(t_list)
      
    • 兄弟节点标签查找

      t_list = bs.select(".mnav ~ .bri")
      print(t_list)
      
    • 获取内容

      t_list = bs.select("title")
      print(bs.select('title')[0].get_text())
      
  8. CSS选择器

    BeautifulSoup支持大部分的CSS选择器,在Tag获取BeautifulSoup对象的.select()方法中传入字符串参 数,即可使用CSS选择器的语法找到Tag:

    • 通过标签名查找

      print(bs.select('title'))
      print(bs.select('a'))
      
    • 通过类名查找

      print(bs.select('.mnav'))
      
    • 通过id查找

      print(bs.select('#u1'))
      
    • 组合查找

      print(bs.select('div .bri'))
      
    • 属性查找

      print(bs.select('a[]'))
      print(bs.select('a[href="http://tieba.baidu.com"]'))
      
    • 直接子标签查找

      t_list = bs.select("head > title")
      print(t_list)
      
    • 兄弟节点标签查找

      t_list = bs.select(".mnav ~ .bri")
      print(t_list)
      
    • 获取内容

      t_list = bs.select("title")
      print(bs.select('title')[0].get_text())
      
正则提取

Re库功能函数:

正则表达式可以包含一些可选标志修饰符来控制匹配的模式。修饰符被指定为一个可选的标志。 多个标志可以通过按位 OR(|) 它们来指定。如 re.I | re.M 被设置成 I 和 M 标志:

主要是想入门python的语法,后面的爬虫 可视化数据就跳过了……

以下学习资料来源 https://github.com/jackfrued/Python-100-Days.git

面向对象 基础

面向对象比较官方的定义:

把一组数据结构和处理它们的方法组成对象(object),把相同行为的对象归纳为类(class),通过类的封装(encapsulation)隐藏内部细节,通过继承(inheritance)实现类的特化(specialization)和泛化(generalization),通过多态(polymorphism)实现基于对象类型的动态分派。

类和对象概念

简单的说,类是对象的蓝图和模板,而对象是类的实例。这个解释虽然有点像用概念在解释概念,但是从这句话我们至少可以看出,类是抽象的概念,而对象是具体的东西。在面向对象编程的世界中,一切皆为对象,对象都有属性和行为,每个对象都是独一无二的,而且对象一定属于某个类(型)。当我们把一大堆拥有共同特征的对象的静态特征(属性)和动态特征(行为)都抽取出来后,就可以定义出一个叫做“类”的东西。

类的定义和使用

在Python中可以使用class关键字定义类,然后在类中通过之前学习过的函数来定义方法,这样就可以将对象的动态特征描述出来,代码如下所示。

class Student(object):

	# __init__是一个特殊方法用于在创建对象时进行初始化 *** 作
	# 通过这个方法我们可以为学生对象绑定name和age两个属性
	def __init__(self, name, age):
		self.name = name
		self.age = age

	def study(self, course_name):
		print('%s正在学习%s.' % (self.name, course_name))
    
    #当使⽤print输出对象的时候,默认打印对象的内存地址。如果类定义了 __str__ ⽅法,那么就会打印从
	#在这个⽅法中 return 的数据。
	def __str__(self):
 		return '这是海尔洗⾐机的说明书'
   	
    #当删除对象时,python解释器也会默认调⽤ __del__() ⽅法。
    def __del__(self):
 		print(f'{self}对象已经被删除')
    
	# PEP 8要求标识符的名字用全小写多个单词用下划线连接
	# 但是很多程序员和公司更倾向于使用驼峰命名法(驼峰标识)
	def watch_av(self):
		if self.age < 18:
			print('%s只能观看《熊出没》.' % self.name)
		else:
			print('%s正在观看电影.' % self.name)
创建和使用对象
def main():
    # 创建学生对象并指定姓名和年龄
	stu1 = Student('骆昊', 38)
    # 给对象发study消息
	stu1.study('Python程序设计')
    # 给对象发watch_av消息
	stu1.watch_av()
	stu2 = Student('王大锤', 15)
	stu2.study('思想品德')
	stu2.watch_av()


if __name__ == '__main__':
	main()

访问可见性

在Java中,通常将对象的属性设置为私有的或受保护的,简而言之,不允许外界访问,对象的方法通常是公开的。在Python中,属性和方法的访问权限只有两种,也就是公开的和私有的,如果希望属性是私有的,在给属性命名时可以用两个下划线作为开头,下面的代码可以验证这一点。

class Test:

    def __init__(self, age):
        self.__age = 18

    def bar(self):
        print(self.__age)  # 私有属性在内部可以访问


def main():
    test = Test('hello')
    #  私有属性在外界无法访问
    # print(test.age)
	#方法也是一样
    test.bar()


if __name__ == "__main__":
    main()

python没有真正意义上的私有,大多数Python程序员会遵循一种命名惯例就是让属性名以单下划线开头来表示属性是受保护的,本类之外的代码在访问这样的属性时应该要保持慎重。这种做法并不是语法上的规则,单下划线开头的属性和方法外界仍然是可以访问的,所以更多的时候它是一种暗示或隐喻。

class Test:

    def __init__(self, age):
        self.__age = 18

    def bar(self):
        print(self.__age)  


def main():
    test = Test('hello')
    #  私有属性可以访问
    print(test._Test__age)

if __name__ == "__main__":
    main()
(补充)python中带下划线或双下划线
  • 以单下划线开头,表示这是一个保护成员,只有类对象和子类对象自己能访问到这些变量。以单下划线开头的变量和函数被默认是内部函数,使用from module import *时不会被获取,但是使用import module可以获取。实例._变量,可以被访问。
  • 以单下划线结尾仅仅是为了区别该名称与关键词
  • 双下划线开头,表示为私有成员,只允许类本身访问,子类也不行。在文本上被替换为_class__method
  • 双下划线开头,双下划线结尾。一种约定,Python内部的名字,用来区别其他用户自定义的命名,以防冲突。是一些 Python 的“魔术”对象,表示这是一个特殊成员,例如:定义类的时候,若是添加__init__方法,那么在创建类的实例的时候,实例会自动调用这个方法,一般用来对实例的属性进行初使化,Python不建议将自己命名的方法写为这种形式。即以双下划线开头的方法和变量不会被继承
进阶 @property装饰器

之前的建议是将属性命名以单下划线开头,通过这种方式来暗示属性是受保护的,不建议外界直接访问,那么如果想访问属性可以通过属性的getter(访问器)和setter(修改器)方法进行对应的 *** 作。如果要做到这点,就可以考虑使用@property包装器来包装getter和setter方法,使得对属性的访问既安全又方便,代码如下所示。

class Person(object):

    def __init__(self, name, age):
        self._name = name
        self._age = age

    # 访问器 - getter方法
    @property
    def name(self):
        return self._name

    # 访问器 - getter方法
    @property
    def age(self):
        return self._age

    # 修改器 - setter方法
    @age.setter
    def age(self, age):
        self._age = age

    def play(self):
        if self._age <= 16:
            print('%s正在玩飞行棋.' % self._name)
        else:
            print('%s正在玩斗地主.' % self._name)


def main():
    person = Person('王大锤', 12)
    person.play()
    person.age = 22
    person.play()
    # person.name = '白元芳'  # AttributeError: can't set attribute


if __name__ == '__main__':
    main()

__slots__魔法

Python是一门动态语言。通常,动态语言允许我们在程序运行时给对象绑定新的属性或方法,当然也可以对已经绑定的属性和方法进行解绑定。但是如果我们需要限定自定义类型的对象只能绑定某些属性,可以通过在类中定义__slots__变量来进行限定。需要注意的是__slots__的限定只对当前类的对象生效,对子类并不起任何作用。

class Person(object):

    # 限定Person对象只能绑定_name, _age和_gender属性
    __slots__ = ('_name', '_age', '_gender')

    def __init__(self, name, age):
        self._name = name
        self._age = age

    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, age):
        self._age = age

    def play(self):
        if self._age <= 16:
            print('%s正在玩飞行棋.' % self._name)
        else:
            print('%s正在玩斗地主.' % self._name)


def main():
    person = Person('王大锤', 22)
    person.play()
    person._gender = '男'
    # AttributeError: 'Person' object has no attribute '_is_gay'
    # person._is_gay = True

静态方法和类方法静态方法和类方法

我们在类中定义的方法都是对象方法,也就是说这些方法都是发送给对象的消息。实际上,我们写在类中的方法并不需要都是对象方法,例如我们定义一个“三角形”类,通过传入三条边长来构造三角形,并提供计算周长和面积的方法,但是传入的三条边长未必能构造出三角形对象,因此我们可以先写一个方法来验证三条边长是否可以构成三角形,这个方法很显然就不是对象方法,因为在调用这个方法时三角形对象尚未创建出来(因为都不知道三条边能不能构成三角形),所以这个方法是属于三角形类而并不属于三角形对象的。我们可以使用静态方法来解决这类问题,代码如下所示。

from math import sqrt


class Triangle(object):

    def __init__(self, a, b, c):
        self._a = a
        self._b = b
        self._c = c

    @staticmethod
    def is_valid(a, b, c):
        return a + b > c and b + c > a and a + c > b

    def perimeter(self):
        return self._a + self._b + self._c

    def area(self):
        half = self.perimeter() / 2
        return sqrt(half * (half - self._a) *
                    (half - self._b) * (half - self._c))


def main():
    a, b, c = 3, 4, 5
    # 静态方法和类方法都是通过给类发消息来调用的
    if Triangle.is_valid(a, b, c):
        t = Triangle(a, b, c)
        print(t.perimeter())
        # 也可以通过给类发消息来调用对象方法但是要传入接收消息的对象作为参数
        # print(Triangle.perimeter(t))
        print(t.area())
        # print(Triangle.area(t))
    else:
        print('无法构成三角形.')


if __name__ == '__main__':
    main()

和静态方法比较类似,Python还可以在类中定义类方法,类方法的第一个参数约定名为cls,它代表的是当前类相关的信息的对象(类本身也是一个对象,有的地方也称之为类的元数据对象),通过这个参数我们可以获取和类相关的信息并且可以创建出类的对象,代码如下所示。

#类方法代码
class Student:
    name="jom"
    age=21
    @classmethod
    def classshow(cls,str):
        print(cls,cls.name,cls.age,str)

p=Student()
print("输出实例p:")
print(p)
print("输出类名称Student:")
print(Student)
print("使用实例调用的结果:")
p.classshow("实例调用")
print("使用类名称调用的结果:")
Student.classshow("类名称调用")
输出实例p:
<__main__.Student object at 0x0000020FB08B6BC0>
输出类名称Student:
<class '__main__.Student'>
使用实例调用的结果:
<class '__main__.Student'> jom 21 实例调用
使用类名称调用的结果:
<class '__main__.Student'> jom 21 类名称调用
类之间的关系

类和类之间的关系有三种:is-a、has-a和use-a关系。

  • is-a关系也叫继承或泛化,比如学生和人的关系、手机和电子产品的关系都属于继承关系。
  • has-a关系通常称之为关联,比如部门和员工的关系,汽车和引擎的关系都属于关联关系;关联关系如果是整体和部分的关联,那么我们称之为聚合关系;如果整体进一步负责了部分的生命周期(整体和部分是不可分割的,同时同在也同时消亡),那么这种就是最强的关联关系,我们称之为合成关系。
  • use-a关系通常称之为依赖,比如司机有一个驾驶的行为(方法),其中(的参数)使用到了汽车,那么司机和汽车的关系就是依赖关系。

利用类之间的这些关系,我们可以在已有类的基础上来完成某些 *** 作,也可以在已有类的基础上创建新的类,这些都是实现代码复用的重要手段。复用现有的代码不仅可以减少开发的工作量,也有利于代码的管理和维护,这是我们在日常工作中都会使用到的技术手段。

继承和多态

刚才我们提到了,可以在已有类的基础上创建新类,这其中的一种做法就是让一个类从另一个类那里将属性和方法直接继承下来,从而减少重复代码的编写。提供继承信息的我们称之为父类,也叫超类或基类;得到继承信息的我们称之为子类,也叫派生类或衍生类。子类除了继承父类提供的属性和方法,还可以定义自己特有的属性和方法,所以子类比父类拥有的更多的能力,在实际开发中,我们经常会用子类对象去替换掉一个父类对象,这是面向对象编程中一个常见的行为,对应的原则称之为里氏替换原则。下面我们先看一个继承的例子。

class Person(object):
    """人"""

    def __init__(self, name, age):
        self._name = name
        self._age = age

    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, age):
        self._age = age

    def play(self):
        print('%s正在愉快的玩耍.' % self._name)

    def watch_av(self):
        if self._age >= 18:
            print('%s正在观看电影.' % self._name)
        else:
            print('%s只能观看《熊出没》.' % self._name)


class Student(Person):
    """学生"""

    def __init__(self, name, age, grade):
        super().__init__(name, age)
        self._grade = grade

    @property
    def grade(self):
        return self._grade

    @grade.setter
    def grade(self, grade):
        self._grade = grade

    def study(self, course):
        print('%s的%s正在学习%s.' % (self._grade, self._name, course))


class Teacher(Person):
    """老师"""

    def __init__(self, name, age, title):
        super().__init__(name, age)
        self._title = title

    @property
    def title(self):
        return self._title

    @title.setter
    def title(self, title):
        self._title = title

    def teach(self, course):
        print('%s%s正在讲%s.' % (self._name, self._title, course))


def main():
    stu = Student('王大锤', 15, '初三')
    stu.study('数学')
    stu.watch_av()
    t = Teacher('骆昊', 38, '老叫兽')
    t.teach('Python程序设计')
    t.watch_av()


if __name__ == '__main__':
    main()

子类在继承了父类的方法后,可以对父类已有的方法给出新的实现版本,这个动作称之为方法重写(override)。通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本,当我们调用这个经过子类重写的方法时,不同的子类对象会表现出不同的行为,这个就是多态(poly-morphism)。

from abc import ABCMeta, abstractmethod


class Pet(object, metaclass=ABCMeta):
    """宠物"""

    def __init__(self, nickname):
        self._nickname = nickname

    @abstractmethod
    def make_voice(self):
        """发出声音"""
        pass


class Dog(Pet):
    """狗"""

    def make_voice(self):
        print('%s: 汪汪汪...' % self._nickname)


class Cat(Pet):
    """猫"""

    def make_voice(self):
        print('%s: 喵...喵...' % self._nickname)


def main():
    pets = [Dog('旺财'), Cat('凯蒂'), Dog('大黄')]
    for pet in pets:
        pet.make_voice()


if __name__ == '__main__':
    main()

在上面的代码中,我们将Pet类处理成了一个抽象类,所谓抽象类就是不能够创建对象的类,这种类的存在就是专门为了让其他类去继承它。Python从语法层面并没有像Java或C#那样提供对抽象类的支持,但是我们可以通过abc模块的ABCMeta元类和abstractmethod包装器来达到抽象类的效果,如果一个类中存在抽象方法那么这个类就不能够实例化(创建对象)。上面的代码中,DogCat两个子类分别对Pet类中的make_voice抽象方法进行了重写并给出了不同的实现版本,当我们在main函数中调用该方法时,这个方法就表现出了多态行为(同样的方法做了不同的事情)。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/922669.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存