元组
元组既能像列表那样容纳多种类型的对象,也拥有字符串不可变的特性。
列表用的是方括号,元组用的是圆括号
元组可以不带括号。
元组也可以通过下标来获取元素
元组的内容不可以被修改
列表是支持修改的,元组不支持,所以元组只支持count和index方法。
元组也可以嵌套,嵌套的方式是直接加个逗号。逗号是构成元组的基本条件
只有列表推导式,没有元组推导式子
定义元组的时候,与其纠结什么时候加上圆括号,什么时候省略,还不如一直都加上圆括号。这样也有助于增加代码的可读性。
生成一个元组我们有时候也称为元组的打包
将元组的元素一次性赋值给三个变量的行为称为解包
赋值号左边的变量名数量必须和右边序列的元素的数量一致。
python可以实现多重赋值
>>> x, y = 10 ,20 >>> x 10 >>> y 20
其实背后的逻辑就是先将元组进行打包,然后再将元组进行解包
>>> _ = (10, 20) >>> x, y = _ >>> x 10 >>> y 20
元组中的元素是不可变的,但是如果元组中的元素是指向一个可变的列表,那我们依然可以修改列表里面的内容
>>> s = [1, 2, 3] >>> t = [4, 5, 6] >>> w = (s, t) >>> w ([1, 2, 3], [4, 5, 6]) >>> w[0][0] = 0 >>> w ([0, 2, 3], [4, 5, 6]) >>>
如果可能,能用tuple代替list就尽量用tuple代替list
字符串
字符串归根到底是像元组那样一个不可变的序列。
字符串有许多方法,封装在内部,是用实现效率更高的C语言来编写的。是属于官方的东西
大小写字母换来换去:
capitalize() // 这是将字符串的首字母变成大写,其余字母变成小写。字符串是不可变的对象,调用这个方法也只是按照这个规则生成一个新的字符串 casefold() // 返回一个所有字母都是小写的新字符串,除了英文字母之外还可以处理其余一些字母 title() // 将字符串中每个单词的首字母变成大写,该单词的其余字母变成小写 swapcase() // 将字符串中的所有字母的大小写翻转 upper() // 将所有的字母都变成大写。 lower() // 将所有的字母都变成小写。只能处理英文字母。
左中右对齐
center(width, fillchar = '') // width参数用来指定整个字符串的宽度。如果说指定的宽度小于或等于源字符串,那就直接输出源字符串 ljust(width, fillchar = '') rjust(width, fillchar = '') zfill(width)
如果一个变量连续调用多个函数,python是从左往右依次执行。
\n这个转义字符不是一个可打印字符。
format方法支持未知参数和关键词参数两个。如果是综合未知参数和关键字参数一起使用,未知参数必须在关键字参数之前
冒号表示格式化符号的开始
format方法是字符串格式化的方法, %运算符也是用来格式化字符串的,%会把任何类型转换为字符串。format()方法相比较于%的方法写起来要麻烦一些。
格式化如果有多个参数的话,用元组的方式将其括起来。
%f可指定小数点的精度
列表、元组和字符串的共同点
- 都可以通过索引得到每一个元素
- 默认索引值总是从0开始
- 可以通过分片的方式得到一个范围内的集合
- 有很多共同的操作符。把这三种数据结构统称为序列。
- 迭代就是重复反馈过程的活动,其目的是接近并达到所需的目标或结果。每一次对过程重复我们称之为迭代。每一次迭代得到的结果,都会被用来作为下一次迭代的初始值。所以迭代可以初步理解为for循环。
- 序列天生就是可迭代的对象
内置方法
list()生成空列表,list([iterable]),把一个可迭代对象转换为列表。
就是新建一个列表,然后通过索引,将括号内元组或者字符串的每个值都插入到列表里。迭代完成再把整个列表返回。
tuple([iterable]),把一个可迭代对象转换为元组
str(obj) 把obj对象转换为字符串。
len(sub),返回参数sub的长度。
max()返回序列或者参数集合中的最大值。
min()返回序列或者参数集合中的最小值。
使用max()方法或者min()方法要保证参数或者序列的数据类型是统一的。
max = tuple1[0] for each in tuple1: if each > max: max = each return max
sum(iterable[, start = 0])返回序列iterable(元组、列表)和可选参数start的总和
sorted()
函数
- 对象,函数,模块是学习python的重点
- 函数就是把代码打包成不同功能的代码块,一个程序可以按照不同功能切割成小小的个体,而函数就是可以完成某一个功能的代码块,在python中创建函数用def关键字
- python根据函数名找到这个函数,依次执行这个函数内的代码
- 如果找不到某个函数而去调用,则会报错
- 调用函数只需要一条语句。
- 函数的功能和参数的意义一定要写好相应的注释
形参和实参
形式参数即形参指的是函数创建(定义)的时候小括号中的参数
因为这个参数只是一个形式,表示占据一个参数位置
某个地方调用这个函数,而传递进来的参数叫做实参,因为这个参数是具体的参数值
def MyFirstFunction(name)
name是形参
函数文档和注释不完全一样
.__doc__ 是函数的特殊属性,能够看到函数文档
关键字参数,调用函数传入参数的时候可以指定关键字。
默认参数就是在定义的过程中为形参赋初值,当函数调用的时候,如果忘了传递参数,就会自动去找默认值。
收集参数(可变参数),在形参前面加*号,传多少个参数都可以。原理是把标记为收集参数的参数用一个元组打包起来
函数是有返回值的,过程是简单的、特殊并且没有返回值的。
python严格来说只有函数,没有过程
就算python的函数没有返回语句,用print来输出,python也会返回一个None对象
我们说一个函数是整型的函数,是说这个函数会返回一个整型的函数值。python不这样,python会动态地确定类型,赋值的时候,编译器会自动确定需要什么类型。
在python里面,一个变量拿来用就行了, 不用去关心它是什么类型。包括返回值也是一样,不用去关注它是什么类型
函数变量的作用域问题
- 一般的编程语言都是由局部变量和全局变量
- 在函数里定义的参数,以及变量,都叫做局部变量,出了这个函数,这些变量都是无效的。函数外是无法访问到函数内部的变量的
- 在函数外边定义的变量叫做全局变量,拥有更大的作用域,它们的作用域是整个代码段。
- 如果在函数内试图去修改全局变量的话,那么python会在函数内创建一个新的局部变量代替,这个局部变量和全局变量名称是一样的
- 全局变量在整个代码中都是可以访问到的,但是不要试图在函数内部去修改它,可以在函数内部去访问全局变量的值,但是不要试图去修改它,如果试图在函数内部去修改全局变量,python会在函数内部创建一个和全局变量名称一样的局部变量代替。很容易犯错
- global关键字,在函数内部修改全局变量
内嵌函数
也叫做内部函数
python支持函数的嵌套
内部函数整个作用域,都在外部函数之内
内部函数的定义和调用,都在外部函数之内
闭包:如果在一个内部函数里,对外部作用域(但不是在全局作用域)的变量进行引用,内部函数就会被认为是闭包
python可以直接返回函数对象
匿名函数
- lambda表达式。冒号左边是原函数的参数,冒号的右边是原函数的返回值
- lambda语句返回一个没有名字的函数对象
- python写一些执行脚本时,使用lambda就可以省下定义函数过程,使用lambda可以使得代码更加精简
- 对于一些比较抽象,并且整个程序执行下来只需要调用一两次的函数,有时候取名比较麻烦,使用lambda就不需要考虑命名的问题
- 使用lambda可以简化代码的可读性,普通的函数阅读经常要跳到开头的def定义部分,使用lambda函数可以省去这样的步骤。
两个内置函数
filter(),通过过滤器可以保留我们所关注的信息
filter()有两个参数,第一个参数可以是一个function,也可以是一个none对象,第二个参数是可迭代的数据iterable,如果第一个参数是函数,那么就把第二个参数的每个数据作为函数的输入,把返回结果为true的数据筛选出来
filter()如果第一个参数是None,就是过滤掉0和false
map(),在编程领域,map一般是指映射
map()这个内置函数仍然有两个参数,仍然是一个函数和一个可迭代的序列,将序列的每一个元素作为函数的参数进行加工,直到可迭代的序列的每一个元素都加工完毕,构成一个可迭代的新序列。
这两个内置函数和java的lambda表达式一样,一个是对可迭代序列的每一个元素进行处理即map(),一个是对可迭代序列对每一个元素进行筛选即过滤,即filter()。
testList.steam().filter(t -> t.....).map(w -> w...).collect(Collectors.toList()))
递归
定义树结构,使用递归的方式来定义,比普通的方式来定义要方便很多。
设置递归的深度
import sys sys.setrecursionlimit(1000000)
python提供一个range()函数,可以生成一个整数序列。
python的循环有两种,一种是for...in...循环,一次把list或tuple的每个元素迭代出来
第二种是while循环,只要条件满足,就不断循环,条件不满足时退出循环。
递归的两个条件
- 调用函数自身
- 设置自身正确的返回值。设置某个值就返回了。有进去就必须要有返回。递归必须要有一个正确的返回条件
递归有一个正确的返回值,就像很多数学题,都必须有一个初始值,f(0) = 0之类,实际上是递归的逆过程。
递归的实现原理是调用自身函数,并且设置有返回值,那每次调用函数,都要进行压栈弹栈,保存和恢复寄存器的栈操作,很消耗时间和空间,如果忘记设置返回,就会使程序崩溃。
递归在python是有默认限制。
递归算法是分治思想,把一个复杂问题分成小的问题,把小的问题再继续分。
字典
python的字典把单词称之为key,把单词的含义称之为value
字典是python唯一的一个映射类型。映射是指两个元素集之间,元素相互对应的关系。通过某种关系进行映射,可以是一对一的关系也可以是一对多的关系
序列类型是以数组的类型进行存储的,通过索引的方式来取值。一般索引值和对应存储的数据是毫无关系的
最好是不要通过下标即索引来进行映射。不能通过下标来保证两个序列的元素是一一对应的。
字典在python里的标志性是大括号,由多个键和对应的值组成。元组的标志是()
dict3 = dict((("F", 2), ("U", 1)))
因为字典的参数只有一个mapping类型即映射类型的参数,通过元素的方式传入这种映射类型的参数就可以,也可以通过列表来传入
用关键词传入,键不能加引号,如下:
dict4 = dict(小甲鱼 = "让编程改变世界", a = "b")
key会自动用字符串实现
字典和序列不一样的是,如果在序列中,试图为一个不存在的位置去赋值的时候会报错,会报越界,如果是在字典中,如果创建的一个键值对的键在原先的字典中存在,则会改变原先的键所对应的value,如果键在原先的字典中不存在,则会自动创建相应的键并添加相应的值进去。
dict()是工厂函数,调用它会生成该类型的一个实例,就像工厂一样。
遍历字典(像这些内容都在java中有对应的知识):for eachkey in dict1.values(): print(eachkey)
1. 遍历字典的键 for eachkey in dict1.keys(): print(eachkey) 2. 遍历字典的value for eachkey in dict1.values(): print(eachkey) 3. 遍历整个键值对(在java中即对应map的entrySet) for eachItem in dict1.items(): print(eachItem)
浅拷贝和赋值是不一样的,赋值是贴了一个不同的标签在相同的数据上
popitem()是随机从字典里弹出一个数据
字典和java的map一样,不能依赖顺序
集合
- {}并不是字典的特权。用{}括起一堆数字,但是这些数字没有映射关系,那他们就是一个集合。
- python中的集合和java中的集合不一样。python中集合的元素是唯一的, 而java中的list,map,set都叫集合,只有set才有这个唯一的特性
- 集合会直接把重复的数据清理掉,集合是无序的,不能试图去索引集合中的某个元素
- python中的集合不支持索引
- 如何创建一个集合,有两种方法:
- 一种是直接把一堆元素用花括号括起来
- 使用set()工厂函数,这个工厂函数可以传列表,元组,字符串,返回的都是集合。
- list()这个工厂函数,也可以传入集合,就会返回一个列表
- set()创造的集合中的元素是无序的
- 如何访问集合中的值:
- 可以使用for把集合中的数据一个个读取出来
- 可以通过in和not in判断一个元素是否在集合中已经存在
文件
- 内存和cpu之间的数据传输速度比硬盘和cpu的数据传输速度快很多。
- windows是以扩展名指出文件是什么类型
- python和java一样有垃圾收集机制,在python里面如果忘记关闭文件,不会造成内存泄漏。不过要养成使用完文件就要进行关闭的习惯
- OS的意思就是操作系统
- 不同操作系统的底层对于文件系统的访问工作原理是不一样的
- 有了OS模块,我们不需要关心什么操作系统下使用什么模块,OS模块会帮你选择正确的模块并调用
- rmdir()是删除目录,应该先把目录里面的文件删掉,然后再删除目录,要确保删除的目录是空的,确保这个目录下面没有其他的子目录,也没有文件
- 在Windows和Linux里面用.表示当前目录,用..表示上一级目录
- os.sep 输出操作系统特性的路径分隔符(Win下为“\\”, Linux下为“/”)
- 每个字节一般是8位。
- 数据存储是以字节(Byte)为单位,数据传输大多是以bit为单位,一个位就代表一个0或1,每8个位组成一个字节
- 将对象转化为二进制称为pickling,将二进制文件转换为对象称为unpickling
异常处理
在编程的时候要把用户想象成不会计算机的用户和黑客,要保证自己程序的稳固,不要认为用户的输入都是合法的。
在python里所有东西包括一个变量名都是一个对象
python的异常后缀都是error(目前看到的)
异常并不会致命,我们需要捕获这些异常并纠正这些错误
常见异常:https://fishc.com.cn/thread-45814-1-1.html
异常检测用try语句实现,任何在try语句范围内的异常都会被检测到
try: 检测范围 except Exception[as reason]: 出现异常后处理的代码
如果except指定的异常不是try语句代码里抛出的异常,则仍然会报异常。
如果不清楚是要对哪一类异常进行处理,只是希望try语句里一旦出现异常,就可以捕获并给用户看到对应的信息,(这里的捕获其实就是指捕获到try语句代码块里的异常并进行一个对应的处理),那么就把except后面的内容全部都去掉(只是可以这样做,但是不推荐)
try语句代码块里的语句一旦出现异常,那么剩下的语句就不会被执行,这里和java是一样的
finally里是指无论如何都会被执行的代码
else语句和with语句
else语句还可以和for语句、while语句进行搭配,还可以和异常处理语句进行搭配
和if语句是构成要么怎样,要么不怎样
和循环语句是构成干完了能怎样,干不完就别想怎样
和异常处理语句是构成没有问题,那就干吧
如果循环语句中使用了break跳出了循环,那么else语句就不会执行
只有是没有break,没有跳出循环的情况下,循环执行完了,才会执行else里面的代码
和异常处理语句搭配是只要try语句块里没有出现异常,那么就会执行else语句里的内容
使用with语句可以大大减少代码量,会自动考虑文件关闭的问题
类和对象
把乱七八糟的数据都扔到列表里面,这是一种封装,这是数据层面的封装,我们还可以把常用的代码段封装成一个函数,这是语句层面的封装
对象是模拟真实世界,把数据和代码都封装在一起。
对象可以从属性和方法(行为)两个方面来描述
对象 = 属性 + 方法,把静态的特征称为属性,把动态的动作称为方法。但是从另一个角度来说,类是抽象的,是静态的,对象是类的具体,是类的实例化,是动态的。
class Turtle: # Python中的类名约定以大写字母开头,在java中类是一个抽象的东西,应该通过实例化来创建一个具体的对象(对类进行实例化),但是在这个类中,属性和方法都具体地定义了出来 #---关于类的一个简单例子---# # 属性 color = "green" weight = 10 legs = 4 shell = True mouth = "大嘴" # 方法(行为) def climb(self): print("我正在很努力地向前爬") def run(self): print("我正在飞快地向前跑") def bite(self): print("咬死你") def eat(self): print("有得吃,真满足") def sleep(self): print("睡了") 这整个代码都是在定义对象看上去是什么样,会做什么,也就是在定义对象的属性以及方法,但是这并不是对象,这充其量称之为类对象
对象是类的实例(instance),有这个类是使得对象可以达到量产的效果。做玩具的时候有一个模具,然后可以进行批量的生产。
python中的类名是以大写字母开头,函数名是以小写字母开头,和java一样
self是什么?当一个对象的方法被调用的时候,对象会将自身作为第一个参数传给self参数
在类的定义的时候,把self写进第一个参数
__init__(self) 这个方法称之为构造方法,只要实例化一个对象的时候,那么这个方法就会在对象被创建的时候自动调用,实例化对象的时候也是可以传入参数的 __init__(self, param1, param2...) # 这种情况定义类的时候就要重写__init__方法
python里面找不到和java中类似的关键字来区分是公有的还是私有的,默认上来说,对象的属性和方法都是公有的。python为了实现私有的特征,采用了**name mangling(名字改编、名字重整)**的技术
在Python中定义私有变量,只需要在变量名或函数名前加上“__”两个下划线,那么这个函数或变量就会变成私有的了。
Python目前的私有机制是伪私有机制,python的类是没有权限控制的,变量是可以被外部调用的。
组合就是把类的实例化放到一个新类里面,那就不用继承了,组合就是把横向关系的几个类放在一起,要实现纵向关系的类的关系,就用继承。
OO的特征
1. 封装
- OO(Object Oriented)的特征,python就是一门纯粹的面向对象编程的语言
- 对象封装了属性和方法,成为一个独立性很强的模块,封装更是一种信息隐蔽技术,使得数据更加安全。
- 列表是python的一个序列对象。(实例化之后的才叫对象,对象是具体的,而类是抽象的),提供了若干种方法, 供我们根据需求来调整列表,但是我们不知道列表对象里面的这些方法是如何实现的,我们也不知道列表对象具有哪些变量。列表是对象,提供方法名字,我们去使用
2. 继承
继承是子类自动共享父类之间数据和方法的机制。
class MyList(list) class DerivedClassName(BaseClassName): ...
一个子类可以继承它的父类的任何属性和方法
如果子类中定义与父类同名的方法或属性,则会自动覆盖父类对应的方法或属性,(覆盖的是子类中调用父类的属性或方法, 父类的实例化对象是可以正常调用父类的方法的。)
调用未绑定的父类方法
import random as r class Fish: def __init__(self): self.x = r.randint(0, 10) self.y = r.randint(0, 10) def move(self): self.x -= 1 print("我的位置是:", self.x, self.y) class Goldfish(Fish): pass class Carp(Fish): pass class Salmon(Fish): pass class Shark(Fish): def __init__(self): Fish.__init__(self) # 这个地方没有绑定父类,给的是子类的实例对象 self.hungry = True def eat(self): if self.hungry: print("吃东西") self.hungry = False else: print("不吃东西")
使用super()方法
import random as r class Fish: def __init__(self): self.x = r.randint(0, 10) self.y = r.randint(0, 10) def move(self): self.x -= 1 print("我的位置是:", self.x, self.y) class Goldfish(Fish): pass class Carp(Fish): pass class Salmon(Fish): pass class Shark(Fish): def __init__(self): super().__init__() # 不用给父类的名字 self.hungry = True def eat(self): if self.hungry: print("吃东西") self.hungry = False else: print("不吃东西")
python还支持多重继承,就是可以同时继承多个父类的属性和方法,
class DerivedClassName(Base1, Base2, Base3): ...
当不确定是否是必须使用多继承的时候请尽量避免使用它,容易导致代码混乱
3. 多态
类、类对象和实例对象
类定义--->类对象--->实例对象
如果属性的名字和方法相同,属性会把方法覆盖掉
因为python的变量不需要声明,所以直接给变量赋值,就相当于定义了一个变量
不要试图在一个类里面定义出所有能想到的特性和方法,应该利用继承和组合机制来进行扩展
用不同的词性命名,如属性名用名词,方法名用动词
什么是绑定?Python严格要求方法需要有实例才能被调用,这种限制其实就是Python所谓的绑定概念
>>> class CC: def setXY(self, x, y): self.x = x self.y = y def printXY(self): print(self.x, self.y) dd.setXY(4, 5) (其实等于 dd.setXY(dd, 4, 5)) 类中定义的函数必须要有self参数,因为要与实例相绑定
类中定义的属性是静态变量,方法也一样,就算类对象被删除了,它们依然是存放在内存中的,只有当程序退出的时候,这个变量才会被释放。
dd = CC(),这里的dd是实例化对象,CC是类对象,当CC即类对象被删除之后,想再通过ee = CC()这种形式实例化对象当然是不行的,但是原先的实例化对象dd还是存在于内存中的,只有当程序退出,才会被释放。
所以在绝大多数情况下,在python中,我们要用实例化对象,而不要用类对象来进行处理。在Java中,现在只遇到工具类里的静态方法, 在不实例化对象的前提下就可以使用
自身可以认为是自身的子类
isinstance(object, classinfo) # 1. 如果第一个参数不是对象,则永远返回false # 2. 如果第二个参数不是类或者由类对象组成的元组,会抛出一个TypeError异常
>>> class C: def __init__(self, size = 10): self.size = size def getSize(self): return self.size def setSize(self, value): self.size = value def delSize(self): del self.size x = property(getSize, setSize, delSize)
魔法方法
魔法方法总是被双下划线包围,例如
__init__(self[, ...]) 在类定义的时候,有时候写__init__方法,有的时候没有写,根据需求 >>> class Rectangle: def __init__(self, x, y): self.x = x self.y = y # 左边的self.x 是类实例化之后的实例对象的局部变量,右边是传入的参数 # __init__ 必须返回None
魔法方法的魔力体现在他们总是能够在适当的时候被调用
__init__方法并不是实例化对象调用的第一个魔法方法 __new__(cls[, ...])才是实例化对象调用的第一个魔法方法,一般不重写,python默认地执行它
list(),tuple()通通是工厂函数,所谓的工厂函数就是类对象
int("123") # 以前这样的做法是调用int函数,把参数"123"转化为整型,但是在现在,是实例化int类的对象,返回一个实例化后的对象,传入的参数是"123" a = int("123") b = int("456") a + b 579 # 发现对象是可以进行计算的,因为python无处不对象,当求a+b的时候,python实际上在将两个对象进行相加的操作
python的魔法方法还提供了对对象自定义数字的处理,通过对魔法方法的重写,可以自定义任何对象间的运算
>>> class Try_int(int): def __add__(self, other): # 这里的self和other都是指的对象!!! return self + other def __sub__(self, other): # 这里的self和other都是指的对象!!! return self - other
一个字节由8位组成,这个位(bit)就是二进制
默认的魔法方法会以合乎逻辑的方式让程序进行
通过对指定魔法方法的重写,完全可以让python根据我们的意图执行程序
随着深入,python允许我们做的事情就更多,比如对魔法方法的重写。
a = int("5") b = int("3") a + b # 这些都是对象之间的运算 # 当a对象的add方法没有实现,python就会自动找到对象b的__radd__方法。
用工厂函数来定义或者说生成某种数据,返回的是对象
self是实例化对象的时候默认传入的一个对象,在其他魔法方法中也要有self
class C: def __init__(self): self.x = "Xman" # 即使括号内,没有x这个参数,也能这样设置,因为这是在设置self.x, self后面加上点,再在后面加什么都可以,这是在定义这个对象自身的属性,而括号里的x只是当作函数的参数进行传入。self.x则是对这个对象自身属性的一个设置。 >>> class C: def __init__(self): self.x = "Xman" >>> c = C() >>> c.x 'Xman'
__getattr__(self, name) # 定义当用户试图获取一个不存在的属性时的行为 __getattribute__(self, name) # 定义当该类的属性被访问时的行为 __setattr__(self, name, value) # 定义当一个属性被设置时的行为 __delattr__(self, name) # 定义当一个属性被删除时的行为
描述符:描述符就是将某种特殊类型的类的实例指派给另一个类的属性
__get__(self, instance, owner) - 用于访问属性,它返回属性的值 __set__(self, instance, value) - 将在属性分配操作中调用,不返回任何内容 __delete__(self, instance) - 控制删除操作,不返回任何内容 >>> class MyDecriptor: def __get__(self, instance, owner): print("getting...", self, instance, owner) def __set__(self, instance, value): print("setting...", self, instance, value) def __delete__(self, instance): print("deleting...", self, instance) >>> class Test: x = MyDecriptor() >>> test = Test() >>> test.x getting... <__main__.MyDecriptor object at 0x00000269E3FF80D0> <__main__.Test object at 0x00000269E3FAE400> <class '__main__.Test'> >>> test <__main__.Test object at 0x00000269E3FAE400> >>> Test <class '__main__.Test'> >>> test.x = "X-man" setting... <__main__.MyDecriptor object at 0x00000269E3FF80D0> <__main__.Test object at 0x00000269E3FAE400> X-man >>> test.x getting... <__main__.MyDecriptor object at 0x00000269E3FF80D0> <__main__.Test object at 0x00000269E3FAE400> <class '__main__.Test'> >>> del test.x deleting... <__main__.MyDecriptor object at 0x00000269E3FF80D0> <__main__.Test object at 0x00000269E3FAE400>
这里的instance是指拥有者的实例对象
class Celsius: def __init__(self, value = 26.0): self.value = float(value) def __get__(self, instance, owner): return self.value def __set__(self, instance, value): self.value = float(value) class Fahrenheit: def __get__(self, instance, owner): return instance.cel * 1.8 + 32 def __set__(self, instance, value): instance.cel = (float(value) - 32) / 1.8 class Temperature: cel = Celsius() fah = Fahrenheit() # 这里对上面的代码进行分析,其实__init__魔法方法可以和java的构造函数对应起来,__get__和__set__方法可以分别对应java的get和set方法,都是通过set来进行对值的定义(set),通过get来进行return。 # 下面的cel = Celsius(), fah = Fahrenheit()其实就是在另一个类中对别的类进行实例化,不要想得太复杂了,实例化之后能够进行set和get操作。 # 比如, >>> temp = Temperature() >>> temp.cel 26.0 >>> temp.cel = 30 >>> temp.fah 86.0 >>> temp.fah = 100 >>> temp.cel 37.77777777777778 # 对上面的代码进行分析,Temperature本身是一个类,这个类中对另外的两个类进行了实例化,现在对它又进行实例化。 # temp.cel 就是直接调用类中的这个对象,cel是类Celsius的实例化对象,这里python会自动调用Celsius的__get__魔法方法,返回的是self.value,显然这个self.value是设置有初始值的,即为26.0 # 然后输入temp.cel = 30, 这里是对temp这个对象的cel对象进行操作,相当于对cel对象进行一个set,那么python会调用Celsius类中的__set__方法,然后输入temp.fah,这里会对temp对象的fah对象进行操作,python会调用Fahrenheit这个类的__get__方法,返回的是实例instance.cel*1.8 + 32,显然instance是temp,temp.cel = 30,那会return 30 * 1.8 + 32 # 然后是输入temp.fah = 100,这里是对temp对象的fah对象进行处理,会调用Fahrenheit的__set__方法,会对instance即temp的cel进行设置为37.7777,那相当于temp.cel = 37.7777,和上面的temp.cel = 30一样,那python会调用Celsius的__set__方法,最后调用Celsius的get方法来获得设置后的结果 # 其实整个过程很简单,把整个过程想象成java中的set和get方法就行了,然后在某个类中实例化另两个类,分别进行get和set操作也是很常有的事。
定制序列:
协议与其他编程语言中的接口很相似,它规定你哪些方法必须要定义,比如java,java中的接口,规定了要定义哪些方法,但是在接口类中不能写方法的具体实现过程,接口之所以叫接口,就是因为把方法内部的实现过程隐藏了起来,到具体的类中去实现。接口只提供表面。
在python中,协议显得不正式,更像是一种指南
字典、列表、元组都是属于容器类型,因为里面存放各种各样的对象。
如果希望定制的容器是不可变的话,只需要定义__len__()和__getitem__()方法 如果希望定制的容器是可变的话,除了__len__()和__getitem__()方法,还需要定义__setitem__()和__delitem__()两个方法
迭代器
迭代的意思就类似于循环,每一次重复的过程被称之为迭代的过程,每一次迭代的结果将会被用来作为下一次迭代的初始值。
提供迭代方法的容器称之为迭代器
迭代器:Iterator
list、dict、str虽然是Iterable,却不是Iterator
每次从容器中依次拿出数据,这就是所谓的迭代操作
iter(),对一个容器对象调用iter(),则得到迭代器,调用next()则会得到下一个值
可以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型,如List、tuple、dict、set、str等
一类是generator,包括生成器和带yield的generator function
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable
可以使用isinstance()判断一个对象是否是Iterable对象。
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator
生成器是Iterator对象,但是list、dict、str虽然是Iterable,即可迭代对象,但是却不是Iterator
Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误
Python的Iterator对象表示的是一个数据流,可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,只有在需要返回下一个数据时它才会计算。
Iterator甚至可以是一个无限大的数据流,因为我们无法提前知道长度,只有在需要的时候,用next()函数返回下一个数据,甚至可以是全体自然数,而使用list是永远不可能存储全体自然数的。
凡是可以作用于for循环的对象都是Iterable类型。即可迭代对象。
凡是可以作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列。
集合数据类型如list,dict,str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象
python的for循环会自动调用next()方法
生成器
生成器并不涉及到魔法方法,甚至避开了类和对象。
生成器是迭代器的一种实现
生成器是一个特殊的函数,可以暂停或者挂起,并在需要的时候从程序离开的地方继续或者重新开始。
一旦一个函数里面出现yield语句,说明这个函数被定义为生成器。相当于return,但是普通函数一遇到return,代表函数结束,但是对于生成器来说,就是把yield后面的参数返回,然后暂停,并不是函数结束,下一次执行从这个地方开始执行。
c = {i for i in [1, 1, 2, 3, 4, 5, 5, 6, 7, 8, 3, 2, 1]} 像这种大括号里没有冒号,就是集合推导式,Python的集合里没有重复的数字,和java不一样,java的list map set都称为集合。
Python有列表推导式、字典推导式、集合推导式,但是没有字符串推导式子。
用普通的圆括号括起来的不是元组推导式,而是生成器推导式
生成器属于迭代器,可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误
生成器推导式如果作为某个函数的参数可以不加最外面的圆括号。
g = (x * x for x in range(10)) g是一个generator即生成器,创建生成器只需要把一个列表生成式的[]改成(),可以通过next()函数获得generator的下一个返回值。 # generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算出最后一个元素,没有更多的元素时,抛出StopIteration错误。生成器是可迭代对象。这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。所以正确的方式是使用for循环
我们创建了一个generator后,基本上永远不会调用
next()
,而是通过for
循环来迭代它,并且不需要关心StopIteration
的错误。
模块
容器 --- 数据的封装
函数 --- 语句的封装
类 ---- 方法和属性的封装
模块就是程序,就是我们平时写的任何代码,保存为.py这样的文件,那就是一个独立的模块。
导入模块之后,要想调用这个模块中的函数,要加上命名空间
导入模块
- import 模块名
- from 模块名 import 函数名(这里的函数名可以使用通配符*来导入这个模块的所有函数,但是不建议这样写,因为这样会使得这种写法的优势荡然无存,会将函数名搞混)
- import 模块名 as 新名字(推荐这种方式来导入)
每个模块之间都是独立开的, 增强了可读性。每个人肯定都是更愿意去阅读和调试一小段代码。
写好的模块应该放在哪里
放在和导入这个模块的文件在同一个路径下。
也可以不放在同一个路径下,python模块的导入需要一个路径搜索的过程,如果导入一个hello.py的模块,python会在预定好的搜索路径里找到这个hello.py这个文件,如果没有则导入失败,这个搜索路径是一个目录。
sys.path可以查看到python会在哪些路径下去找模块,如果我们要导入的模块没有放在这些路径下的其中一个路径,就会导入失败,如果不想移动模块的位置,那么通过sys.path.append将模块所在的路径添加进python的搜索路径
python也有包的概念,不要和模块搞混,
创建一个文件夹,用于存放相关的模块,文件夹的名字即包的名字。可以理解为一个包下面有许多模块,一个.py文件是模块,一个包是由多个.py文件组成。可以和java类比一下,一个jar包是由多个类组成,这里的类在java程序中也是用来import,对应于.py文件,而一个jar包下有多个类,相关的类放在一起组成一个jar包。
在文件夹中创建一个__init__.py的模块文件,内容可以为空
Python标准库中包含一般任务所需要的模块(.py文件)
Python除了官方的标准库中的模块,pypi社区收集了全球python爱好者发布的模块。一个py文件就是模块,一个包下面有许多模块。
用C/C++给python编写扩展模块
__all__显示的就是这个模块可以供外界调用的所有信息。 不是所有的模块都有__all__这个属性,__all__是这个模块的作者希望这个模块被调用的东西 如果一个模块有__all__属性, from timeit import * 则__all__属性里面的名字(类、方法(函数))才会被导入
导入模块方式:
import xxx
这种方式使用导入模块里的变量或者函数的时候,必须是
模块名.变量名(函数名)
的形式。import A as B
这种方式同上
from A import B
这种方式如果当前导入的模块里有跟当前名称空间相冲突的名字,那么下面的名字就会覆盖上面
去调用模块里的函数名或者变量名的时候,不需要以模块名作为前缀。
这里的B也可以是通配符
*
要记住这种导入方式,右边的B,仍然是指的模块里的内容
导入包的方式:
包就是一个目录,在Python中,一个文件夹中有__init__.py的文件
是一个有层级的目录结构,包含n个模块或者n个子包,包中一定要有__init__.py文件
也就是包下面可以是子包和模块,并不是说包里面就一定全是模块。
python的库是完成一定功能的代码集合,表现形式是一个模块,或包。
第三方库可以是模块,也可以是包
无论是import形式还是from...import形式,凡是在导入语句中(而不是在使用时)遇到带点的,都要第一时间提高警觉:这是关于包才有的导入语法
在导入的时候出现了
.
,说明这是包的导入方式。凡是在导入时带
.
的,.
的左边必须是一个包,否则非法。需要注意的是from...import导入的模块,import后面必须是导一个具体的目标,错误语法(from a import b.c)正确语法(from a.b.c import d)
from 模块 import 方法
,这个a.b.c就代表模块,其中a和b都是包from glance.api.policy import get
没有from 包 import 模块这种写法。 因为可以写成
import 包.模块
爬虫
什么是网络爬虫?
网络爬虫又称为网络蜘蛛,如果把整个互联网想象成蜘蛛网的构造,每个网站都是一个节点,蜘蛛在上面爬来爬取,抓取我们最有用的资源。
之所以百度或谷歌这种搜索引擎检索到网页,靠的就是每天派出大量的蜘蛛在互联网上爬,对网页中的关键字建立索引,数据库,排序,结果按照搜索的相关度的高低展现给用户。
python怎么连接互联网,通过库urllib
URL的一般格式为:
protocal://hostname[:port]/path
URL由三部分组成:
第一部分是协议:http、https、ftp、file、ed2k...
第二部分是存放资源的服务器的域名系统或IP地址(有时候要包含端口号,各种传输协议都有默认的端口号,如http的默认端口为80)
https://www.bilibili.com/video/BV1xs411Q799?p=54 其中www.bilibili.com是域名系统
第三部分是资源的具体地址,如目录或文件名等。
第一部分和第二部分用://隔开,第三部分用/隔开。
urlopen这个函数的参数既可以是字符串,也可以是requestObject对象。
在客户端和服务器之间进行请求相应的方法最常用的是GET和POST,在定义上来说,GET是指从服务器请求获得数据,而POST是指向指定服务器提交被处理的数据,但是在现实情况中,GET也常常用来提交数据
Request Headers是客户端(可以是网页,可以是python,可以是java...)
服务端可以通过这个Request Header判断访问是否是来自于人类。通过User-Agent的内容,不过User-Agent的内容可以进行简单地自定义。
Form Data是表单数据,是POST提交的主要内容。
urlopen这个函数有一个data参数,data参数如果被赋值,那么就是以POST形式取代GET形式提交,如果这个参数没赋值即默认是None状态,就默认以GET形式提交
json是一种轻量级的数据交换格式。
有一些网站不喜欢被爬虫程序访问,会检查链接的来源,如果来源不是正常的途径,就会被拒绝,所以我们需要对代码进行隐藏,让程序看起来是普通人经过正常浏览器的点击
服务器一般检查链接,是检查链接里的headers里的userAgent来判断来自于代码还是浏览器。
headers必须是字典,一种方法是在作为Request方法的参数传入之前,先进行定义,然后传入
另一种方法是生成request对象的时候,Request方法里没有传入headers参数,进行add_header来添加。Requset.add_header()方法修改。
网站屏蔽爬虫的一种方法是记录IP的访问频率,如果IP的访问频率很快,那么就会被认为是爬虫,就可以不管User-Agent是什么,就会拒绝。我们可以延迟提交的时间,使爬虫看起来更像是人类在浏览,另一种方法就是代理。
代理
把需要访问的网址告诉代理,代理进行访问,把看到的内容转发给我们。服务器看到的是代理的IP地址,而不是我们的IP地址。(代理 肉鸡??搭梯子??)
步骤
参数是一个字典{""类型": "代理IP:端口号"}
proxy_support = urllib.request.ProxyHandler({})
定制、创建一个opener
opener = urllib.request.build_opener(proxy_support)
opener可以看作是一个私人定制。定制一个opener(加上特殊的headers,或者指定代理)来访问。
两种方法
a. 安装opener
urllib.request.install_opener(opener)
这是一劳永逸的做法,在此之后,只要使用普通的urlopen函数,就会自动使用定制好的opener进行工作
b. 定制opener之后调用opener
opener.open(url)
正则表达式是描述复杂规则的工具
search()方法用于在字符串中搜索正则表达式模式第一次出现的位置。范围下标是前闭后开区间
正则表达式也有通配符,是用一个点号来表示。点号能够匹配除了反斜杠之外的任何字符。
在正则表达式中,前面加上反斜杠\就是代表匹配元字符。
字符类就是中括号[]括起来的里面的字符,里面的字符只要有一个匹配成功,就算匹配成功。
\
1. 将一个普通字符变成特殊字符,例如\d表示匹配所有十进制数字。 2. 解除元字符的特殊功能,例如\.表示匹配点号本身 3. 如果在\后加的是数字,引用序号对应的子组所匹配的字符串
子组(子表达式)就是用小括号括起来的。
字符类[]就是一个字符集合的意思,匹配所包含的任意一个字符
1. 连字符-如果出现在字符串中间表示字符范围描述,如果出现在字符串首位则仅作为普通字符 2. 特殊字符仅有反斜线\保持特殊含义,用于转义字符,其他特殊字符如*、+、?等均作为普通字符匹配。 3. 脱字符^如果出现在首位则表示匹配不包含其中的任意字符,如果^出现在字符串中间就仅作为普通字符匹配。 字符类的意思将里面的内容作为普通的字符看待。
{M,N}
M和N均为非负整数,其中M<=N,表示前边的字符匹配M~N次 1. {M,}表示前边的RE至少匹配M次 2. {,N}等价于{0,N} 3. {N}表示前边的RE需要匹配N次。 4. *表示匹配前面的子表达式零次或多次,等价于{0,} 5. +表示匹配前面的子表达式一次或多次,等价于{1,} 6. ?表示匹配前面的子表达式零次或一次,等价于{0,1} 加上小括号就是子表达式
\序号 1. 引用序号对应的子组所匹配的字符串,子组的序号从1开始计算 2. 如果序号是以0开头,或者3个数字的长度,那么不会被用于引用对应的子组,而是用于匹配八进制数字所表示的ASCII码值对应的字符。
如果你需要重复地使用某个正则表达式,那么你可以先将该正则表达式编译成模式对象
我们使用re.compile()方法来编译
>>> p = re.compile(r"[A-Z]") >>> type(p) <class 're.Pattern'> >>> p.search("I love FishC.com!") <re.Match object; span=(0, 1), match='I'> >>> p.findall("I love FishC.com!") ['I', 'F', 'C'] 用这种方式第一个参数就省掉了。
findall()方法,找到所有匹配的内容,组织成列表的形式返回。
findall()方法中,正则表达式如果包含子组的话,会把子组的内容单独返回回来
HTTPError是URLError的子类,服务器上每一个HTTP的响应都包含一个数字的状态码。
需要关注的是400~599这个范围的状态码,因为它们代表响应出了问题,其中出现4xx的状态码,说明问题来自客户端,就是自己哪里做错了,出现5xx的状态码,就是来自服务器的问题。
处理异常的第一种写法 from urllib.request import Request, urlopen from urllib.error import URLError, HTTPError req = Request(someurl) try: response = urlopen(req) except HTTPError as e: print("The server couldn\'t fulfill the request.") print(e.code) except URLError as e: print("We failed to reach a server.") print(e.reason) else: # everything is fine. 处理异常的第二种写法 from urllib.request import Request, urlopen from urllib.error import URLError, HTTPError req = Request(someurl) try: response = urlopen(req) except URLError as e: if hasattr(e, "reason"): print("We failed to reach a server.") print("Reason: ", e.reason) elif hasattr(e, "code"): print("The server couldn\'t fulfill the request.") print(e.code) else: # everything is fine.
Scrapy
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,可以应用在一系列包括数据挖掘,信息处理或存储历史数据等一系列的程序中
Scrapy最初是为了页面抓取所设计的,也可以应用在获取API所返回的数据或者通用的网络爬虫。
pip是python安装软件的工具,python会默认安装pip,会在pypi社区自动搜索模块,输入命令进行安装。
使用Scrapy抓取一个网站一共需要四个步骤
创建一个Scrapy项目
定义Item容器
Item是保存爬取到的数据的容器,其使用方法和python字典类似,并且提供了额外保护机制来避免拼写错误导致的未定义字段错误。
首先我们需要对需要获取的数据进行建模(items.py文件里创建字段)。
编写爬虫
接下来是编写爬虫类Spider,Spider是用户编写用于从网站上爬取数据的类。
其包含了一个用于下载的初始URL,然后是如何跟进网页中的链接以及如何分析页面中的内容,还有提取生成item的方法(parse())。
scrapy crawl dmoz,就会来找name的蜘蛛或者爬虫,所以这个name是唯一的
# 找到爬虫之后,就会知道下面的start_urls列表里的两个初始化地址,这个地址会提交给Scheduler,Scheduler再安排好顺序发给Downloader去下载,下载之后返回response给Spiders # 也就是下面的函数parse会接收到这个response
# parse()方法接收Downloader返回的response,然后进行分析处理,提取items给Item Pipeline
爬的过程完成之后,就是取的过程,就要用到Scrapy框架的Item容器。从得到的网页内容提取出我们需要的数据,这一步我们之前使用过正则表达式,现在在Scrapy框架中,可以用Selector。
Selector是一个选择器,它有四个基本的方法:
xpath(): 传入xpath表达式,返回该表达式所对应的所有节点的 # XPath是一门在网页中查找特定信息的语言,所以用XPath来筛选数据,要比使用正则表达式更容易些 /html/head/title: 选择HTML文档中<head>标签内的<title>元素 /html/head/title/text(): 选择上面提到的<title>元素的文字 response.xpath() == response.selector.xpath() selector list列表。 >>> response.xpath('//section/div/div/div/div[@class="title-and-desc"]/a/@href').extract() >>> response.xpath('//section/div/div/div/div[@class="title-and-desc"]/a/div/text()').extract() >>> sites = response.xpath('//section/div/div/div/div[@class="title-and-desc"]') >>> for site in sites: ... title = site.xpath('a/div/text()').extract() ... print(title) css(): 传入CSS表达式,返回该表达式所对应的所有节点的selector list列表 extract(): 序列化该节点位unicode字符串并返回list re(): 根据传入的正则表达式对数据进行提取,返回unicode字符串list列表。
- /html/body/div[5]/div/section[3]/div/div/div[5]/div[3]/a
存储内容
Scrapy项目创建好之后,初始项目结构如下:
tutorial/ scrapy.cfg # 项目的配置文件,不要删除,保持默认。 tutorial/ # 存放的是模块的代码 __init__.py items.py # 是容器。 pipelines.py settings.py # 设置文件 spiders/ __init__.py ... # 这是一个框架,它提供了基本的部分,剩余的部分需要我们来完成。
字符串里涉及到需要双引号的,最外层用单引号便可以解决这个转义问题
GUI Tkinter
- 获取输入框里面的内容可以用entry组件的get方法。
- 为了在某个组件上安装垂直滚动条,你需要做两件事:
- 设置该组件的yscrollbarcommand选项为Scrollbar组件的set()方法
- 设置Scrollbar组件的command选项为该组件的yview()方法
pygame
一个包里面包含着各种不同功能的模块,一个.py文件就是一个模块。
用户的一切行为都会变成一个个事件消息。
什么是surface对象
surface对象就是pygame里面用来表示图像的对象。
以后在pygame里面说图像,其实就是指的surface对象
将一个图像绘制到另一个图像上是怎么回事
bilt方法将一个图像放到另一个图像上,并不是真的说将一个图像拷贝到另一个图像上去,实际上是修改另一个图像某一部分像素的颜色,最终每一刻呈现出来的仍然是一个图像而不是两个图像。
移动图像
调用rect对象(矩形)的move方法,修改矩形的位置
pygame的效率高不高
不高
rect(Surface, color, Rect, width = 0)
Surface
指定的是矩形将绘制在哪个surface对象上color
是指定矩形的颜色Rect
指定矩形的范围,其实就是矩形对象width
指定边框的大小
图像是特定像素的组合,Surface对象是pygame里面对图像的描述,在pygame中,到处都是surface对象。
小乌龟在画布上运动,其实是小乌龟也是一个surface对象,画布也是surface对象,把一个surface对象放到另一个surface对象上,占的位置为一个矩形对象。运动的话就是改变像素。
载入图片image.load会返回surface对象
事件
事件是pygame提供给我们去干预游戏的机制
比如说点关闭是quit事件
处理quit事件的方法就是调用system.exit()方法
事件随时都可能发生,移动鼠标,点击鼠标,敲击键盘都会产生事件。
pygame的做法就是把所有产生的事件都放到一个事件队列里面
通过for语句迭代取出每一个事件,对我们关注的事件进行处理
pygame并不可以直接让我们在surface即图像对象上绘制想要的文字
font的render方法,将文字渲染成surface对象
飞机大战
把能独立分开的代码独立成一个一个的模块,一个.py文件就是一个模块,这个是类似于java中的一个类
导入模块和导入包是不同的,具体的区分一定要弄清楚。
写程序的逻辑一定要清楚,在一个项目里面,资源(resource)都装在对应的文件夹里。