我们来看看其它网站的学习流程
可以跟着流程走一遍,但是还不够高效。
简单看一下上面的目录,入门一种编程语言,究竟学的是什么。我记得我第一种编程语言,是visual basic 6.0
而第一次系统学一门语言。是C语言,在我看来,c语言比我学过的java python php js ts shell sql,不知道要高到哪里去了。我们不用关注那么多细节,入门最快的一种方法是写一个排序算法。因为声明变量,声明和使用列表,循环,判断,全都用到了。所以我也会从这里入手,教大家快速上手python,看完这篇文章后,再回过头来,去看我上面发的链接。
那么开始吧。打开我们的pycharm软件,新建一个项目innerSort
点击右下角的create,出现下列界面
右键项目名innerSort,创建新文件
回车,如下:
将下面代码拷贝到代码编辑区
A=[3,2,1]
j=0
temp=0
n=len(A)
for i in range(1,n):
j=i
temp=A[i]
while(j>0 and A[j-1]>temp):
A[j]=A[j-1]
j=j-1
A[j]=temp
print(A)
效果如下:
按ctrl+shift+F10执行,结果如下:
执行完毕正常了,再删掉,自己手动敲一遍,里面的空格是按TAB键产生的
现在回到这段代码,
A=[3,2,1]
创建了一个数组对象,里面有三个元素3,2,1,A=[3,2,1],好比[3,2,1]是一个一辆车,车名称就叫A。
一个数组我们关注的是
1.如何创建数组
2.如何增加一个元素
3.如何访问数组的元素
4.如何获取数组长度
5.如何修改数组的值
我们在innerSort项目内创建一个新文件test,用来测试用法
1.如何创建一个数组
创建了一个最简单的空数组
2.如何增加一个元素
就像一列火车,A.append表示A这列火车带有挂车厢的方法,我直接往后面挂一个车厢,车厢里的值是3
3.如何访问数组的元素
我们使用print()方法打印出来了A数组,如果打印第一个元素,则使用下面的方法
就把3打印成功了,而为什么0是第一个呢,按我C语言的理解,一个数组会分配是连续的内存地址,所以我们只要知道第一个元素的内存地址,加上每个元素的内存大小,就能随机访问所有的元素,比如三个元素的数组,每个元素的大小是10,数组的内存地址是100,那么第一个元素也就是数组内存地址,也就是100+0*10=100,同理第二个是100+1*10=110,第三个元素是100+2*10=120,这样。所以约定成俗,第一个元素我们就用下标0去访问。
看到第三行代码,为什么append()方法用起来需要A.append,而print()方法可以直接使用?
因为数组是一个对象,对象含有自己的方法,要使用该方法,就是得用点号操作符去操作该变量名A。
而print(),我初步判断它是一个全局方法,也就是全局对象X,然后X.print(),由于是全局对象,所以省略了这个.号
但是这样解释又太不准确,因为我也可以直接解释print()是一个全局方法,不用声明就可以使用。
最后我能给出的一个解释就是,print()是python内置函数。
4.如何获取数组长度
len()方法
5.如何修改数组的值
接下来看
j=0
temp=0
就是声明了两个变量 j temp,并赋值0,print(j,temp)可以打印这两个变量,如下
n=len(A)
len()也相当于内置方法,输出数组长度,也就是存储的元素个数
接下来看
for i in range(1,n):
首先看这个range()方法,传入了两个参数,关于range()本质,我认识有限所以无法阐释清除,range的效果有点类似迭代器。
使用list(range(1,3))可以生成一个数组[1,2],如下
使用list(range(3))可以生成一个[0,1,2]数组,各位可以尝试一下,相当于左开右闭区间
可以使用for 循环遍历这个数组,如下
接下来往下看
j=i
temp=A[i]
就是把i复制给j,把A数组的第i+1个元素(因为A[0]是第一个,A[1]是第二个)赋值给temp变量。
这里的=与我们数学上的=不同。
数学上是设y=2x+1 y=3x,列一个方程,求出x,y的值,这里的“=”相当于等价的意思,对应python的符号是“==”,两个等号
而python中的“=”表示赋值操作,也就是,x=1,是把右边的值,赋给左右,相当于复制右边的值,粘贴给左边的变量。
要更深一点,就是1是一个对象/数值,内存地址用变量x存储。使用内存地址就能操作该对象/值。
接下来看
while(j>0 and A[j-1]>temp):
while表示循环过程,我们来做一个测试,打印输出1到100,怎么做(代码最好自己手打一遍,因为里面很多细节,可能对着敲都不能正确执行并得到结果,这里不提供源码供大家复制)
while (循环条件):
循环体
当循环条件的结果是"真",则执行一遍循环体,对应上图的代码,就是执行到第四行的时候,就表示一遍执行完毕,然后继续检查循环条件结果是否为"真"
如果为真,继续执行循环体,然后继续检查循环条件……
如果为假,则结束循环
这里i=1,满足循环条件,则打印1,然后i=2,检查也满足循环条件……
直到i=100,执行第三行的打印操作,然后执行第四行i自加1,现在i=101,然后回到开头,检查循环条件,不满足i<=100的条件,于是退出循环,所以只打印出来1到100。
如果要同时满足两个条件,需要怎么做,如下图:
这里的if后面跟着的括号,也是循环条件,同while括号的内容
使用循环需要注意的一点,一定要有退出循环的可能
比如下面的代码,结束不了,就会有问题
while True虽然不会自己退出,我们需要使用break主动退出
接下来往下看
A[j]=A[j-1]
j=j-1
把数组后面的值赋给前面的
然后j的值减一
这里有一点是python比较特别的地方,就是它没有括号,你是如何判断那些代码是循环体呢,就是靠缩进
红色部分是对齐的(按TAB键对齐),就是for循环体中的内容,蓝色方框内的内容,就属于while循环体
最后一部分,打印数组A,整个代码语法部分就讲解完毕了。
接下来我们想知道,为什么这串代码,可以实现排序的功能,这个代码的逻辑,叫插入排序算法。你可以理解成算法就是协议,写出来的代码,就是其实现。
其实讲现实中排队的例子,可能交换排序更直观一点,但是写都写到这里了,就讲插入排序把
有3个数3,2,1 如何按从小到大的顺序排列,插入排序的思想如下:
考虑第1个数3,找到3在前面序列的实际位置,将3放到该位置上
由于3前面没有数字,所以不做任何操作
考虑第二个数字2.找到2在前面的序列的实际位置,将2放在该位置上
2前面的序列就是有3,2比3小,所以3要往它前面挪动(前面指下标增加的方向)所以A数组变成这样
331,然后把2放在最后的位置,于是数组变成231
然后考虑第三个数1,1比3小,于是3要往前面挪,变成
233,然后1和2比,1也比2小,变成
223,最后把1放到最后的位置
变成123。
感觉说不清楚,至少得配个动图大家能更好的理解
那么总结一下把,插入排序的核心就是,有一个大小为i-1的有序序列n1,n2,n3,……ni-1
(翻译成白话就是,现在有一个大小为2的有序序列2,3,需要把第三个数1,插入到这个有序序列中,结果就是这三个数有序)
这时需要把ni插入到这个有序序列中,于是ni需要和ni-1相比,如果ni比它前面的数小,就把ni-1往前挪一位
(翻译一下就是,1比3小,于是3要往前移动一位,变成233,中间的3不变,当然你可以先临时把1放在这个位置,因为这个位置不是1的最终位置,还是要看左边的数是不是也大于1,为了节省操作,就没有让数组的状态变成213)
然后ni继续和ni-2相比,如果仍旧比ni-2小,则ni-2的值挪动到ni-1的位置上,ni再与ni-3相比,最后一直到n1,经过这一系列操作后,就得到了i个有序序列。
但是整个数组的元素可能大于i个,这是问题就变成了如何将ni+1,插入到这i个有序序列中
所以同理,有序序列n1,n2,n3,……ni-1也是这么来的。
如果还是不清楚插入排序的原理,我给大家一个教科书的解释吧
我觉得比我的废话讲的清晰明了,所以python编程语言什么时候出一个教科书?
这里我给出一个图解
一个有序数列 7 9 12如下
这时新增一个数8,如何才能让四个数有序
8先和12比
发现比12小,于是12要自己把空位挪出来
但是在计算机中,做出这个操作需要有两步
1.A[3]=A[2]
2.A[2]=null
为了让程序执行的操作最少,我们省略第二步操作
12比完了,继续移动8,和9比
发现8还是比9小,于是做同样的操作,把9往右移动
继续和7比,发现比7大,于是不移动了,8直接插入进来
就完成了4个数的插入排序
1.这个操作的前提是,待插入的序列必须有序,所以你要得到7 9 12,你得先从1个数开始排,假如刚开始只有一个9,9本身就是有序的,新增一个7,就是9和7之间的插入排序,得到7、9。然后在把12插入到这个有序数列中。
2.假设总共需要排列的数是n,当前子序列长度是m,需要交换的最大趟数是m,所以每次新增一个数,总共最大的交换次数就是n(n-1)/2,也就是n的平方的复杂度。为了减小时间复杂度,我们不用每次都要一个一个比,然后交换,直接用折半查找,找到8最终的位置,然后直接插入,这个方法叫折半插入排序。
扩展一下关键词,有兴趣的可以自行搜索
线性表:(顺序表,链表)
时间复杂度,空间复杂度
排序的稳定性
插入排序,交换排序,选择排序
内部排序,外部排序
以及最核心,数据结构是什么,算法又是什么。
现在我们得到了一个插入排序算法代码,但是就像我们使用的文具一样,我们想有一个文具盒,带着这个文具盒,把这堆代码装进去,于是我们得到了函数。
先全选
然后按TAB键,得到下面的样子(按shift+tab可以缩进回去)
在上面加上一行代码,如下
这就完成一个打包(区别于前端的打包)过程,把这个排序的功能,定义成了一个函数insertSort,
然后我们只需要调用这个函数,就能实现排序功能,按crtl+shift+F10运行,结果如下:
这个函数只能对321进行排序,我们也想对54321排序,可以这么做
源码:
def insertSort(A):
j = 0
temp = 0
n = len(A)
for i in range(1, n):
j = i
temp = A[i]
while (j > 0 and A[j - 1] > temp):
A[j] = A[j - 1]
j = j - 1
A[j] = temp
print(A)
A=[5,4,3,2,1]
insertSort(A)
可以看到我们在insertSort()括号里面增加了一个A,代表局部变量
然后在函数体外声明了一个全局变量A,这个A和函数体内的A不是同一个
然后传入到函数insertSort()中。
这里我们做两个测试:
出现这个结果,是跟变量作用域有关
一个变量名,会先从函数体内查找,查找不到,再会去函数体外查找该变量。
所以上图的结果是5,4,3,2,1
如下图
在函数内查找到了,则不会去函数体外查找
有办法找函数体外的吗,有办法,强制声明A为全局变量,如下
用global修饰的变量是全局变量,在函数体内修改该变量的值,打印函数体外的A,发现值改变了。
各位可以去掉第二行的gloal A,然后执行看看结果
说明函数变量声明中的A就是一个符号而已,不会检测A的变量类型。
讲完了函数,我们在讲讲类。
class Cat():
def __init__(self):
self.name="小猫"
def sound(self):
print(self.name+"喵喵叫")
oneCat=Cat()
oneCat.sound()
class Cat定义了一个类,一个类有属性name,还有方法sound。
方法想要访问属性,需要使用self.name。
oneCat=Cat(),表示用类实例化了一个对象,对象名称叫oneCat。然后该对象就可以使用类Cat创建的属性和方法。
上面代码运行结果如下
那如果我们想要在方法中使用方法呢,如下:
同样要加上self关键词
回到代码中来,我可以做如下修改
我修改了对象的name属性,这时候就出错了,小狗不是汪汪叫么,所以为了防止有人修改某些属性导入意料之外的错误,我们限定name为私有变量,如下
加上两个横线后,类外部就无法修改了,甚至无法访问
那组合在一起,发生了什么?
看到这里,是不是很疑惑,不是不能访问以两个横线标志的私有变量__name么。怎么成功访问了?
请往下看
原来这个在类里面定义的私有变量__name,python解释器会自动补全成为_Cat__name。所以我直接访问__name,是不存在这个属性的。
而oneCat.__name="小狗",这一行代码则帮我们新建了一个属性,赋值为小狗,所以
输出结果变成了小狗。
既然可以新增属性,那么可以新增方法吗,我们来试一下
跟新增属性用法一样。
那么从猫到汤姆猫,该如何实现呢(当然TomCat也是一个Web应用服务器)
只要使用一行代码,在创建TomCat类的时候,将Cat当作参数传递进去。
这里Cat我们叫做父类,TomCat叫做子类,有父类产生子类的过程我们叫继承。
当然我们的汤姆猫,我们可以重写名称和方法
小猫和汤姆猫,都喜欢吃饭的时间发出声音,如下
这种行为叫多态。更多的解释请参考:继承和多态 - 廖雪峰的官方网站
我们是在test文件下编写的代码,现在我们需要创建一个Cat.py文件,并把我们的有关Cat类的代码复制到里面
class Cat():
def __init__(self,name):
self.__name=name
def sound(self):
print(self.__name+"喵喵叫")
oneCat=Cat("大猫")
oneCat.sound()
那么假如我需要在test文件中使用这个大猫的类,该如何去做
我们先删除掉下面两行代码
然后回到test界面
我们可以使用import语句导入Cat模块,这个模块就是Cat.py文件。
当然我们也可以使用下面的写法
采用from import写法可以不用写Cat模块名,上一张图出现了Cat.Cat,第一个Cat是模块名称,第二个Cat是我声明的一个类。
所以尽量取不同的名称。各位可以在Cat.py文件中再增加一些函数,变量,类等,然后看看怎么在test中调用声明的这些变量、方法。这里我就不演示了。
还是演示一下吧
Cat.py
class Cat():
def __init__(self,name):
self.__name=name
def sound(self):
print(self.__name+"喵喵叫")
#函数
def eatWhat():
print("吃小鱼干")
#变量
weight="5kg"
test.py
from Cat import *
oneCat=Cat("大猫")
oneCat.sound()
eatWhat()
print(weight)
结果如下
各位可以尝试改回import Cat,看看test.py应该如何修改
最后回忆一下我们是如何创建test.py和inserSort.py的,我们创建了一个项目innerSort,在该项目下创建了两个文件。这里我要提到一个“包”的概念。
比如我写的猫的中华田园猫,国外也有一个程序员,写的是品种猫,如下
class Cat():
def __init__(self,name):
self.__name=name
def sound(self):
print(self.__name+" mews")
现在我的test里面想要引入这两个类,应该这么写
import Cat
import EuramericanCat
cat1=Cat.Cat("小猫")
cat2=EuramericanCat.Cat("little cat")
cat1.sound()
cat2.sound()
结果如下
那么假如国外的程序员,模块名称也叫Cat,也就是文件名称也叫Cat.py,这时候test.py里的代码应该怎么写呢?
这时我们借助“包”也就是package,来解决这个命令冲突的问题,如下
回车后如下:
将EuramericanCat.py移动到Eur包下
操作方式就是鼠标拖动该项到上面,然后点击Refactor,效果如下
选择EuramericanCat.py后,按SHIFT+F6修改文件名为Cat(也可直接鼠标点击该项,然后右键菜单中选择Refactor选项)
结果如下
那么现在test.py代码可以这这样编写
也可以这样写
总结一下,看如下两个例子
import agent.base
a=agent.base.Agent()
from agent.base import Agent
a = Agent()
import agent.base 可以翻译成 import agent.base.py 将文件base.py导入进来,这个文件位于agent文件夹下, base.py也对应模块名,最终你要使用Agent类,则每次使用Agent都需要写完整Agent类在那个文件夹下,哪个模块(文件)内
所以为了方便,可以换成第二种写法:
from agent.base import Agent,可以翻译成,从agent.base.py导入Agent类,导入完毕之后抹去了命名空间的信息,因此使用的时候直接使用Agent,缺点是假如另一个类同样也叫Agent,但是位于不同的模块,则会造成命名冲突,a = Agent(),这个Agent是哪个模块的Agent.所以你也可以这样改
from agent.base import Agent as Agent1,然后调用的时候 a=Agent1()
除此之外,还有一种import方式, base是模块名,base.py,调用的时候必须加上模块名
from agent import base
a=base.Agent()
看到这里后,请再次浏览一遍网站上提高的python教程,这时候你应该游刃有余了吧