Python基础

开始学习图形化程序设计

>>>import turtle                  #导入turtle模块

>>>turtle.showturtle()         #显示箭头

>>>turtle.write("高琪")        #写字符串

>>>turtle.forward(300)        #前进300像素

>>>turtle.color("red")          #画笔颜色改为red

>>>turtle.left(90)                 #箭头左转90度

>>>turtle.forward(300)

>>>turtle.goto(0,50)           #去坐标(0,50)

>>>turtle.goto(0,0)

>>>turtle.penup()                #抬笔。这样,路径向上

>>>turtle.goto(0,300) 

>>>turtle.pendown()           #下笔。这样,路径向下

>>>turtle.circle(100)           #画圆

自己练手做了一个奥运五环的图标

#绘制奥运五环
import turtle
turtle.width(10)
turtle.color("blue")
turtle.circle(50)

turtle.penup()
turtle.goto(120,0)
turtle.pendown()

turtle.color("black")
turtle.circle(50)

turtle.penup()
turtle.goto(240,0)
turtle.pendown()

turtle.color("red")
turtle.circle(50)

turtle.penup()
turtle.goto(60,-50)
turtle.pendown()

turtle.color("yellow")
turtle.circle(50)

turtle.penup()
turtle.goto(180,-50)
turtle.pendown()

turtle.color("green")
turtle.circle(50)
可以用IDLE运行出来一个图片,感觉很有意思,有兴趣的可以试一试

引用

               在Python中,变量也成为:对象的引用。因为,变量存储的就是对象的地址。

               变量通过地址引用了“对象”。

                变量位于:栈内存。

                 对象位于:堆内存。

         Python是动态类型语言

变量不需要显示声明类型。根据变量引用的对象,Python解释器自动确定数据类型。

         Python是强类型语言

每个对象都有数据类型,只支持该类型支持的操作。

 

变量和简单赋值语句

  变量的声明和赋值

变量的声明和赋值用于将一个变量绑定到一个对象上,格式如下:

变量名 = 表达式

最简单的表达式就是字面量。比如: a = 123。运行过程中,解释器先运行右边的表达式。

生成一个代表表达式运算结果的对象:然后,将这个对象地址赋值给左边的变量。

  删除变量和垃圾回收机制

可以通过del语句删除不在使用的变量。

链式赋值

链式赋值用于同一个对象赋值给多个变量。

x=y=123 相当于:x=123;y=123

系列解包赋值

系列数据赋值给对应相同个数的变量(个数必须保持一致)

>>>a,b,c=4,5,6  相当于:a=4;b=5;c=6

[操作]使用系列解包赋值实现变量交换


>>> a,b=1,2
>>> a,b=b,a
>>> print(a,b)
2 1


常量

Python不支持常量,即没有语法规则限制改变一个常量的值,我们只能约定常量的命名规则,以及在程序的逻辑上不对常量的值作出修改。

>>> MAX_SPEED = 120
>>> print(MAX_SPEED)
120
>>> MAX_SPEED=140  #实际是可以改变的,只能逻辑上不做修改
>>> print(MAX_SPEED)
140

 

最基本内置数据类型介绍

   每个对象都有类型,python中最基本的内置数据类型:

   1.整型

      整数,2345,,10,50

    2.浮点数

      小数,3.14或者科学计数法314e-2

    3.布尔型

      表示真假,仅包含:True、False

    4.字符串型

      由字符组成的序列。“abc”,"sxt","尚学堂",“百战程序员”

数字

    Python支持整数(如:50,520)和浮点数(如:3.14,10.0,1.23e2),我们可以对数字做如下运算。

     

 

运算符说明示例结果
+加法3+25
-减法30-525
*乘法3*618
/浮点数除法8/24.0
//整数除法7//23
%模(取余)7%43
**2**38

 

使用divmod函数同时得到商和余数:

>>> divmod(13,3)
(4, 1)

 

整数

Python中,除10进制,还有其他三种进制:

0b或0B,二进制 0    1

0o或0O,八进制 0     1      2       3        4        5        6        7

0x或0X,十六进制0     1      2       3        4        5        6        7        8        9        a        b        c        d        e        f

这三种进制可以非常方便的进行“位运算”操作。

【操作】测试不同进制

>>> 12
12
>>> 0b101
5
>>> 0o10
8
>>> 0xff
255
>>> 0xf
15
>>> 0x10
16

 

使用int()实现类型转换:

1.浮点数直接舍去小数部分,如:int(9.9)结果为9

2.布尔值True转为1,False转为0. 如:int(True)结果为1

3.字符串符合整数格式(浮点数格式不行)则直接转成对应整数,否则报错。

>>> int("456")
456
>>> int("456abc")
Traceback (most recent call last):
  File "<pyshell#20>", line 1, in <module>
    int("456abc")
ValueError: invalid literal for int() with base 10: '456abc'

 

自动转型:

     整数和浮点数混合运算时,表达式结果自动转型为浮点数,比如:2+8.0的结果是10.0

整数可以有多大?

     Python2中,int是32位,可以存储从-2147483648到2147483647的整数(约±21亿),Long类型是64位,可以存储:-2^63---2^63-1之间的数值。

     Python3中,int可以存储任意大小的整数,long被取消。我们甚至可以存储下面的值:

>>> googol=10**100
>>> googol
10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

 

Googol也是Google最初的名字,这也是Google最初的含义。

Python3中可以做超大数的计算,而不会造成“整数溢出”,这也是Python特别适合科学运算的特点。

 

浮点数

浮点数,称为float.

浮点数用a*b的十次方形式的科学计数法表示。比如:3.14,表示成:314E-2或者314e-2.

这些数字在内存中也是按照科学计数法存储。

类型转换和四舍五入

1.类似于int(),我们也可以使用float()将其他类型转化成浮点数。

2.整数和浮点数混合运算时,表达式结果自动转型成浮点数。比如:2+8.0的结果是10.0

3.round(value)可以返回四舍五入的值

     注:但不会改变原有值,而是产生新的值

增强型赋值运算符

运算符+、-、*、/、//、**和%和赋值符=结合可以构成“增强型赋值运算符”。

a=a+1 等价于:a+=1

                                      增强型赋值运算符

 

运算符例子等价
+=a+=2a=a+2
-=a-=2a=a-2
*=a*=2a=a*2
/=a/=2

a=a/2

//=a//=2a=a//2
**=a**=2a=a**2
%=a%=2a=a%2

 

注意:“+=”中间不能加空格!

 

 

【操作】定义多点坐标_绘出折线_并计算起始点和终点距离

 

import turtle
import math

#定义多个点的坐标

x1,y1=100,100
x2,y2=100,-100
x3,y3=-100,-100
x4,y4=-100,100


#绘制折线
turtle.penup()
turtle.goto(x1,y1)
turtle.pendown()
turtle.goto(x2,y2)
turtle.goto(x3,y3)
turtle.goto(x4,y4)


#计算起始点和终点的距离

distance = math.sqrt((x1-x4)**2+(y1-y4)**2)
turtle.write(distance)
 

 

布尔值

Python2中没有布尔值,直接用数字0表示False,用数字1表示True.

Python3中,把True和False定义成了关键字,但他们的本质还是1和0,甚至可以和数字相加。

>>> a=True
>>> b=3
>>> a+b
4

比较运算符

所有比较运算符返回1表示真,返回0表示假。这分别与特殊的变量True和False等价。

一下假设变量a为15,变量b为30:

逻辑运算符

同一运算符

同一运算符用于比较两个对象的存储单元,实际比较的是对象的地址。

is    与   ==区别:

is用于判断两个变量引用对象是否为同一个,既比较对象的地址。

==用于判断引用变量引用对象的值是否相等,默认调用对象的__eq__()方法。

>>> a=1000
>>> b=1000
>>> a==b
True
>>> a is b
False
>>> id(a)
1860714118064
>>> id(b)
1860719150256
>>> 

·整数缓存问题

      Python仅仅对比较小的整数对象进行缓存(范围[-5,256])缓存起来,而并非是所有整数对象。需要注意的是,这仅仅是在命令行中执行,而在Pycharm或者保存为文件执行,结果是不一样的,这是因为解释器做了一部分优化(范围[-5,任意正整数])。

 

·总结

      1.is 比较两个对象的id值是否相等,是否指向同一个内存地址;

      2.==比较的是两个对象的内容是否相等,值是否相等;

      3.小整数对象[-5,256]在全局解释器范围内被放入缓存供重复使用;

      4.is运算符比==效率高,在变量和None进行比较时,应该使用is.

 

字符串

 字符串基本特点

      字符串的本质是:字符序列。Python的字符串是不可变的,我们无法对原字符串做任何修改,但,可以将字符串的一部分复制到新创建的字符串,达到“看起来修改”的效果。

      Python不支持单字符类型,单字符也是作为一个字符串使用的。

 

字符串的编码

     Python3直接支持Unicode,可以表示世界上任何书面语言的字符。Python3的字符默认就是16位Unicode编码,ASCII码是Unicode编码的子集。

     使用内置函数ord()可以把字符转换成对应的Unicode码。

      使用内置函数chr()可以把十进制数字转换成对应的字符。

>>> ord('A')
65
>>> ord('高')
39640
>>> chr(66)
'B'

 

引号创建字符串

我们可以通过单引号或者双引号创建字符串。例如:a='abc';b="sxt".

使用两种引号的好处是可以创建本身就包含引导的字符串,而不用使用转义字符。例如:

>>> a="I'm a teacher!"
>>> print(a)
I'm a teacher!
>>> b='my_name is "TOM"'
>>> print(b)
my_name is "TOM"

 

连续三个单引号或三个双引号,可以帮助我们创建多行字符串。例如:

>>> resume='''name="gaoqi"                                                       company="sxt"   age=18                                                          lover="TOM"'''
>>> print(resume)
name="gaoqi"                                                       company="sxt"   age=18                                                          lover="TOM"
 

 

空字符串和len()和函数

Python允许空字符串的存在,不包括任何字符且长度为0.例如:

>>> c=''
>>> len(c)
0

len()用于计算字符串含有多少字符。例如:

>>> d='abc尚学堂'
>>> len(d)
6

 

转义字符

我们可以使用“\+特殊字符”,实现某些难以用字符表示的效果。比如:换行等。常见的转义字符有这些:

【操作】测试转义字符的使用

>>> a='I\nlove\nU'
>>> a
'I\nlove\nU'
>>> print(a)
I
love
U

>>> print('aaabb\
cccddd')
aaabbcccddd

 

 

字符串拼接

1.可以使用+将多个字符串拼接起来。例如:‘aa'+'bb'==>'aabb'

(1)如果+两边都是字符串。则拼接。

(2)如果+两边都是数字,则加法运算。

(3)如果+两边类型不同,则抛出异常。

2.可以将多个字面字符串直接放到一起实现拼接。例如:'aa' 'bb' ==> 'aabb'

【操作】字符串拼接操作

>>> a='sxt'+'gaoqi'
>>> a
'sxtgaoqi'
>>> b='sxt''gaoqi'
>>> b
'sxtgaoqi'

字符串复制

使用*可以实现字符串复制。

【操作】字符串复制操作

>>> a='Sxt'*3
>>> a
'SxtSxtSxt'

不换行打印

我们前面调用print时,会自动打印一个换行符。有时,我们不想换行,不想自动添加换行符。我们可以自己通过参数end=“任意字符串”。实现末尾添加任何内容:

建立源文件:

print("sxt",end=' ')
print("sxt",end='##')
print("sxt")
 

运行结果:

sxt sxt##sxt

 

从控制台读取字符串

我们可以使用input()从控制台读取键盘输入的内容。

>>> myname=input("请输入名字:")
请输入名字:邹
>>> myname
'邹'

str()实现数字转型字符串

str()可以帮助我们将其他数据类型转换为字符串。例如:

str(5.20) == > '5.20'                     str(3.14e2) == >'314.0'            str(True) == >  'True'

当我们调用print()函数时,解释器自动调用了str()将非字符串的对象转成了字符串。

使用[]提取字符

字符串的本质就是字符序列,我们可以通过在字符串后面添加[],在[]里面指定偏移量,可以提取该位置的单个字符。

正向搜索:

      最左侧第一个字符,偏移量是0,第二个偏移量是1,以此类推。直到len(str)-1为止。

反向搜索:

      最右侧第一个字符,偏移量是-1,倒数第二个偏移量是-2,以此类推,直到-len(str)为止。

[操作]使用[]提取字符串中的字符

>>> a='abcdefghijklmnopqrstuvwxyz'
>>> a
'abcdefghijklmnopqrstuvwxyz'
>>> a[0]
'a'
>>> a[3]
'd'
>>> a[26-1]
'z'
>>> a[-1]
'z'
>>> a[-26]
'a'

replace()实现字符串替换

字符串是“不可改变”的,我们通过[]可以获取字符串指定位置的字符,但是我们不能改变字符串,我们尝试改变字符串中的某个字符,发现报错了:

>>> a='abcdefghijklmnopqrstuvwxyz'
>>> a
'abcdefghijklmnopqrstuvwxyz'
>>> a[3]='搞'
Traceback (most recent call last):
  File "<pyshell#51>", line 1, in <module>
    a[3]='搞'
TypeError: 'str' object does not support item assignment

字符串不可改变,但是,我们确实有时候需要替换某些字符,这是,只能通过创建新的字符串来实现。

>>> a='abcdefghijklmnopqrstuvwxyz'
>>> a
'abcdefghijklmnopqrstuvwxyz'
>>> a.replace('c','高')
'ab高defghijklmnopqrstuvwxyz'

整个过程中,实际上我们是创建了新的字符串对象,并指向了变量a,而不是修改了以前的字符串,内存图如下

字符串切片slice操作

切片slice操作可以让我们快速的提取字符串,标准格式为:

[起始偏移量  start :  终止偏移量   end   :  步长   step]

典型操作(三个量为正数的情况)如下:

 

 

其他操作(三个量为负数)的情况:

切片操作时,起始偏移量和终止偏移量不在[0,字符串长度-1]这个范围,也不会报错。起始偏移量小于0则会当做0,终止偏移量大于“长度-1”会被当成-1.例如:

>>>"abcdefg"[3:50]

'defg'

我们发现正常输出了结果,没有报错。

split()分割和join()合并

split()可以基于指定分隔符将字符串分隔成多个子字符串(存储到列表中)。如果不指定分隔符,则默认使用空白符(换行符/空格/制表符)。示例代码如下:

>>> a="to be or not to be"
>>> a.split()
['to', 'be', 'or', 'not', 'to', 'be']
>>> a.split('be')
['to ', ' or not to ', '']

join()的作用和split()作用刚好相反,用于将一系列子字符串连接起来。示例代码如下:

>>> a=['sxt','sxt100','sxt200']
>>> '*'.join(a)
'sxt*sxt100*sxt200'

拼接字符串要点:

     使用字符串拼接符+,会生成新的字符串对象,因此不推荐使用+来拼接字符串。推荐使用join函数,因此join函数在拼接字符串之前会计算所有字符串的长度,然后逐一拷贝。仅新建一次对象。

 

字符串驻留机制和字符串比较

字符串驻留:仅保存一份相同且不可变字符串的方法,不同的值被存放在字符串驻留池中。

Python支持字符串驻留机制,对于符合标识符规则的字符串(仅包含下划线(_)、字母和数字)会启用字符串驻留机制驻留机制。

>>> a="abd_33"
>>> b="abd_33"
>>> a is b
True
>>> c="dd#"
>>> d="dd#"
>>> c is d
False
>>> str1="aa"
>>> str2="bb"
>>> str1+str2 is "aabb"
False

字符串比较和同一性

我们可以直接使用==,!=对字符串进行比较,是否含有相同的字符。

我们使用is /not is, 判断两个对象是否同一对象。比较的是对象的地址,即id(obj1)是否和id(obj2)相等。

成员操作符

in / not in 关键字,判断某个字符(子字符串)是否存在于字符串中。

字符串常用方法汇总

字符串有很多常用的方法,我们需要熟悉。我们通过表格将这些方法汇总起来,方便大家查阅,希望大家针对每个方法都做一次测试。

 常用查找方法

去除首尾信息

我们可以通过strip()去除字符串首尾指定信息。通过lstrip()去除字符串左边指定信息,rstrip()去除字符串右边指定信息。

>>> "*s*x*t*".strip("*")
's*x*t'
>>> "*s*x*t*".lstrip("*")
's*x*t*'
>>> "*s*x*t*".rstrip("*")
'*s*x*t'
>>> "   sxt   ".strip()
'sxt'

大小写转换

格式排版

其他方法

1.  isalnum()          是否为字母或数字

2.  isalpha()           检测字符串是否只由字母组成(含汉字)。

3.  isdigit()             检测字符串是否只由数字组成。

4.  isspace()          检测是否为空白符

5.  isupper()           是否为大写字母

6.  islower()            是否为小写字母

>>> "sxt100".isalnum()
True
>>> "sxt尚学堂".isalpha()
True
>>> "234.3".isdigit()
False
>>> "23423".isdigit()
True
>>> "aB".isupper()
False
>>> "A".isupper()
True
>>> "\t\n".isspace()
True

字符串的格式化

format()基本用法

Python2.6开始,新增了一种格式化字符串格式化的功能。

基本语法是通过{}和:来代替以前的%。

format函数可以接受不限个参数,位置可以不按顺序。

我们可以通过{索引}/{参数名},直接映射参数值,实现对字符串的格式化,非常方便。

填充和对齐

填充常跟对齐一起使用

^ 、<、>分别是居中,左对齐,右对齐,后面带宽度

:后面带填充的字符,只能是一个字符,不指定的话默认是用空格填充

数字格式化

可变字符串

Python中,字符串属于不可变对象,不支持原地修改,如果需要修改其中的值,智能创建新的字符串对象,但是,经常我们确实需要原地修改字符串,可以使用。io.StringIO对象或array模块。

>>> import io
>>> s="hello,sxt"
>>> sio=io.StringIO(s)
>>> sio
<_io.StringIO object at 0x000001AA7E43B558>
>>> sio.getvalue()
'hello,sxt'
>>> sio.seek(7)
7
>>> sio.write("9")
1
>>> sio.getvalue()
'hello,s9t'

 

基本运算符

我们在前面讲解了“+”、“-”、“*”、“/”、“//”、“%”等运算符,这里我们继续讲解一些其他运算符,并进行学习和测试。

 

1.比较运算符可以连用,并且含义和我们日常使用完全一致。

    >>>a=4

    >>>3<a<10    #关系运算符可以连用

    True

2.位操作

   >>>a=0b11001

   >>>b=0b01000

   >>>c=a|b

   >>>bin(c)       #bin()可以将数字转成二进制表示

   '0b11001'

   >>>bin(c&b)

   '0b1000'

   >>>bin(c^b)

   '0b10001'

   >>> a=3
   >>> a<<2       #左移1位相当于乘以2.左移2位,相当于乘以4
   12
   >>> a=8
   >>> a>>1       #右移1位相当于除以2
   4

3.加法操作

      (1)数字相加           3+2 ==>5

      (2)字符串拼接        “3”+“2”==>“32”

      (3)列表、元组等合并   [10,20,30]+[5,10,100] ==>[10,20,30,5,10,100]

4.乘法操作

      (1)数字相乘            3*2==>6

      (2)字符串复制         “sxt”*3 ==>"sxtsxtsxt"

      (3)列表、元组等复制   [10,20,30]*3 ==>[10,20,30,10,20,30,10,20,30]

复合赋值运算符

符合赋值可以让程序更加精炼,提高效率。

 

 

注:与C和JAVA不一样,Python不支持自增(++)和自减(--)

运算符优先级问题

如下优先级,从高到低。

实际使用中,记住如下简单的规则即可,复杂的表达式一定要使用小括号组织。

          1.乘除优先加减

          2.位运算和算术运算>比较运算符>赋值运算符

 

序列

序列是一种数据存储方式,用来存储一系列的数据。在内存中,序列就是一块用来存放多个值的连续的内存空间。比如一个整数序列[10,20,30,40].可以这样示意表示:

10203040

 

由于Python3中一切皆对象,在内存中实际是按照如下方式存储的:

                                      a  =  [10,20,30,40]

 

从图示中,我们可以看出序列中存储的整数对象的地址,而不是整数对象的值。python中常用的序列结构有:

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

我们上一章学习的字符串就是一种序列。关于字符串里面很多操作,在这一章中仍然会用到,大家一定会感觉非常熟悉。

 

列表简介

         列表:用于存储任意数目、任意类型的数据集合。

         列表是内置可变序列,是包含多个元素的有序连续的内存空间。列表定义的标准语法格式:

                                a  =  [10,20,30,40]

         其中,10,20,30,40这些称为:列表a的元素。

         列表中元素可以各不相同,可以是任意类型。比如:

                              a  =  [10,20,'abc',True]

列表对象的常用方法汇总如下,

方法要点描述
list.append(x)增加元素将元素x增加到列表list尾部
list.extend(aList)增加元素将列表alist所有元素加到列表list尾部
list.insert(index,x)增加元素在列表list指定位置index处插入元素x
list.remove(x)删除元素在列表list中删除首次出现的指定元素x
list.pop([index])删除元素删除并返回列表list指定为止index处的元素,默认是最后一个元素
list.clear()删除所有元素删除列表所有元素,并不是删除列表对象
list.index(x)访问元素返回第一个x的索引位置,若不存在x元素抛出异常
list.count(x)计数返回指定元素x在列表list中出现的次数
len(list)列表长度返回列表中包含元素的个数
list.reverse()翻转列表所有元素原地翻转
list.sort()排序所有元素原地排序
list.copy()浅拷贝返回列表对象的浅拷贝

Python的列表大小可变,根据需要随时增加或缩小。

 

字符串和列表都是序列类型,一个字符串是一个字符序列,一个列表是任何元素的序列。

 

列表的创建

基本语法[]创建

>>> a=[10,20,'gaoqi','sxt']
>>> a=[]               #创建一个空的列表对象

list()创建

使用list()可以将任何可迭代的数据转化成列表。

>>> a=list()
>>> a=list(range(10))
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> a=list("gaoqi,sxt")
>>> a
['g', 'a', 'o', 'q', 'i', ',', 's', 'x', 't']

 

range()创建整数列表

range()可以帮助我们非常方便的创建整数列表,这在开发中及其有用。语法格式为:

                                                     range([start],end,[step])

start参数:可选,表示起始数字。默认是0

end参数:必选,表示结尾数字。

step参数:可选,表示步长,默认为1

python3中range()返回的是一个range对象,而不是列表。我们需要通过list()方法将其转换成列表对象。

典型示例如下:

>>> list(range(3,15,2))
[3, 5, 7, 9, 11, 13]
>>> list(range(15,3,-1))
[15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4]
>>> list(range(3,-10,-1))
[3, 2, 1, 0, -1, -2, -3, -4, -5, -6, -7, -8, -9]

 

推导式生成列表

使用列表推导式可以非常方便的创建列表,在开发中经常使用。但是,由于涉及到for循环和if语句。

>>> a=[x*2 for x in range(5)]                  #循环创建多个元素
>>> a
[0, 2, 4, 6, 8]
>>> a=[x*2 for x in range(100) if x%9==0]           #通过if过滤元素
>>> a
[0, 18, 36, 54, 72, 90, 108, 126, 144, 162, 180, 198]

 

 

列表元素的增加和删除

       当列表增加和删除元素时,列表会自动进行内存管理,大大减少了程序员的负担。但这个特点涉及列表元素的大量移动,效率较低。除非必要,我们一般只在列表的尾部添加元素或删除元素,这会大大提高列表的操作效率。

 

append()方法

原地修改列表对象,是真正的列表尾部添加新的元素,速度最快,推荐使用。

>>> a=[20,40]
>>> a.append(80)
>>> a
[20, 40, 80]

+运算符操作

并不是真正的尾部添加元素,而是创建新的列表对象;将原列表的元素和新列表的元素依次复制到新的列表对象中。这样,会涉及大量的复制操作,对于操作大量元素不建议使用。

>>> a=[20,40]
>>> id(a)
1831775170504
>>> a=a+[50]
>>> id(a)
1831775169800

通过如上测试,我们发现变量a的地址发生了变化。也就是创建了新的列表对象。

extend()方法

将目标列表的所有元素添加到本列表的尾部,属于原地操作,不创建新的列表对象。

>>> a=[20,40]
>>> id(a)
1831775170056
>>> a.extend([50,60])
>>> id(a)
1831775170056

insert()插入元素

使用insert()方法可以将指定元素插入到列表对象的任意指定位置。这样会让插入位置后面所有的元素进行移动,会影响处理速度。涉及大量元素时,尽量避免使用。类似发生这种移动的函数还有:remove()、pop()、del(),它们在删除非尾部元素时也会发生操作位置后面元素的移动。

>>> a=[10,20,30]
>>> a.insert(2,100)
>>> a
[10, 20, 100, 30]

乘法扩展

使用乘法扩展列表,生成一个新列表,新列表元素是原列表元素的多次重复。

>>> a=['sxt',100]
>>> b=a*3
>>> a
['sxt', 100]
>>> b
['sxt', 100, 'sxt', 100, 'sxt', 100]

适用于乘法操作的,还有:字符串、元组,例如:

>>> c='sxt'
>>> d=c*3
>>> c
'sxt'
>>> d
'sxtsxtsxt'

列表元素的删除

 

del删除

删除列表指定位置的元素。

>>> a=[10,20,30]
>>> del a[1]
>>> a
[10, 30]

pop()方法

pop()删除并返回指定位置元素,如果未指定位置则默认操作列表最后一个元素。

>>> a=[10,20,30,40,50]
>>> a.pop()
50
>>> a
[10, 20, 30, 40]

>>> a.pop(1)
20
>>> a
[10, 30, 40]

remove()方法

删除首次出现的指定元素,若不存在该元素抛出异常。

>>> a=[10,20,30,40,50,20,30,20,30]
>>> a.remove(20)
>>> a
[10, 30, 40, 50, 20, 30, 20, 30]
>>> a.remove(60)
Traceback (most recent call last):
  File "<pyshell#144>", line 1, in <module>
    a.remove(60)
ValueError: list.remove(x): x not in list

 

列表元素访问和计数

通过索引直接访问元素

 我们可以通过索引直接访问元素。索引的区间在[0,列表长度-1]这个范围。超过这个范围则会抛出异常。

>>> a=[10,20,30,40,50,20,30,20,30]
>>> a[2]
30
>>> a[10]
Traceback (most recent call last):
  File "<pyshell#147>", line 1, in <module>
    a[10]
IndexError: list index out of range

 

index()获得指定元素在列表中首次出现的索引

index()可以获得指定元素首次出现的索引位置。语法:index(value,[start,[end]])。其中,

start和end指定了搜索的范围。

>>> a=[10,20,30,40,50,20,30,20,30]
>>> a.index(20)
1
>>> a.index(20,3)       #从索引位置3开始往后搜索的第一个20
5
>>> a.index(30,5,7)      #从索引位置5到7这个区间,第一次出现30元素的位置
6

 

count()获得指定元素在列表中出现的次数

count()可以返回指定元素在列表中出现的次数。

>>> a=[10,20,30,40,50,20,30,20,30]
>>> a.count(20)
3

 

len()返回列表长度

>>> a=[10,20,30]
>>> len(a)
3

 

成员资格判断

判断列表中是否存在指定的元素,我们可以使用count()方法,返回0则表示不存在,返回大于0则表示存在。但是,一般我们会使用更加简洁的in关键字来判断,直接返回True或False。

>>> a=[10,20,30,40,50,20,30,20,30]
>>> 20 in a
True
>>> 100 not in a
True

 

切片操作

切片是Python序列及其重要的操作,适用于列表、元组、字符串等等。切片的格式如下

  切片slice操作可以让我们快速提取子列表或修改。标准格式为:

                 [起始偏移量 start:终止偏移量 end[:步长 step]]

注:当步长省略时顺便可以省略第二个冒号

典型操作(三个量为正数的情况)如下:

 

 

其他操作(三个量为负数)的情况:

切片操作时,起始偏移量和终止偏移量不在[0,字符串长度-1]这个范围,也不会报错。起始偏移量小于0则会当做0,终止偏移量大于“长度-1”会被当成-1。例如:

>>> [10,20,30,40][1:30]
[20, 30, 40]

 

列表的遍历

           for obj in listObj:

                print(obj)

 

列表排序

修改原列表,不建新列表的排序

>>> a=[20,10,30,40]
>>> id(a)
1831774893256
>>> a.sort()                           #默认是升序排列


>>> a
[10, 20, 30, 40]
>>> a=[10,20,30,40]
>>> a.sort(reverse=True)      #降序排列
>>> a
[40, 30, 20, 10]

>>> import random
>>> random.shuffle(a)            #打乱顺序
>>> a
[10, 40, 30, 20]

 

建新列表的排序

我们可以通过内置函数sorted()进行排序,这个方法返回新列表,不对原列表做修改。

>>> a=[20,10,30,40]
>>> id(a)
1831775170504
>>> a=sorted(a)                        #默认升序
>>> a
[10, 20, 30, 40]
>>> id(a)
1831775179080

>>> b=sorted(a)
>>> b
[10, 20, 30, 40]
>>> id(a)
1831775179080
>>> id(b)
1831774893896
>>> c=sorted(a,reverse=True)             #降序
>>> c
[40, 30, 20, 10]

 

reversed()返回迭代器

内置函数reversed()也支持进行逆序排列,与列表对象reverse()方法不同的是,内置函数reversed()不对原列表做任何修改,只是返回一个逆序排列的迭代器对象。

>>> a=[20,10,30,40]
>>> c=reversed(a)
>>> c
<list_reverseiterator object at 0x000001AA7E4BA7F0>
>>> list(c)
[40, 30, 10, 20]
>>> list(c)
[]

我们打印输出c发现提示是:list_reverseiterator.也就是一个迭代对象。同时,我们使用list(c)进行输出,发现只能使用一次。第一次输出了元素,第二次为空。那是因为迭代对象在第一次时已经遍历结束了,第二次不能再使用了。

 

列表相关的其他内置函数汇总

max和min

用于返回列表中最大和最小值。

>>> a=[3,10,20,15,9]
>>> max(a)
20
>>> min(a)
3

sum

对数值型列表的所有元素进行求和操作,对非数值型列表运算则会报错。

>>> a=[3,10,20,15,9]
>>> sum(a)
57

 

多维列表

二维列表

一维列表可以帮助我们存储一维、线性的数据。

二维列表可以帮助我们存储二维、表格的数据。例如下表的数据:

姓名年龄薪资城市
高小一1830000北京
高小二1920000上海
高小五2010000深圳

 

a=[

          ["高小一",18,30000,“北京”],

          ["高小二",19,20000,“上海”],

          ["高小五",20,10000,“深圳”],

    ]

for m in range(3):

      for n in range(4):

            print(a[m][n],endl="\t")

      print() #打印完一行,换行

 

元组tuple

列表属于可变序列,可以任意修改列表中的元素。元组属于不可变序列,不能修改元组中的元素。因此,元组没有增加元素、修改元素、删除元素相关的方法。

因此,我们只需要学习元组的创建和删除,元组中元素的访问和计数即可。元组支持如下操作:

                 1.索引访问

                 2.切片操作

                 3.连接操作

                 4.成员关系操作

                 5.比较运算操作

                 6.计数:元组长度len()、最大值max()、最小值min()、求和sum()等。

 

元组的创建

1.通过()创建元组。小括号可以省略。

            a=(10,20,30)               或者             a=10,20,30

如果元组只有一个元素,则必须后面加逗号。这是因为解释器会把(1)解释为整数1,(1,)解释为元组。

  

>>> a=(1)
>>> type(a)
<class 'int'>
>>> a=(1,)                          #或者           a=1,
>>> type(a)
<class 'tuple'>
 

2.通过tuple()创建元组

tuple(可迭代的对象)

例如:

b=tuple()                   #创建一个空元组对象

b=tuple("abc")

b=tuple(range(3))

b=tuple([2,3,4])

总结: 

                 tuple()可以接收列表、字符串、其他序列类型。迭代器等生成元组。

                 list()可以接收元组、字符串、其他序列类型、迭代器等生成列表。

 

元组的元素访问和计数

1.元组的元素不能修改

>>> a=(20,10,30,9,8)
>>> a[3]=33
Traceback (most recent call last):
  File "<pyshell#200>", line 1, in <module>
    a[3]=33
TypeError: 'tuple' object does not support item assignment

2.元组的元素访问和列表一样,只不过返回的仍然是元组对象。

>>> a=(20,10,30,9,8)
>>> a[1]
10
>>> a[1:3]
(10, 30)
>>> a[:4]
(20, 10, 30, 9)

3.列表关于排序的方法list.sorted()是修改原列表对象,元组没有该方法。如果要对元组排序,只能使用内置函数sorted(tupleObj),

并生成新的列表对象。

>>> a=(20,10,30,9,8)
>>> sorted(a)
[8, 9, 10, 20, 30]

 

zip

zip(列表1,列表2,……)将多个列表对应位置的元素组合成为元组,并返回这个zip对象。

>>> a=[10,20,30]
>>> b=[40,50,60]
>>> c=[70,80,90]
>>> d=zip(a,b,c)
>>> list(d)
[(10, 40, 70), (20, 50, 80), (30, 60, 90)]

生成器推导式创建元组

          从形式上看,生成器推导式与列表推导式类似,只是生成器推导式使用小括号。列表推导式直接生成列表对象,生成器推导式生成的不是列表也不是元组,而是一个生成器对象。

          我们可以通过生成器对象,转化成列表或者元组。也可以使用生成器对象的_next_()方法进行遍历,或者直接作为迭代器对象来使用。不管什么方式使用,元素访问结束后,如果需要重新访问其中的元素,必须重新创建该生成器对象。

【操作】生成器的使用测试

>>> s=(x*2 for x in range(5))
>>> s
<generator object <genexpr> at 0x000001AA7E4AD660>
>>> tuple(s)
(0, 2, 4, 6, 8)
>>> list(s)           #只能访问一次元素。第二次就为空了。需要再生成一次
[]

>>> s=(x*2 for x in range(5))
>>> s
<generator object <genexpr> at 0x0000025AFCA396D8>
>>> tuple(s)
(0, 2, 4, 6, 8)
>>> tuple(s)
()
>>> s=(x*2 for x in range(5))
>>> s._next_()

0

>>> s._next_()

2

>>> s._next_()

4

元组总结

       1.元组的核心特点是:不可变序列。

        2.元组的访问和处理速度比列表快。

        3.与整数和字符串一样,元组可以作为字典的键,列表则永远不能作为字典的键使用。

 

字典

字典是“键对值”的无序可变序列,字典中的每个元素都是一个“键对值”,包含:“键对象”和“值对象”。可以通过“键对象”实现快速获取、删除、更新对应的“值对象”,

列表中我们通过“下标数字”找到对应的对象。字典中通过“键对象”找到对应的“值对象”。“键”是任意的不可变数据,比如:整数、浮点数、字符串、元组。但是:列表、字典、集合这些可变对象,不能作为“键”,而且“键”不可重复。

“值”是任意的数据,并且课重复。

一个典型的字典的定义方式:

                                            a={'name':'gaoqi','age':'18','job':'programmer'}

字典的创建

1.我们可以通过{}、dict()来创建字典对象。

>>> a={'name':'gaoqi','age':'18','job':'programmer'}
>>> b=dict(name='gaoqi',age=18,job='programmer')
>>> a=dict([("name","gaoqi"),("age",18)])
>>> c={}                    #空的字典对象
>>> d=dict()               #空的字典对象

2.通过zip()创建字典对象

>>> k=['name','age','job']
>>> v=['gaoqi','18','programmer']
>>> d=dict(zip(k,v))
>>> d
{'name': 'gaoqi', 'age': '18', 'job': 'programmer'}

3.通过fromkeys创建值为空的字典

>>> a=dict.fromkeys(['name','age','job'])
>>> a
{'name': None, 'age': None, 'job': None}

 

字典元素的访问

为了测试各种访问方法,我们这里设定一个字典对象:

          a={'name':'gaoqi','age':'18','job':'programmer'}

1.通过[键]获得“值”。若键不存在,则抛出异常。

>>> a={'name':'gaoqi','age':'18','job':'programmer'}
>>> a['name']
'gaoqi'
>>> a['age']
'18'
>>> a['sex']
Traceback (most recent call last):
  File "<pyshell#18>", line 1, in <module>
    a['sex']
KeyError: 'sex'

2.通过get()方法获得“值”。推荐使用。优点是:指定键不存在,返回None;也可以设定指定键不存在时默认返回的对象。推荐使用get()获取“值对象”。

>>> a.get('name')
'gaoqi'
>>> a.get('sex')
>>> a.get('sex','一个人')
'一个人'

3.列出所有的键值对

>>> a.items()
dict_items([('name', 'gaoqi'), ('age', '18'), ('job', 'programmer')])

4.列出所有的键,列出所有的值

>>> a.keys()
dict_keys(['name', 'age', 'job'])
>>> a.values()
dict_values(['gaoqi', '18', 'programmer'])

5.len() 键值对的个数

6.检测一个“键”是否在字典中

>>> a={"name":"gaoqi","age":18}
>>> "name" in a
True

 

字典元素添加、修改、删除

1.给字典新增“键对值”,如果“键”已经存在,则覆盖就得键对值,如果“键”不存在,

则新增“键值对”.

>>> a={'name':'gaoqi','age':'18','job':'programmer'}
>>> a['address']='西三旗1号院'
>>> a['age']=16
>>> a
{'name': 'gaoqi', 'age': 16, 'job': 'programmer', 'address': '西三旗1号院'}

2.使用update()将新字典中所有键值对全部添加到旧字典对象上,如果key有重复,则直接覆盖。

>>> a={'name':'gaoqi','age':18,'job':'programmer'}
>>> b={'name':'gaoxixi','money':1000,'sex':'男'}
>>> a.update(b)
>>> a
{'name': 'gaoxixi', 'age': 18, 'job': 'programmer', 'money': 1000, 'sex': '男'}
 

3.字典中元素的删除,可以使用del()方法;或者clear()删除所有键值对;pop()删除指定键值对,并返回对应的“值对象”;

>>> a={'name':'gaoqi','age':18,'job':'programmer'}
>>> del(a['name'])
>>> a
{'age': 18, 'job': 'programmer'}
>>> a.pop('age')
18
>>> a
{'job': 'programmer'}

4.popitem():随机删除和返回该键值对,字典是“无序可变序列”,因此没有第一个元素、最后一个元素的概念;popitem()弹出随机的项,因为字典并没有“最后的元素”或者其他有关顺序的概念。若想一个接一个地移除并处理项,这个方法就非常有效(因为不用首先获取键的列表)。

>>> a={'name':'gaoqi','age':18,'job':'programmer'}
>>> a.popitem()
('job', 'programmer')
>>> a
{'name': 'gaoqi', 'age': 18}
>>> a.popitem()
('age', 18)
>>> a
{'name': 'gaoqi'}

 

序列解包

序列解包可以用于元组、列表、字典。序列解包可以让我们方便的对多个变量赋值。

>>> x,y,z=(20,30,10)
>>> x
20
>>> y
30
>>> z
10
>>> (a,b,c)=[9,8,10]
>>> a
9
>>> b
8
>>> [a,b,c]=[10,20,30]
>>> a
10
>>> b
20

序列解包用于字典时,默认是对“键”进行操作;如果需要对键值对操作,则需要使用items();如果需要对“值”进行操作,则需要使用values();

>>> s={'name':'gaoqi','age':18,'job':'programmer'}
>>> name,age,job=s
>>> name
'name'
>>> name,age,job=s.items()
>>> name
('name', 'gaoqi')
>>> name,age,job=s.values()
>>> name
'gaoqi'

 

表格数据使用字典和列表存储,并实现访问

姓名年龄薪资城市
高小一1830000北京
高小二1920000上海
高小五2010000深圳

 

{"name":"高小一","age":18,"salary":3000,"city":"北京"}
r2={"name":"高小二","age":19,"salary":2000,"city":"上海"}
r3={"name":"高小五","age":20,"salary":1000,"city":"深圳"}


tb=[r1,r2,r3]

#获得第二行的人的薪资
print(tb[1].get("salary"))


#打印表中所有的薪资
for i in range(len(tb)):
    print(tb[i].get("salary"))

#打印表的所有数据
for i in range(len(tb)):
    print(tb[i].get("name"),tb[i].get("age"),tb[i].get("salary"),tb[i].get("city"))
    

字典核心底层原理

字典对象的核心是散列表。散列表是一个稀疏数组(总是有空白元素的数组),数组的每个单元叫做bucket,每个bucket有两个部分:一个是键对象的引用,一个是值对象的引用。

由于,所有bucket结构和大小一致,我们可以通过偏移量来读取指定bucket.

 

 

将一个键值对放进字典的底层过程

>>>a={}

>>>

a["name"="gaoqi"]

假设字典a对象创建完后,数组长度为8

 我们要把“name”="gaoqi"这个键值对放到字典对象a中,首先第一步需要计算键“name”的散列值。Python中可以通过hash()来计算。

>>> bin(hash("name"))
'0b110011011100010010000011001010100010110010101011000100000000010'

由于数组长度为8,我们可以拿计算出的散列值的最右边3位数字作为偏移量,即“101”,十进制是数字5.我们查看偏移量5,对应的ducket是否为空。如果为空,则将键值对放进去。如果不为空,则依次取右边3位作为偏移量,即“100”,十进制是数字4。在查看偏移量为4的bucket是否为空。直到找到为空的bucket将键值对放进去。流程图如下:

扩容

      python会根据散列表的拥挤程度扩容。“扩容”指的是:创造更大的数组,将原来内容拷贝到新数组中。

      接近2/3时,数组就会扩容。

根据键查找“键值对”的底层过程

           我们明白了,一个键值对是如何存储到数组中的,根据键对象取到值对象,理解起来就简单了。

>>> a={"name":"gaoqi","age":21,"job":"student"}
>>> a.get("name")
'gaoqi'

当我们调用a.get("name"),就是根据键“name”查找到“键值对”,从而找到值对象“gaoqi”。

第一步,我们仍然要计算“name”对象的散列值:

>>> bin(hash("name"))
'0b110011011100010010000011001010100010110010101011000100000000010'

和存储的底层流程算法一致,也是依次取散列值的不同位置的数字。假设数组长度为8,我们可以拿计算出的散列值的最右边3位数字作为偏移量,即“101”,十进制是数字5.我们查看偏移量5,对应的bucket是否为空。如果为空,则返回None.如果不为空,则将这个bucket的键对象计算对应散列值,和我们的散列值进行比较,如果相等,这将对应“值对象”返回。如果不相等,则再依次取其他几位数字,重新计算偏移量。依次取完后仍然没有找到。则返回None。流程图如下:

用法总结:

1.键必须可散列

         (1)数字、字符串、元组,都是可散列的。

         (2)自定义对象需要支持下面三点:

              ①支持hash()函数

              ②支持通过_eq_()方法检测相等性。

              ③若a==b为真,则hash(a)==hash(b)也为真。

2.字典在内存中开销巨大,典型的空间换时间。

3.键查询速度很快

4.网字典里面添加新建可能导致扩容,导致散列表中键的次序变化。因此,不要在遍历字典的同时进行字典的修改。

 

集合

集合是无序可变,元素不能重复。实际上,集合底层是字典实现,集合的所有元素都是字典中的“键对象”,因此是不能重复的且唯一的。

集合创建和删除

1.使用{}创建集合对象,并使用add()方法添加元素

>>> a={3,5,7}
>>> a
{3, 5, 7}
>>> a.add(9)
>>> a
{9, 3, 5, 7}

2.使用set(),将列表、元组等可迭代对象转成集合。如果原来数据存在重复数据,则只保留一个。

>>> a={'a','b','c'}
>>> b=set(a)
>>> b
{'b', 'a', 'c'}

3.remove()删除指定元素;clear()清空整个集合

>>> a={10,20,30,40,50}
>>> a.remove(20)
>>> a
{40, 10, 50, 30}

集合相关操作

像数学中概念一样,Python对集合也提供了并集、交集、差集等运算。我们给出示例:

>>> a={1,3,'sxt'}
>>> b={'he','it','sxt'}
>>> a|b                         #并集
{'sxt', 1, 3, 'it', 'he'}
>>> a&b                        #交集
{'sxt'}
>>> a-b                          #差集
{1, 3}
>>> a.union(b)               #并集
{'sxt', 1, 3, 'it', 'he'}
>>> a.intersection(b)     #交集
{'sxt'}
>>> a.difference(b)       #差集
{1, 3}

 

 

控制语句

选择结构

选择结构通过判断条件是否成立,来决定执行那个分支。选择结构多种形式,分为:单分支、双分支、多分支。流程图如下:

单分支选择结构

if语句单分支结构的语法形式如下:

if条件表达式

    语句/语句块

其中:

①. 条件表达式:可以是逻辑表达式,关系表达式,算术表达式等等。

②. 语句/语句块:可以是一条语句,也可以是多条语句,缩进必须对齐一致。

【操作】输入一个数字,小于10,则打印这个数字

num=input("输入一个数字")
if int(num)<10:
    print(num)

 

条件表达式详解

在选择和循环结构中,条件表达式的值为False的情况如下:

 False、0、0.0、空值None、空序列对象(空列表、空元组、空集合、空字典、空字符串)、空range对象、空迭代对象。

其他情况,均为True.这么看来,Python所有的合法表达式都可以看做条件表达式,甚至包括函数调用的表达式。

【操作】测试各种条件表达式

if 3:                                  #整数作为条件表达式

         print("ok")

a=[]                                  #列表作为条件表达式,由于空列表,是False

if a:

         print("空列表,False")

s='False'                             #非空字符串,是True

if s:

         print("非空字符串,是True")

c=9

if 3<c<20:

          print("3<c<20")

if 3<c and c<20:

         print("3<c and c<20")

if True:

        print("True")

条件表达式中,不能有赋值操作符“=”

在Python中,条件表达式不能出现赋值操作符“=”,避免了其他语言中经常误将关系运算符“==”写作赋值运算符“=”带来的困扰。如下代码将会报语法错误:

if 3<c and (c=20):

   print("赋值符不能出现在条件表达式中")

双分支选择结构

双分支结构的语法格式如下:

多分支选择结构

多分支结构,几个分支之间是有逻辑关系的,不能随意颠倒顺序。

【操作】输入一个学生的成绩,将其转化成简单描述:不及格(小于60)、及格(60-79)、良好(80-89)、优秀(90-100)。

方法一(使用完整的条件表达)

score=int(input("请输入分数:"))
grade=''
if(score<60):
    grade='不及格'
if(60<=score<80):
    grade='及格'
if(80<=score<89):
    grade='良好'
if(90<=score<=100):
    grade='优秀'
print("分数是{0},等级是{1}".format(score,grade))

运行结果:

请输入分数:98
分数是98,等级是优秀
 

每个分支都使用了独立的、完整的判断,顺序可以随意挪动,而不影响程序运行。

方法二(利用多分支结构)

score=int(input("请输入分数:"))
grade=''
if score<60:
    grade='不及格'
elif score<80:
    grade='及格'
elif score<89:
    grade='良好'
elif score<=100:
    grade='优秀'
print("分数是{0},等级是{1}".format(score,grade))

运行结果:

请输入分数:87
分数是87,等级是良好
 

【操作】已知点的坐标(x,y),判断其所在的象限

x=int(input("请输入x的坐标"))
y=int(input("请输入y的坐标"))
if(x==0 and y==0):print("原点")
elif(x==0):print("y轴")
elif(y==0):print("x轴")
elif(x>0 and y>0):print("第一象限")
elif(x<0 and y>0):print("第二象限")
elif(x<0 and y<0):print("第三象限")
else:
    print("第四象限")

运行结果:

请输入x的坐标1
请输入y的坐标3
第一象限
 

 

选择结构嵌套

 

循环结构

循环结构用来重复执行一条或多条语句。表达这样的逻辑:如果符合条件,则反复执行循环体里的语句。在每次执行完后都会判断一次条件是否为True,如果为True则重复执行循环体里的语句。图示如下:

循环体里面的语句至少应该包含改变条件表达式的语句,以使循环趋于结束;否则,就会变成一个死循环。

 

while循环

while循环的语法格式如下:

while 条件表达式:

      循环体语句

【操作】利用while循环打印从0-10的数字。

num=0
while num<=10:
    print(num)
    num+=1

运行结果:

0
1
2
3
4
5
6
7
8
9
10

 

for循环和可迭代对象遍历

for循环通常用于可迭代对象的遍历。for循环的语法格式如下:

for 变量  in 可迭代对象:

      循环语句

【操作】遍历一个元组或列表

for x in (20,30,40):
    print(x*3)

运行结果:

60
90
120
 

可迭代对象

Python包含以下几种可迭代对象:

1.序列。包含:字符串,列表、元组

2.字典

3.迭代器对象(iterator)

4.生成器函数(generator)

5.文件对象

【操作】遍历字符串中的字符

for x in "sxt001":
    print(x)

运行结果:

s
x
t
0
0
1

【操作】遍历字典

d={'name':'gaoqi','age':18,'job':'programmer'}
for x in d:
    print(x)           #遍历字典所有的key
for x in d.keys():
    print(x)           #遍历字典所有的key
for x in d.values():
    print(x)            #遍历字典所有的value
for x in d.items():
    print(x)            #遍历字典所有的“键值对”

运行结果:

name
age
job
name
age
job
gaoqi
18
programmer
('name', 'gaoqi')
('age', 18)
('job', 'programmer')
 

range对象

range对象是一个迭代器对象,用来产生指定范围的数字序列。格式为:

                                      range(start,end,[step])

生成的数值序列从start开始到end结束(不包含end)。若没有填写start,则默认从0开始。step是可选的步长,默认为1.如下是几种典型示例:

for i in range(10)                 产生序列:0,1,2,3,4,5,6,7,8,9

for i in range(3,10)                 产生序列:3,4,5,6,7,8,9

for i in range(3,10,2)                 产生序列:3,5,7,9

【操作】利用for循环,计算1-100之间数字的累加和;计算1-100之间偶数的累加和,计算1-100之间奇数的累加和。

sum_all=0
sum_odd=0
for i in range(101):
    sum_all+=i
    if i%2==1:
        sum_odd+=i
print(sum_all)
print(sum_odd)

运行结果:

5050
2500
 

嵌套循环

一个循环内可以嵌入另一个循环,一般称为“嵌套循环”,或者“多重循环”。

【操作】打印如下图案

                                  

for x in range(5):
    for y in range(5):
        print(x,end="\t")
    print()            #起到换行的作用

【操作】利用嵌套循环打印九九乘法表

for m in range(1,10):
    s=""
    for n in range(1,m+1):
        s+=str.format("{0}*{1}={2}\t",m,n,m*n)
    print(s)

 

break语句

break语句可用于while和for循环,用来结束整个循环。当有嵌套循环时,break语句只能跳出最近一层的循环。

【操作】使用break语句结束循环

while True:
    a=input("请输入一个字符(输入Q或q结束)")
    if a.upper()=='Q':
        print("循环结束,退出")
        break
    else:
        print(a)

continue语句

continue语句用于结束本次循环,继续下一次。多个循环嵌套时,continue也是应用于最近的一层循环。

【操作】要求输入员工的薪资,若薪资小于0则重新输入。最后打印出录入员工的数量和薪资明细,以及平均薪资。

empNum=0
salarySum=0
salarys=[]
while True:
    s=input("请输入员工的薪资(按Q或q结束)")

    if s.upper()=='Q':
        print("录入完成,退出")
        break
    if float(s)<0:
        continue
    empNum+=1
    salarys.append(float(s))
    salarySum+=float(s)

print("员工数{0}".format(empNum))
print("录入薪资:",salarys)
print("平均薪资{0}".format(salarySum/empNum))

运行结果:

请输入员工的薪资(按Q或q结束)20000
请输入员工的薪资(按Q或q结束)10000
请输入员工的薪资(按Q或q结束)q
录入完成,退出
员工数3
录入薪资: [20000.0, 20000.0, 10000.0]
平均薪资16666.666666666668
 

else语句

 

循环代码优化

import time
start=time.time()
for i in range(1000):
    result=[]
    for m in range(10000):
        result.append(i*1000+m*100)

end=time.time()
print("耗时:{0}".format((end-start)))

start2=time.time()
for i in range(1000):
    result=[]
    c=i*1000
    for m in range(10000):
        result.append(c+m*100)
end2=time.time()
print("耗时:{0}".format((end2-start2)))

其他优化手段:

1.连续多个字符串,使用join()而不使用+

2.列表进行元素插入和删除,尽量在列表尾部操作

 

使用zip()并行迭代

我们可以通过zip()函数对多个序列进行并行迭代,zip()函数在最短序列“用亮”时就会停止。

【操作】测试zip()并行迭代

names=("高琪","高老二","高老三","高老三")
ages=(18,16,20,25)
jobs=("老师","程序员","公务员")
for name,age,job in zip(names,ages,jobs):
    print("{0}--{1}--{2}".format(name,age,job))

执行结果:

高琪--18--老师
高老二--16--程序员
高老三--20--公务员
 

 

推导式创建序列

推导式是从一个或多个迭代器快速创建序列的一种方法。它可以将循环和条件判断结合,从而避免冗长的代码。推导式是典型的Python初学者的水平。

列表推导式

列表推导式生成列表对象,语法如下:

                                     [表达式   for item in 可迭代对象]

或者 :[表达式 for item in 可迭代对象  if  条件判断]

>>> [x for x in range(1,5)]
[1, 2, 3, 4]
>>> [x*2 for x in range(1,5)]
[2, 4, 6, 8]
>>> [x*2 for x in range(1,20) if x%5==0]
[10, 20, 30]
>>> [a for a in "abcdefg"]
['a', 'b', 'c', 'd', 'e', 'f', 'g']
>>> cells=[(row,col) for row range(1,10) for col in range(1,10)]
SyntaxError: invalid syntax
>>> cells=[(row,col) for row in range(1,10) for col in range(1,10)]
>>> print(cells)
[(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (2, 6), (2, 7), (2, 8), (2, 9), (3, 1), (3, 2), (3, 3), (3, 4), (3, 5), (3, 6), (3, 7), (3, 8), (3, 9), (4, 1), (4, 2), (4, 3), (4, 4), (4, 5), (4, 6), (4, 7), (4, 8), (4, 9), (5, 1), (5, 2), (5, 3), (5, 4), (5, 5), (5, 6), (5, 7), (5, 8), (5, 9), (6, 1), (6, 2), (6, 3), (6, 4), (6, 5), (6, 6), (6, 7), (6, 8), (6, 9), (7, 1), (7, 2), (7, 3), (7, 4), (7, 5), (7, 6), (7, 7), (7, 8), (7, 9), (8, 1), (8, 2), (8, 3), (8, 4), (8, 5), (8, 6), (8, 7), (8, 8), (8, 9), (9, 1), (9, 2), (9, 3), (9, 4), (9, 5), (9, 6), (9, 7), (9, 8), (9, 9)]
>>> for cell in cells:
    print(cell)

    
(1, 1)
(1, 2)
(1, 3)
(1, 4)
(1, 5)
(1, 6)
(1, 7)
(1, 8)
(1, 9)
(2, 1)
(2, 2)
(2, 3)
(2, 4)
(2, 5)
(2, 6)
(2, 7)
(2, 8)
(2, 9)
(3, 1)
(3, 2)
(3, 3)
(3, 4)
(3, 5)
(3, 6)
(3, 7)
(3, 8)
(3, 9)
(4, 1)
(4, 2)
(4, 3)
(4, 4)
(4, 5)
(4, 6)
(4, 7)
(4, 8)
(4, 9)
(5, 1)
(5, 2)
(5, 3)
(5, 4)
(5, 5)
(5, 6)
(5, 7)
(5, 8)
(5, 9)
(6, 1)
(6, 2)
(6, 3)
(6, 4)
(6, 5)
(6, 6)
(6, 7)
(6, 8)
(6, 9)
(7, 1)
(7, 2)
(7, 3)
(7, 4)
(7, 5)
(7, 6)
(7, 7)
(7, 8)
(7, 9)
(8, 1)
(8, 2)
(8, 3)
(8, 4)
(8, 5)
(8, 6)
(8, 7)
(8, 8)
(8, 9)
(9, 1)
(9, 2)
(9, 3)
(9, 4)
(9, 5)
(9, 6)
(9, 7)
(9, 8)
(9, 9)

字典推导式

字典的推导式生成字典对象,格式如下:

                   {key_expression :  value_expression     for    表达式      in       可迭代对象}

类似于列表推导式,字典推导也可以增加if条件判断、多个for循环。

统计文本字符出现的次数:

         

>>> my_text='i love you,i love sxt,i love gaoqi'
>>> char_count={c:my_text.count(c) for c in my_text}
>>> char_count
{'i': 4, ' ': 6, 'l': 3, 'o': 5, 'v': 3, 'e': 3, 'y': 1, 'u': 1, ',': 2, 's': 1, 'x': 1, 't': 1, 'g': 1, 'a': 1, 'q': 1}

集合推导式

集合推导式生成集合,和列表推导式的语法格式类似:

                                {表达式   for item in 可迭代对象 }

                  或者:{表达式     for    item    in   可迭代对象     if    条件判断}

>>> {x for x in range(1,100) if x%9==0}
{99, 36, 72, 9, 45, 81, 18, 54, 90, 27, 63}

生成器推导式(生成元组)

>>> (x for x in range(1,100) if x%9==0)
<generator object <genexpr> at 0x000001F1ADDA96D8>

我们发现提示的是“一个生成器对象”。显然,元组是没有推导式的。

一个生成器只能运行一次。第一次迭代可以得到数据,第二次迭代发现数据已经没有了。

>>> gnt=(x for x in range(1,100) if x%9==0)
>>> for x in gnt:
    print(x,end=' ')

    
9 18 27 36 45 54 63 72 81 90 99 
>>> for x in gnt:
    print(x,end='')

    

                函数用法和底层分析

函数的基本概念

1.一个程序由一个个任务组成:函数就是代表一个任务或者一个功能。

2.函数是代码复用的通用机制。

Python函数的分类

1.内置函数

     我们前面使用的str(),list(),len()等这些都是内置函数,我们可以拿来直接使用。

2.标准库函数

     我们可以通过import语句导入库,然后使用其中定义的函数。

3.第三方库函数

      Python社区也提供了很多高质量的库。下载安装这些库后,也通过import语句导入,然后可以使用这些第三方库的函数

4.用户自定义函数

     用户自己定义的函数,显然也是开发中适应用户自身需求定义的函数。

 

函数定义和调用

核心要点

Python中,定义函数的语法如下:

def      函数名([参数列表]):

       '''文档字符串'''

  函数体/若干语句

要点:

1.我们使用def来定义函数,然后就是一个空格和函数名称;

    (1)Python执行def时,会创建一个函数对象,并绑定到函数名变量上。

2.参数列表

    (1)圆括号内形式参数列表,有多个参数则使用逗号隔开

    (2)形式参数不需要声明类型,也不需要指定函数返回值类型

    (3)无参数,也必须保留空的圆括号

    (4)实参列表必须与形参列表一一对应

3.return返回值

    (1)如果函数体中包含return语句,则结束函数执行并返回值;

    (2)如果函数体中不包含return语句,则返回None值。

4.调用函数之前,必须要先定义函数,即先调用def创建函数对象

    (1)内置函数对象会自动创建

    (2)标准库和第三方库函数,通过import 导入模块时,会执行模块中的def语句

 

形参和实参

【操作】定义一个函数,实现两个数的比较,并返回较大的值。

def printMax(a,b):
    '''实现两个数的比较,并返回较大的值'''
    if a>b:
        print(a,'较大值')
    else:
        print(b,'较大值')

printMax(10,20)
printMax(30,5)

执行结果:

20 较大值
30 较大值

            上面的printMax函数中,在定义时写的printMax(a,b).a和b称为“形式参数”,简称“形参”。也就是说,形式参数是在定义函数使用的。形式参数的命名只要符合“标识符”命名规则即可。

             在调用函数时,传递的参数称为“实际参数”,简称“实参”。上面代码中,printMax(10,20).10,和20就是实际参数。

文档字符串(函数的注释)

程序的可读性最重要,一般建议在函数体开始的部分附上函数定义说明,这就是“文档字符串”,也有人成为“函数的注释”。我们通过三个单引号或三对双引号来实现,中间可以加入多行文字进行说明。

【操作】测试文档字符串的使用

def print_star():
      '''根据传入的n,打印多个星号'''
help(print_star)

测试结果:

print_star()
    根据传入的n,打印多个星号

返回值

return 返回值要点:

1.如果函数体包含return语句,则结束函数执行并返回值;

2.如果函数体中不包含reutrn语句,则返回None值。

3.要返回多个返回值,使用列表、元组、字典、集合将多个值“存起来”即可。

【操作】定义一个打印n个星号的无返回值的函数

def print_star(n):
    print("*"*n)
print_star(5)

运行结果:

*****

【操作】定义一个返回两个数平均值的函数

def my_avg(a,b):
    return (a+b)/2
#如下是函数的调用
c=my_avg(20,30)
print(c)

运行结果:

25.0
 

 

函数也是对象,内存底层分析

Python中,“一切都是对象”。实际上,执行def定义函数后,系统就创建了相应的函数对象。我们执行如下程序,然后进行解释:

def print_star(n):
    print("*"*n)

print(print_star)
print(id(print_star))
c=print_star
c(3)

测试结果:

<function print_star at 0x0000022EB8FAC1E0>
2399695192544
***
 

上面代码执行def时,系统中会创建函数对象,并通过print_star这个变量进行引用。

 

 

 

变量的作用域(全局变量和局部变量)

变量起作用的范围称为变量的作用域,不同作用域内同名变量之间互不影响。变量分为:全局变量、局部变量。

全局变量:

1.在函数和类定义之外声明的变量。作用域为定义的模块,从定义位置开始直到模块结束。

2.全局变量降低了函数的通用性和可读性。应尽量避免全局变量的使用。

3.全局变量一般做常量使用。

4.函数内要改变全局变量的值,使用global声明一下。

局部变量:

1.在函数体中(包含形式参数)声明的变量。

2.局部变量的引用比全局变量快,优先考虑使用。

3.如果局部变量和全局变量同名,则在函数内隐藏全局变量,只使用同名的局部变量。

【操作】全局变量的作用域测试

a=100                       #全局变量
def f1():
    global a   #如果要在函数内改变全局变量的值,增加global关键之声明
    print(a)     打印全局变量a的值,       
    a=300
f1()
print(a)

测试结果:

100
300
 

局部变量和全局变量效率测试

局部变量的查询和访问速度比全局变量快,优先考虑使用,尤其是在循环的时候。

在特别强调效率的地方或者循环次数较多的地方,可以通过将全局变量转为局部变量提高运行速度。

import math
import time
def test01():
    start=time.time()
    for i in range(10000000):
        math.sqrt(30)
    end=time.time()
    print("耗时{0}".format(end-start))
def test02():
    start=time.time()
    b=math.sqrt
    for i in range(10000000):
        b(30)
    end=time.time()
    print("耗时{0}".format(end-start))
test01()
test02()

测试结果:

耗时2.029999256134033
耗时1.4491524696350098

参数的传递

函数声明时的形式参数,使用时,等同于函数体内的局部变量。由于Python中一切皆对象。

因此,参数传递时直接传递对象的地址,但具体使用分两种类型:

1.传递不可变对象的引用(起到其他语言值传递的效果)

2.传递可变对象的引用(起到其他语言引用传递的效果)

函数的参数传递本质上就是:从实参到形参的赋值操作。Python中“一切皆对象”,

所有的赋值操作都是“引用的赋值”。所以,Python中参数的传递都是“引用传递”,不是“值传递”。

具体操作时分两类:

1.对“可变对象”进行“写操作”,直接作用于原对象本身。

2.对“不可变对象”进行“写操作”,会产生一个新的“对象空间”,并用新的值填充这块空间。(起到其他语言的“值传递”效果,但不是“值传递”)

可变对象有:

           字典、列表、集合、自定义的对象等

不可变对象有:

            数字、字符串、元组、function等

 

传递可变对象的引用

传递参数是可变对象(例如:列表、字典、自定义的其他可变对象等),实际传递的还是对象的引用。在函数体中不创建新的对象拷贝,而是可以直接修改所传递的对象。

【操作】参数传递:传递可变对象的引用

b=[10,20]
def f2(m):
    print("m: ",id(m))    #b和m是同一个对象
    m.append(30)   #由于m是可变对象,不创建对象拷贝,直接修改这个对象
f2(b)
print("b: ",id(b))
print(b)

测试结果:

m:  2121923846792
b:  2121923846792
[10, 20, 30]
 

传递不可变对象的引用

传递参数是不可变对象(例如:int、float、字符串、元组、布尔值),实际传递的还是对象的引用。在“赋值操作”时,由于不可变对象无法修改,系统会新创建一个对象。

【操作】参数传递:传递不可变对象的引用

a=100
def f3(n):
    print("n: ",id(n))
    n=n+200
    print("n: ",id(n))
    print(n)


f3(a)
print("a: ",id(a))

运行结果:

n:  140723893923568
n:  2362965088080
300
a:  140723893923568
 

显然通过id值我们可以看到n和a一开始是同一个对象。给n赋值后,n是新的对象。

浅拷贝和深拷贝

内置函数:

copy(浅拷贝):不拷贝子对象的内容,只拷贝子对象的引用。

deepcopy(深拷贝):会连子对象的内存也全部拷贝一份,对子对象的修改不会影响原对象

#测试浅拷贝和深拷贝
import copy
def Copy():
 a=[10,20,[5,6]]
 b=copy.copy(a)
 print("a: ", a)
 print("b: ", b)
 b.append(30)
 b[2].append(7)
 print("浅拷贝。。。。。")
 print("a: ",a)
 print("b: ",b)

def DeepCopy():
     a = [10, 20, [5, 6]]
     b = copy.deepcopy(a)
     print("a: ", a)
     print("b: ", b)
     b.append(30)
     b[2].append(7)
     print("深拷贝。。。。。")
     print("a: ",a)
     print("b: ",b)
Copy()
DeepCopy()

测试结果:

a:  [10, 20, [5, 6]]
b:  [10, 20, [5, 6]]
浅拷贝。。。。。
a:  [10, 20, [5, 6, 7]]
b:  [10, 20, [5, 6, 7], 30]


a:  [10, 20, [5, 6]]
b:  [10, 20, [5, 6]]
深拷贝。。。。。
a:  [10, 20, [5, 6]]
b:  [10, 20, [5, 6, 7], 30]

参数的类型---不可变对象含可变子对象

#传递不可变对象时,不可变对象里面包含的子对象是可变的。则方法内修改了这个可变对象,源对象也发生了变化。
a=[10,20,[5,6]]
print("a: ",id(a))
def test01(m):
    print("m: ",id(m))
    m[2][0]=88
    print(m)
    print("m: ",id(m))

test01(a)
print(a)

测试结果:

a:  1599817736904
m:  1599817736904
[10, 20, [88, 6]]
m:  1599817736904
[10, 20, [88, 6]]

 

 

参数的几种类型

位置参数

函数调用时,实参默认按位置顺序传递,需要个数和形参匹配。按位置传递的参数,称为:

“位置参数”。

【操作】测试位置参数

def f1(a,b,c):
    print(a,b,c)

f1(2,3,4)
f1(4,5)

测试结果:

2 3 4
Traceback (most recent call last):
  File "D:/Python/python基础教程/参数.py", line 5, in <module>
    f1(4,5)
TypeError: f1() missing 1 required positional argument: 'c'
 

默认值参数

我们可以为某些参数设置默认值,这样这些参数在传递时就是可选的。称为“默认值参数”。

默认值参数放在位置参数后面。

【操作】测试默认值参数

def f2(a,b,c=10,d=20):   #默认值参数必须位于普通位置参数后面
    print(a,b,c,d)
f2(8,9)
f2(8,9,19)
f2(8,9,19,24)

执行结果:

8 9 10 20
8 9 19 20
8 9 19 24
 

命名参数

我们也可以按照形参的名称传递参数,称为“命名参数”,也称“关键字参数”。

【操作】测试命名参数

#命名参数
def f3(a,b,c):
    print(a,b,c)
f3(7,2,4)         #位置参数
f3(a=7,b=2,c=4)   #命名参数

执行结果:

7 2 4
7 2 4
 

可变参数

可变参数指的是“可变数量的参数”。分两种情况:

1.*param(一个星号),将多个参数收集到一个“元组”对象中。

2.**param(两个星号),将多个参数收集到一个“字典”对象中。

【操作】测试可变参数处理(元组、字典两种方式)

#可变参数
def f1(a,b,*c):
    print(a,b,c)
f1(8,9,19,20)

def f2(a,b,**c):
    print(a,b,c)
f2(7,2,name='gaoqi',age=21)

def f3(a,b,*c,**d):
    print(a,b,c,d)
f3(7,2,24,3,name='gaoqi',age=21)

执行结果:

8 9 (19, 20)
7 2 {'name': 'gaoqi', 'age': 21}
7 2 (24, 3) {'name': 'gaoqi', 'age': 21}
 

强制命名参数

再带星号的“可变参数”后面增加新的参数,必须是“强制命名参数”。

【操作】强制命名参数的使用

#强制命名参数
def f4(*a,b,c):
    print(a,b,c)
#f4(2,3,4)    #会报错。由于a是可变参数,将2,3,4全部收集,造成b和c没有赋值。
f4(2,b=3,c=4)

执行结果:

(2,) 3 4

 

lambda表达式和匿名函数

lambda表达式可以用来声明匿名函数。lambda函数是一种简单的、在同一行中定义函数的方法。lambda函数实际生成了一个函数对象。

lambda表达式只允许包含一个表达式,不能包含复杂语句,该表达式的计算结果就是函数的返回值。

lambda表达式的基本语法如下:

                        lambda   arg1,arg2,arg3...  :<表达式>

arg1,arg2,arg3为函数的参数。<表达式>相当于函数体。运算结果是:表达式的运算结果。

 

【操作】lambda表达式使用

f=lambda a,b,c:a+b+c
print(f)
print(f(2,3,4))

g=[lambda a:a*2,lambda b:b*3,lambda c:c*4]
print(g[0](6),g[1](7),g[2](8))

测试结果:

<function <lambda> at 0x000002C387A9D1E0>
9
12 21 32
 

eval()函数

功能:将字符串str当成有效的表达式来求值并返回计算结果。

语法:eval(source[,globals[,locals]])->value

参数:

     source:一个Python表达式或函数compile()返回的代码对象

     globals:可选。必须是dictionary

     locals:可选。任意映射对象

#测试eval()函数
s="print('abcde')"
eval(s)

a=10
b=20
c=eval("a+b")
print(c)
dict1=dict(a=100,b=200)
d=eval("a+b",dict1)
print(d)

执行结果:

abcde
30
300

递归函数

递归函数指的是:自己调用自己的函数,在函数体内部直接或间接的自己调用自己。递归类似于大家

中学数学学过的“数学归纳法”。每个递归函数必须包含两个部分:

1.终止条件

          表示递归什么时候结束。一般用于返回值,不再调用自己。

2.递归步骤

          把第n步的值和第n-1步相关联。

递归函数由于会创建大量的函数对象、过量的消耗内存和运算能力。在处理大量数据时,谨慎使用。

【操作】用递归函数计算阶乘

#使用递归函数,计算阶乘


def factorial(n):
    if n==1:
        return 1
    else:
        return n*factorial(n-1)

result=factorial(5)
print(result)

测试结果:

120

嵌套函数(内部函数)

嵌套函数:

        在函数内部定义的函数!

【操作】嵌套函数定义

#嵌套函数
def f1():
    print('f1  running...')
    def f2():
        print('f2 running...')
    f2()
f1()

测试结果:

f1  running...
f2 running...

上面程序中,f2()就是定义在f1函数内部的函数。f2()的定义和调用都在f1()函数内部。

使用嵌套函数的情况:

1.封装  -----数据隐藏

               外部无法访问“嵌套函数”

2.贯彻DRY(Don't Repeat Yourself)原则

               嵌套函数,可以让我们在函数内部避免重复代码。

3.闭包

 

nonlocal关键字

nonlocal         用来声明外层的局部变量

global             用来声明全局变量

【操作】使用nonlocal声明外层局部变量

#测试nonlocal,global
a=100
def outer():
    b=10
    def inner():
        nonlocal b  #声明外部函数的局部变量
        print("inner b: ",b)
        b=20
        global a
        a=1000
        inner()
        print("outer b: ",b)

outer()
print("a: ",a)

测试结果:

inner b:10

outer b:20

a:    1000

LEGB规则

Python在查找“名称”时,是按照LEGB规则查找的:

Local-->Enclosed-->Global-->Built in.

Local     指的就是函数或者类的方法内部

Enclosed     指的是嵌套函数(一个函数包裹另一个函数,闭包)

Global    指的是模块中的全局变量

Built in    指的是Python为自己保留的特殊名称。

 

如果某个name映射在局部(local)命名空间中没有找到,接下来就会在闭包作用域(enclosed)进行搜索,如果闭包作用域也没有找到,Python就会在全局(global)命名空间进行查找,最后会在内建(built in)命名空间搜索(如果一个名称在所有命名空间中都没有找到,就会产生一个NameError).

#LEGD测试
str="global str"
def outer():
    str="outer"
    def inner():
        str='inner'
        print(str)
    inner()
outer()

执行结果:

inner

 

第六章             面向对象编程

面向对象和面向过程区别

面向过程(Procedure Oriented)思维

      面向过程编程更加关注的是“程序的逻辑流程”,是一种“执行者”思维,适合编写小规模的程序。

面向对象(Object Oriented)思维

     面向对象更加关注的是“软件中对象之间的关系”,是一种“设计者”思维,适合编写大规模的程序。

面向对象思考方式

      遇到复杂问题,先从问题中找名词(面向过程更多的是找动词),然后确立这些名词那些可以作为类,再根据问题需求确定类的属性和方法,确定类之间的关系。

面向对象和面向过程总结

·都是解决问题的思维方式,都是代码组织的方式。

·解决简单问题可以使用面向过程。

·解决复杂问题:宏观上使用面向对象把握,微观处理上仍然是面向过程。

 

对象的进化

1.简单数据

像30,40,50,4等这些数字,可以看做是简单数据。最初的计算机编程,都是像这样的数字。

2.数组

 将同类型的数据放到一起。比如:整数数组[20,30,40],浮点数数组[10.2,11.3,12.4],字符串数组:["aa","bb","ccc"]

3.结构体

将不同类型的数据放到一起,是C语言中的数据结构。比如:

         struct resume{

                int age;

                char name[10];

                double salary;

};

4.对象

将不同类型的数据、方法(即函数)放到一起,就是对象。比如:

class Student:
    company="SXT"
    count=0
    def __init__(self,name,score):
        self.name=name
        self.score=score
        Student.count=Student.count+1
    def say_score(self):
        print("我的公司是:",Student.company)
        print(self.name,'的分数是:',self.score)

我们前面学习的数字也是对象。比如:整数9,就是一个包含了加法、乘法等方法的对象。

 

类的定义

从一个类创建对象时,每个对象会共享这个类的行为(类中定义的方法),但会有自己的属性值(不共享状态)。更具体一点:“方法代码是共享的,属性数据不共享”。

Python中,“一切皆对象”。类也称为“类对象”,类的实例也称为“实例对象”。

定义类的语法格式如下:

class    类名:

       类体

【操作】一个典型的类的定义

class Student:
    def __init__(self,name,score):
        self.name=name
        self.score=score

    def say_score(self):
        print(self.name,'的分数是:',self.score)
s1=Student('张三',80)
s1.say_score()

执行结果:

张三 的分数是: 80

 

构造函数_init_()

类是抽象的,也称为“对象的模板”。我们需要通过类这个模板,创建类的实例对象,然后才能使用类定义的功能。

Python对象包含三个部分:id(identity识别码)、type(对象类型)、value(对象的值)。

1.id(identity识别码)

2.type(对象类型)

3.value(对象的值)

    (1)属性(attribute)

      (2)   方法(method)

创建对象,我们需要定义构造函数_init_()方法。构造方法用于执行“实例对象的初始化工作”,即对象创建后,初始化当前对象的相关属性,无返回值。

_init_()的要点如下:

1.名称固定,必须为:_init_()

2.第一个参数固定,必须为:self。self指的是刚刚创建好的实例对象。

3.构造函数通常用来初始化实例对象的实例属性,如下代码就是初始化实例属性:name和score。

4.通过“类名(参数列表)”来调用构造函数。调用后,将创建好的对象返回给相应的变量。

比如:s1=Student('张三',80)

5._init_()方法:初始化创建好的对象,初始化指的是:“给实例属性赋值”

6._new_()方法:用于创建对象,但我们一般无需重定义该方法。

 

其他操作:

1.dir(obj) 可以获得对象的所有属性、方法

2.obj._dict_   对象的属性字典

3.pass 空语句

4.isinstance(对象、类型)   判断“对象”是不是“指定类型”

 

类对象

我们在前面讲的类定义格式中,“class   类名:”。实际上,当解释器执行class语句时,就会创建一个类对象。

【操作】测试类对象的生成

class Student:
    pass           #空语句
print(type(Student))
print(id(Student))
Stu2=Student
s1=Stu2()
print(s1)

执行结果:

<class 'type'>
1505054938584
<__main__.Student object at 0x0000015E6E1434E0>

我们可以看到实际上生成了一个变量名就是类名“Student”的对象。我们通过赋值给新变量Stu2,也能实现相关的调用。说明,确实创建了“类对象”。

【注】pass为空语句。就是表示什么都不做,只是作为一个占位符存在。当你写代码时,遇到暂时不知道往方法或者类中加入什么时,可以先用pass站位,后期再补上。

 

类属性和类方法

类属性

类属性是从属于“类对象”的属性,也称为“类变量”。由于,类属性从属于类对象,可以被所有实例对象共享。

类属性的定义方式:

                                 class    类名:

                                              类变量名  =  初始值

在类中或者类的外面,我们可以通过:“类名.类变量名”来读写。

【操作】类属性的使用测试

class Student:
    company="SXT"   #类属性
    count=0         #类属性
    def __init__(self,name,score):
        self.name=name           #实例属性
        self.score=score
        Student.count=Student.count+1
    def say_score(self):         #实例方法
        print("我的公司是:",Student.company)
        print(self.name,'的分数是:',self.score)
s1=Student('张三',80) #s1是实例对象,自动调用_init_()方法
s1.say_score()
print('一共创建{0}个Student对象'.format(Student.count))

执行结果:

我的公司是: SXT
张三 的分数是: 80
一共创建1个Student对象

类方法

#测试类方法
class Student:
    company="SXT"        #类属性
    @classmethod
    def printCompany(cls):
        print(cls.company)
Student.printCompany()

执行结果:

SXT

 

静态方法

 

【操作】类方法测试

class Student2:
    company2="SXT"
    @staticmethod
    def add(a,b):
        print("{0}+{1}={2}".format(a,b,(a+b)))
        return a+b
Student2.add(20,30)

执行结果:

20+30=50
 

 

_del_方法(析构函数)和垃圾回收机制

_del_方法称为:“析构方法”,用于实现对象被销毁时所需的操作。比如:释放对象占用的资源,例如:打开的文件资源、网络连接等。

Python实现自动的垃圾回收,当对象没有被引用时(引用计数为0),由垃圾回收器调用_del_方法。

我们可以通过del语句删除对象,从而保证调用_del_方法。

系统会自动提供_del_方法,一般不需要自定义析构方法。

#析构函数
class Person:
    def __del__(self):
        print("销毁对象:{0}".format(self))
p1=Person()
p2=Person()
del p2
print("程序结束")

执行结果:

销毁对象:<__main__.Person object at 0x00000240E787AC18>
程序结束
销毁对象:<__main__.Person object at 0x00000240E7873588>

_call_方法和可调用对象

定义了_call_方法的对象,称为“可调用对象”,即该对象可以像函数一样被调用。

#测试可调用方法_call_()
class SalaryAccount:
    def __call__(self, salary):
        print("算工资啦。。。")
        yearSalary=salary*12
        daySalary=salary//22.5
        hourSalary=daySalary//8
        return dict(yearSalary=yearSalary,monthSalary=salary,daySalary=daySalary,hourSalary=hourSalary)
s=SalaryAccount()
print(s(8000))

执行结果:

算工资啦。。。
{'yearSalary': 96000, 'monthSalary': 8000, 'daySalary': 355.0, 'hourSalary': 44.0}

方法没有重载

在其他语言中,可以定义多个重名的方法,只要保证方法签名唯一即可。方法签名包含3个部分:方法名、参数数量、参数类型。

Python中,方法的参数没有生命类型(调用时确定参数的类型),参数的数量也可以由可变参数控制,因此,Python中是没有办法的重载的。定义一个方法即可有多种调用方法,相当于实现了其他语言中的方法的重载。

如果我们在类体中定义了多个重名的方法,只有最后一个方法有效。

建议:不要使用重名的方法!Python中方法没有重载。

#Python中没有方法的重载,定义多个同名方法,只有最后一个有效
class Person:
    def say_hi(self):
        print("hello")
    def say_hi(self,name):
        print("{0},hello".format(name))
p1=Person()
p1.say_hi("gaoqi")

执行结果:

gaoqi,hello

方法的动态性

Python是动态语言,

#测试方法的动态性
class Person:
    def work(self):
        print("好好学习")

def play_game(s):
    print("{0}在玩游戏!".format(s))
Person.play=play_game
p=Person()
p.work()
p.play()

执行结果:

好好学习
<__main__.Person object at 0x000001FFEDCDA128>在玩游戏!
 

#测试私有属性
class Employee:
    def __init__(self,name,age):
        self.name=name
        self.age=age          #私有属性
    
e=Employee("高琪",18)
print(e.name)
print(e.age)          

print(dir(e))

执行结果:

高琪
18
['__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__', 'age', 'name']
 

@property装饰器

@property可以将一个方法的调用方式变成“属性调用”。

#测试@property的最简化的使用
'''
class Employee:
    @property
    def salary(self):
        print("salary run...")
        return 10000


empl=Employee()
print(empl.salary)
'''
#@property装饰器的用法
class Employee:
    def __init__(self,name,salary):
        self.__name = name    # self.name=name
        self.__salary = salary  #self.salary=salary
    @property
    def salary(self):
        return self.__salary
    @salary.setter
    def salary(self,salary):
        if 1000<salary<50000:
            self.__salary=salary
        else:
            print("录入错误!薪水在1000--50000这个范围")


        '''   
    def get_salary(self):
        return self.__salary
    def set_salary(self,salary):
        if 1000<salary<50000:
            self.__salary=salary
        else:
            print("录入错误!薪水在1000--50000这个范围")
'''
empl=Employee("高琪",30000)
'''
print(empl.get_salary())    #print(empl.salary)
empl.set_salary(-20000)                            #empl.salary=20000
print(empl.get_salary())      #print(empl.salary)
'''
print(empl.salary)
empl.salary=2000
print(empl.salary)

面向对象三大特征介绍

Python是面向对象的语言.也支持面向对象编程的三大特性:继承、封装(隐藏)、多态。
●封装(隐藏)
隐藏对象的属性和实现细节,只对外提供必要的方法。相当于将“细节封装起来”,只
对外暴露“相关调用方法”。
通过前面学习的“私有属性、私有方法” 的方式,实现“封装”。Python追求简洁的
语法,没有严格的语法级别的“访问控制符”,更多的是依靠程序员自觉实现。
●继承
继承可以让子类具有父类的特性,提高了代码的重用性。
从设计上是一种增量进化,原有父类设计不变的情况下,可以增加新的功能,或者改进
已有的算法。
●多态
多态是指同一个方法调用由于对象不同会产生不同的行为。生活中这样的例子比比皆
是:同样是休息方法,人不同休息方法不同。张三休息是睡觉,李四休息是玩游戏,程序员
休息是“敲几行代码”。
 

继承

      继承是面向对象程序设计的重要特征,也是实现“代码复用”的重要手段。
      如果一个新类继承自一个设计好的类, 就直接具备了已有类的特征,就大大降低了工作
难度。已有的类,我们称为“父类或者基类”,新的类,我们称为“子类或者派生类”。
 

语法格式

Python支持多重继承,一个子类可以继承多个父类。继承的语法格式如下:

          class     子类类名(父类1[,父类2,...]):

                      类体

如果在类定义中没有指定父类,则默认父类是object类。也就是说,object是所有类的父类,里面定义了一些所有的默认实现,比如:_new_()。

定义子类时,必须在其构造函数中调用父类的构造函数。调用格式如下:

父类名._init_(self,参数列表)

#继承
class Person:
    def __init__(self,name,age):
        self.name=name
        self._age=age          #私有属性

    def say_age(self):
        print(self.name,"的年龄是:",self._age)


class Student(Person):
    def __init__(self,name,age,score):
        self.score=score
        Person.__init__(self,name,age)
        #构造函数中包含调用父类构造函数。根据需要,不是必须。子类并不会自动调用父类的_init_(),我们必须

print(Student.mro())
s=Student("高琪",18,100)
s.say_age()
print(s.name)
print(s.score)

测试结果:

[<class '__main__.Student'>, <class '__main__.Person'>, <class 'object'>]
高琪 的年龄是: 18
高琪
100

 

类成员的继承和重写

1.成员继承:子类继承了父类除构造方法之外的所有成员。

2.方法重写:子类可以重新定义父类中的方法,这样就会覆盖父类的方法,也称重写

【操作】继承和重写的案例

#继承和重写
class Person:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def say_age(self):
        print(self.name,"的年龄是:",self.age)

    def say_name(self):
        print("我是:",self.name)


class Student(Person):
    def __init__(self, name, age, score):
        self.score = score
        Person.__init__(self, name, age)

    def say_name(self):
        print("I know")
s=Student("高琪",18,80)
s.say_age()
s.say_name()

执行结果:

高琪 的年龄是: 18
I know

查看类的继承层次结构

通过类的方法mro()或者类的属性_mor_可以输出这个类的继承层次结构。

【操作】查看类的继承层次结构

class A:pass
class B(A):pass
class C(B):pass
print(C.mro())

执行结果:

[<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
 

object根类

object类是所有类的父类,因此所有的类都有object类的属性和方法。

dir()查看对象属性

为了深入学习对象,我们先学习内置函数dir(),它可以让我们方便的看到指定对象所有的属性。

【操作】查看对象所有属性以及和object进行比对

class Person:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def say_age(self):
        print(self.name,"的年龄是:",self.age)
obj=object()
print(dir(obj))
s2=Person("高琪",21)
print(dir(s2))

执行结果:

['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
['__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__', 'age', 'name', 'say_age']
 

从上面我们可以发现这样几个要点:

1.Person对象增加了六个属性:

              _dict_       _module_     _weakref_  age     name     say_age

2.object的所有属性,Person类作为object的子类,显然包含了所有的属性。

3.我们打印age、name、say_age,发现say_age虽然是方法,实际上也是属性,只不过这个属性的类型是“method”而已。

  age<class 'int'>

  name<class 'str'>

  say_age<class 'method'>

 

重写_str_()方法

object有一个_str_()方法,用于返回一个对于“对象的描述”,对应于内置函数str()经常用于print()方法,帮助我们查看对象的信息,_str_()可以重写。

class Person:
    def __init__(self,name):
        self.name=name

    def __str__(self):
         return "名字是:{0}".format(self.name)
p=Person("高琪")
print(p)

执行结果:

名字是:高琪
 

多重继承 

Python支持多重继承,一个子类可以有多个“直接父类”。这样,就具备了“多个父类”的特点。但是由于,这样会被“类的整体层次”搞得异常复杂,尽量避免使用。

 
class A:
    def aa(self):
        print("aa")
class B:
    def bb(self):
        print("bb")
class C(B,A):
    def cc(self):
        print("cc")
c=C()
c.aa()
c.bb()
c.cc()

执行结果:

aa
bb
cc
 

MRO()

Python支持多继承,如果父类中有相同名字的方法,在子类没有指定父类名时,解释器将
“从左向右”按顺序搜索。
MRO ( Method Resolution Order )方法解析顺序,我们可以通过mro()方法获得
“类的层次结构”,方法解析顺序也是按照这个“类的层次结构”寻找的。

class A:
    def aa(self):
        print("aa")
    def say(self):
        print("say AAA")
class B:
    def bb(self):
        print("bb")
    def say(self):
        print("say baibai!")
class C(B,A):
    def cc(self):
        print("cc")
c=C()

c.bb()
c.cc()
c.aa()
print(C.mro())    # 打印类的层次结构
c.say()     #解释器寻找方法是“从左到右”的方式寻找,此时会执行B中的say()

执行结果:

bb
cc
aa
[<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
say baibai!
 

super()获得父类定义

在子类中,如果获得父类的方法时,我们可以通过super()来做。

super()代表父类的定义,不是父类对象。

class A:
     def say(self):
        print("say AAA:",self)
class B(A):

    def say(self):
        super().say()
        print("say baibai!",self)

B().say()

执行结果:

say AAA: <__main__.B object at 0x0000020ED248A240>
say baibai! <__main__.B object at 0x0000020ED248A240>
 

多态

1.多态是方法的多态,属性没有多态。

2.多态的存在有2个必要条件:继承、方法重写。

#多态
class Man:
    def eat(self):
        print("吃饭")

class Chinese(Man):
    def eat(self):
        print("中国人用筷子吃饭")
class English(Man):
    def eat(self):
        print("英国人吃饭用叉子")
def manEat(m):         
    if isinstance(m,Man):
        m.eat()    #多态,一个方法调用,根据对象不同调用不同的方法!
    else:
        print("不能吃饭")

manEat(Chinese())
manEat(English())
执行结果:

中国人用筷子吃饭
英国人吃饭用叉子
 

 

特殊方法和运算重载

Python的运算符实际上是通过对象的特殊方法实现的。

比如:

a=20
b=30
c=a+b
d=a.__add__(b)
print(c)
print(d)

执行结果:

50

50

我们可以重写上面的特殊方法,即实现了“运算符的重载”。

class Person:
    def __init__(self,name):
        self.name=name

    def __add__(self, other):
        
       if isinstance(other,Person):
           return "{0}---{1}".format(self.name,other.name)
       else:
           return "不是同类对象,不能相加"
p=Person("高琪")
q=Person("高希希")
x=p+q
print(x)

执行结果:

高琪---高希希

 

特殊属性

对象的浅拷贝和深拷贝

变量的赋值操作

    只是形成了两个变量,实际还是指向同一个对象。

浅拷贝

    Python拷贝一般都是浅拷贝。拷贝时,对象包含的子对象内容不拷贝,因此,源对象和拷贝对象会引用同一个子对象。

深拷贝

    使用copy模块的deepcopy函数,递归拷贝对象中包含的子对象,源对象和拷贝对象所有的子对象也不同。

#测试对象的浅拷贝、深拷贝
import copy
class MobilePhone:
    def __init__(self,cpu,screen):
        self.cpu=cpu
        self.screen=screen
class CPU:
    def calculate(self):
        print("算你个12345")
        print("cpu对象:",self)
class Screen:
    def show(self):
        print("显示一个好看的画面,亮瞎你的钛合金大眼")
        print("screen对象:",self)



#测试变量赋值
c1=CPU()
c2=c1
print(c1)
print(c2)
print("测试浅复制。。。")
#测试浅复制
s1=Screen()
m1=MobilePhone(c1,s1)
m2=copy.copy(m1)
print(m1,m1.cpu,m1.screen)

print(m2,m2.cpu,m2.screen)

print("测试深复制。。。")
#测试深复制
m3=copy.deepcopy(m1)
print(m1,m1.cpu,m1.screen)
print(m3,m3.cpu,m3.screen)

执行结果:

<__main__.CPU object at 0x0000019A7984B278>
<__main__.CPU object at 0x0000019A7984B278>
测试浅复制。。。
<__main__.MobilePhone object at 0x0000019A798F9A90> <__main__.CPU object at 0x0000019A7984B278> <__main__.Screen object at 0x0000019A798F9B70>
<__main__.MobilePhone object at 0x0000019A798F9EB8> <__main__.CPU object at 0x0000019A7984B278> <__main__.Screen object at 0x0000019A798F9B70>
测试深复制。。。
<__main__.MobilePhone object at 0x0000019A798F9A90> <__main__.CPU object at 0x0000019A7984B278> <__main__.Screen object at 0x0000019A798F9B70>
<__main__.MobilePhone object at 0x0000019A79913DA0> <__main__.CPU object at 0x0000019A79913E48> <__main__.Screen object at 0x0000019A79913EB8>

组合

    “is-a”关系,我们可以使用“继承”。从而实现了子类拥有的父类的方法和属性。“is-a”关系指的是类似这样的关系:狗是动物,dog   is   animal。狗类就应该继承动物类。

     “has-a”关系指的是这样的关系:手机拥有CPU。MobilePhone  has  a  CPU.

 

#测试组合
#使用组合实现代码的复用
class A1:
    def say_a1(self):
        print("a1 a1 a1")

class B1:
       def __init__(self,a):
           self.a=a


a1=A1()
b1=B1(a1)
b1.a.say_a1()

#使用继承实现代码的复用
class A2:
    def say_a2(self):
        print("a2 a2 a2")

class B2(A2):
      pass


b2=B2()
b2.say_a2()

执行结果:

a1 a1 a1
a2 a2 a2
 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值