# 第一优先级规则声明:
# 除了梦境,每一个意识主进程都必须与一个身体参与的机械进程相匹配,否则结束意识主进程。如学习python同时必须伴有记笔记、敲代码等机械进程,学习英语必须伴有朗读、听说、查字典、查语法书等机械进程跟随,拆解问题必须在纸上或iPad上实时记录意识的每一次尝试。
如果身体状况或是客观情况不允许有机械进程存在,请转入其他进程,如注意力恢复(闭目养神、睡觉)或是娱乐进程(健身、看电影等)。
# 此优先级将在每一个意识进程前声明。
Day1:
Why Python 3.5(意义赋予):
- Life is short and I need python.
- 以前没有编程基础,只有Linux和MySQL的基础。
- 每天能抽出时间不多。
- 工作的强烈需求。
- Python大量的第三方库
- 开源社区的强力支持
- 3.5总比2.7强,长远来看
Installation: 略
IDLE: Pycharm
编码声明:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
也可以借助pycharm右下角的编码来修改,或是notepad++的encoding-转为utf-8无BOM格式
I/O:
print()
print在python3.5中正式成为函数,允许用“,”分隔多个字符串,也接受数学计算
input()
接受用户的输入,同时也接受()内插入内容。
如果要规范输入的内容,可以用这种方法
int(input(...))
代码风格:
python使用缩进来组织代码块,请移动使用4个空格的缩进(pycharm会对不规范的代码风格给出波浪下划红线的warning)
数据类型和变量:
类型在python中极其重要,最常见的报错就是“你不能对xx类型进行xx操作”!
整数integer:正整数,负整数,0
浮点数float:小数(叫浮点数是因为用科学计数法表示,一个浮点数的小数点位置可变),存在四舍五入的误差。
字符串string:单引号或者双引号括起来的任意文本(里边有特殊符号请转义)。需要转义的入托太多,用r'string' 来带来一大堆的\。计算string多长,用len()
布尔值:True/False 1/0。如果True/False拼错了或者大小写出错,并不会识别为布尔值(在pycharm中识别成为橙色)。可加减乘除,可and/or/not。
空值:None
变量赋值符号是“=”,等于号是‘==’,所以请正确理解
x = 3
x = x + 3
赋值过程依照语句顺序而来。
常量:不能变的变量
Python支持多种数据类型,可以把任何数据都看成一个对象,
- 变量就是在程序中用来指向这些数据对象的。
- 对变量赋值就是把数据和变量给关联起来
字符串和编码
Python3支持中文
1个中文字符经过utf-8编码后占用3个字节,一个英文字符占用1个字节。
格式化:
格式化的使用频率非常高,其应用主要在于占位符的使用
%s 字符串(如果不清楚数据类型,可以用%s来接受一切)
%d 整数 (整数接受%002d这种表达,来填充位数)
%f 浮点数(浮点数接受%.3f这种表达,来截取位数)
%s 字符串
%x 十六进制整数
%%来表示正常的百分比
条件判断和循环
计算机可以自动化,其中最重要的原因就是因为有条件判断和循环
条件判断
(注意冒号!)
if <条件判断1>:
<执行1>
elif <条件判断2>:
<执行2>
elif <条件判断3>:
<执行3>
else:
<执行4>
if 语句从上往下判断,如果某个判断是True,执行完毕就不执行了。
循环
1. 遍历式循环(for x in ...)
for i in ... :
.......
2. while循环: 只要条件满足,就不断循环(类似scratch里用if和forever在循环中结合的用法)
不过代码会出现死循环(如果写的有问题),就像Human Resource Machine里小人不停地搬箱子。这时候可用ctrl+c结束(与linux一致)
List和Tuple:
list: 列表,python内置。是一种有序的元素集合(或者说容器),元素是什么数据类型都可以可以随时添加和删除其中的元素()。同样,可以用len()获取list中的个数(string也能进行len()操作,因为string本质上也是list)
访问list中的每个元素需要通过索引(索引:从0开始的序列号),格式为list[1],超出索引报错IndexError。也可以通过负数的索引号来倒取元素
支持list.append(),追加元素到末尾
支持list.insert(index, 'element'),插入元素到指定位置。
支持空List,且len(List)为0
因为list是可变的,所以才可以list.sort()来排序
Tuple: 不可变的list是tuple, 不能append/insert操作,可以用索引取值,但不能再次赋值了。
tp = ()
tp = (1,) 如果要定义的是tuple, 务必通过括号内逗号来消除歧义。
Dict和Set
dict
其他语言中也称之为map, 使用key-value存储,优点是查找速度极快。
d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
为什么查找dict特别快,比查找list要开快得多呢? list查询是遍历查询,从第一个翻到最后一个,查询速度直接取决于list大小。dict直接计算出对应的位置,直接取出,非常快。根据什么算出来呢?就是key-value。
dict有个缺点:占用内存大。正因为如此,拿空间换时间,占用空间大,所以查找速度快。
既然是通过Key来查找,那么key一定要是不可变对象。通过key来计算位置的算法,就叫做hash算法。
要保证hash正确性,那么key就不可变(如字符串和整数)。但是list这种就是可变的,就不能作为Key。这时候报错就是Typeerror: unhasable type
d['barry'] = 90 这个过程跟给变量赋值没什么区别,相当于给key这个变量赋值。
调用不存在key的,会返回keyerror。
怎么判断Key在不在dict里呢?
1. 可以通过key in dict来判断,会返回布尔值。
2. 也可以d.get['barry']。如果key不存在,可以返回None, 或者自己制定的value
d.get['key', value]
要删除一个Key,就d.pop('key').
既然是一个字典dict,那么哪一对key-value在前面是无所谓的。也就是说,无所谓key-value的顺序,什么时候放入的不重要。
set
set和dict唯一的区别就是,只存储Key,不存储value。因为dict内部key就不能重复,所以set里边的元素也不能重复(即使重复,也会自动会被过滤)。一样,单纯的key集合也无所谓先后顺序。
这样,才形成了set数学意义上的无序+无重复元素的集合。
s = set(list)
list = [1, 2, 3]
可以通过add(key)来添加元素(key)到set当中,重复添加倒是可以,不过还是会被过滤掉。
可以通过remove(key)来删除元素。
可以s1 & s2 取交集,s1 | s2取并集。(跟linux非常相似)
可变和不可变
不可变对象(如string)调用对象自身的任意方法,也不会改变对象自身的内容。相反这些方法会创建新的对象然后返回,这样就保证了不可变对象的不便。
函数
函数是啥?函数本质上就是把一些现成的功能写成一个封装的包(跟第三方库非常像),调用即可。也就是说,函数可以自己写,也可以用写好的现成的。比如不用每次都写平方或者指数不需要每次都写x * x...这种底层写法。很多函数内置,很多函数在可调用库。(import大法好!)
抽象
抽象是啥?每次都还原成最底层代码烦不烦?那你需要抽象!
抽象可以使我们每次都调用一些聚合过的概念,如求和西格玛符号。
有好多内置函数(build-in functions),可以直接调用。(至少读一遍,知道都有啥。Python真的是能够做到,你能想到的函数,99%都有之前人写过,stack overflow/github/python各种库,多读读)
Built-in functions:
数学运算类:
abs(x) 取x的绝对值
divmod(a, b) 返回(商,余数)
float(x) 取浮点数 ;bool() 取布尔值; str()转换成字符; int() 转换成正整数
int(x, base=n) 取整数,base是可选的,表示进制) 也可以long(x, base=n) 转Long
pow(x, y, z) x的y次方(如果z存在,那么再对结果取模)
range([start], stop[, step]) 产生一个序列,规定结束,可以规定开始和步长。
round(x[,n) 四舍五入,可以规定n位数
sum(iterable[, start]) 对可迭代对象求和
集合操作符
iter(o[, sentinel) 生成一个对象的迭代器,第二个参数表示分隔符(?)
max() min() 最大最小值
dict() set() list() str() tupe()创建
sorted(iterable, key=None, reverse=True/False) 排序
all(iterable) 集合中的所有 元素都为真时候为真,空串返回为真。
any(iterable) 任一为真就为真
IO操作
input() 获取用户输入
open(file_name, mode) mode可以是'r', 'w', 'a'
print() 打印函数
神器
help()
函数
自己动手写函数
def my_abs(x):
if x > = 0:
return x
else:
return -x
逻辑是: 执行到return就结束了。没有return,函数执行完毕,返回None
如果某一个步骤没想好,还可以直接pass(放过),占着位置
一个函数的功能完善,包括三方面的完善: stdin, stdout, stderror。所以如果我们要自己定义一个完善的函数,必须包括这三个方面。要不然调用函数错误,都不知道为什么错误。
def my_abs(x):
if not isinstace(x, (int, float)):
raise TypeError(‘bad operand')
if x>= 0:
return x
if x < 0:
return -x
(isinstance(x, type)是判断x是否为type的函数)
很多函数都return一个值,那么可以return多个值么?可以,但是返回的本质上是个tuple
比如刚刚学到的科学计算函数divmod(x, y), 返回的就是两个值(商,余数),但是如果你去尝试type(),就会看到这是一个tuple
type(divmod(5, 2)
Out: tuple
函数的参数
对于日常使用来说,因为python大量的build-in functions和变态的import功能,我们非常少去自己写一个函数。更多的是理解自己调用的函数该如何使用——核心就在于,函数的参数的了解和使用。日常使用的大量报错都是因为:对于某个函数是否能够接受某种方式传入的某个参数的不确定,导致大量debug时间(随机尝试不是个长期解决方案,虽然能够不断地返回error信息以供调试)
1. 位置参数
简单理解,位置固定的参数,如pow(x, y)计算x的y次方。直接写pow(3, 2)不再需要规定哪个是x, 哪个是y
2. 默认参数: 大大减轻了函数调用的难度
比如range(5),实际上start位置的参数就被忽略了,只传入了stop。但是仍然能运行,因为range()内部规定了start默认为0。
也可以不接受默认设定,自己规定:range(2, 5)
但是在自己定义函数的时候,必选参数在前,默认参数在后。且默认参数指向不可变对象(不能指向个变量什么的!)
3. 可变参数
并不规定要传入几个参数,多少个都行。define的时候加个星号即可
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
这样numbers里边有多少个都没问题。
如果已经有个numbers集合(list或者tuple)咋办?
num = [1, 2, 3]
calc(*num)
这种写法,就是把num这个list中所有元素都传入calc()进行计算。如果不加*会咋样?
TypeError: can't multiply sequence by non-int of type 'list'
原理上,可变参数允许你传入 0 个或任意个参数,这些可变参数在函数调用时自动组装为一个 tuple。
4. 关键字参数
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
除了规定的name和age, 还接受扩展性的其他参数。这就被称为关键字参数
比如用户除了必填项之外,还愿意提供其他参数,那么就可以通过关键字参数来扩展。
5. 命名关键字参数(限制了可扩展的关键字)
def person(nama, age, *, city, job)
这里的星号后边的city和job就是命名关键字参数,也就是限定了的关键字参数。(不加星号,就变成了位置参数)
语法风格
**args: 可变参数,接受的是一个tuple
**kw: 关键字参数,kw接收的是一个dict
可变参数既可以直接传入func(1, 2, 3), 又可以先组装list或tuple,再通过**args传入func(*(1, 2, 3))
关键字参数既可以直接传入 func(a=1, b=2), 又可以先组装dict, 再通过**kw传入 func(**{'a': 1, 'b': 2})
切片
list切片 L[1:3]这种方式,包前不包后。也可以L[:3]或者L[2:]。L[:]就是区这个List本身
实际上,切片的规则是list[start:stop:step]
list的切片结果是List,tuple的切片结果是tuple。
string也是一种List
迭代:
通过for循环来遍历list或是tuple或是其他可迭代对象,这边遍历我们称为迭代iteration。
python中,迭代是通过for...in完成的。
像dict这种类型,默认迭代的是key。想迭代value, 可以for value in d.values()。同时要迭代key和value, 可以for k, v in d.items()
只要是可迭代对象,就可迭代。判断是否是可迭代对象,方法是
from collections import Iterable
isinstance('abc', Iterable) 前面我们学过了isinstance函数
列表生成式List comprehesions
顾名思义,就是为了生成List的(list几乎是应用最为广泛的类型) [x * x for x in range(1, 11)]
要生成的元素放在前面,后边跟for循环(其实就是for循环的一种简写)
还可以加上if(但是不接受else)
x * x for x in range(1, 11) if x % 2 ==0
m + n for m in 'ABC' for n in 'XYZ'
利用这个列表生成式,可以生成非常简介的代码,比如生成当前目录下所有的文件名称
import os
[d for d in os.list('.')]
生成器generator
列表生成式虽然生成方便(一个规则+for循环[+if语句]),但是问题也很明显。
你生成辣么大的列表,占着内存,留着过年么?又不见得全都用得上!
这时候你就需要个generator, 也就是 边生成,边循环,边计算。
符号上跟列表生成式的区别只有一个
List = [x * x for x in range(10)]
Generator = (x * x for x in range(10))
对,没看错,就是把中括号变成了小括号,就从list变成了generator。(如果你去type()他们,会看到区别)。
调用列表当然很简单,怎么调用generator呢。怎么个生成法呢?
用next(generator)来调用,调用一次生成一次。也就是每次next(generator)结果都不一样,next()到最后一步,抛出Stopiteration错误。不过你也看出来了,也不能每次都next(),啥时候是个头啊?(实际上几乎用不上next())
所幸,一个generator仍然能被for循环调用。
for n in generator:
print(n)
那么问题来了:挖掘机技术哪家强?如果列表生成式这种简单的结构,描述不了一个generator的算法怎么办?(当然能看出来,列表生成式虽然入手容易,不过干不了太复杂的事情——你看它那个傻样连else都不接受)
好消息!好消息!用定义函数的方法就可以创建generator啦!不过要把return改成yield。(等用到再来填坑)
迭代器Iterator
前面说到,可直接作用于for循环的数据类型有
1. 集合数据类型:list, tuple, dict, set, str等
2. generator,包括生成器和带yield的generator function
这些可以直接作用于for循环的对象,叫Iterable可迭代对象。判断类型有两个方法:type()和isinstance(obj, type)
切记:判断Iterable之前,请先from collections import Iterable
可以用next()调用并不断返回下一个值的对象成为迭代器Iterator。想把Iterable对象编程iterator,可以使用Iter()函数。
for循环本质上就是不断调用next()实现的。以下两个是等价的:
for x in [1, 2, 3, 4, 5]:
pass
it = iter([1, 2, 3, 4, 5])
while True:
try:
x = next(it)
except StopIteration
break
高阶函数
1. 变量可以指向函数,函数的参数能接受变量。
2. 所以,一个函数局可以接收另一个函数作为参数。这种函数就称为高阶函数。
def add(x, y, f):
return f(x) + f(y)
就是传入两个值,绝对值后再相加。
map/reduce
map(function, iterable)得到一个Iterator。map就是把一个函数,作用于一个iteratable。
(想要读取结果,可以list(iterator))
list(map(str, [1, 2, 3])) # 把列表里的123全部字符串化。
reduce把一个函数作用在一个序列上,这个函数必须接受两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:
from functools import reduce
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
filter函数用于过滤序列,和map一样,filter也接受一个函数和序列,然后根据函数返回值是T/F来决定保留还是丢弃该元素。返回的也是惰性序列,需要list()。一样,filter也是惰性计算,不调用不筛选。
sorted既是可以对list进行排序的函数,也是个高阶函数。
sorted([36, 5, -12, 9, -21], key=abs)
sorted(['bob', 'about', 'Zoo'], key=str.lower, reverse=True)
高阶函数抽象能力强,代码简洁
注意,这些高阶函数因为是把一个函数分配到iterable里边的每一个元素里边了,所以不用再去考虑怎么样调用每一个元素了。比如
L= [('Bob',75),('Adam',92),('Bart',66),('Lisa',88)]
def by_name(t):
return t[0].lower()
L2 = sorted(L, key=by_name)
print(L2)
不用再想怎么去调用每一个元素了,比如L[0][1]这种。。。
返回函数(坑,小特性,待填充)
匿名函数
很多时候我们不需要显式定义函数,直接做一个匿名函数更方便,而且函数名还不会重复。
list(map(lambda x: x * x, [1, 2, 3, 4])
要比这么写方便很多
def f(x):
return x * x
关键字lambda表示匿名函数,冒号前面x表示函数参数。但是只能有一个表达式,不用写return,返回值就是该表达式的结果。
也可以把匿名函数作为返回值返回
def build(x, y):
return lambda: x * x + y * y
python的一些单条表达式适合用lambda函数,都是一些比较简单的状况。
装饰器Decorator(没搞懂)
函数支持通过function_name.__name__来调用函数名称(后面会详细解析)
装饰器就是在函数动态运行期间,给函数增加了的功能(其实就是返回函数)。
@log # 把log函数通过@方法,放到now()前面,作为装饰器出现。
def now():
print('2016-10-01')
在面向对象的设计模式中,decorator被成为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。(啥意思?)
偏函数
借助functools.partial(需要先import functools),可以创建一个部分参数被固定的常用函数。这就称为偏函数。
比如int(obj, base=n)。如果进制参数base的n常用(比如转换成为16进制),那么可以规定
int(obj, base=16)作为偏函数。使用方法如下
import functools
int2 = functools.partial(int, base=8)
但是,调用的时候如果主动改变规定好的默认参数,比如int2(int, base=16),16进制转换仍然有效。
当函数的参数个数太多需要简化的时候,固定住某一些参数,从而方便调用。
模块
把函数分组,放到不同的.py文件里,每一个.py文件就叫做模块。
调用模块的最大好处在于,不需要重复制作轮子,只要调用就好。
模块会组成package(包),所以我们会说“调用第三方包/库,说的就是这个意思。
包目录下,必须有__init__.py文件,否则python就不识别这个包目录,而是当成普通目录。
切记:不能与系统自带模块重名,比如sys。
使用模块要先import sys。
模块的编写(略)
安装模块
安装第三方模块,是通过pip工具
$ pip install pandas
调用的时候, 只import就只会在1. 当前目录 2. 所有已经安装的内置模块 3.第三方模块 进行搜索,实际上遍历了sys.path变量下的文件内容
import sys
sys.path
如果要暂时添加,用append()
sys.path.append('the directory you want to add')
第二种方法设置环境变量PYTHONPATH,该环境变量的内容会被自动添加到模块搜索路径
(待填坑)
面向对象编程Object Orient Programming(OOP)
OOP把对象作为程序设计的基本单元,一个对象包含了数据和(操作数据的)函数。把计算机程序程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息。执行程序就是在对象之间传递信息。
面向过程的程序设计把计算机程序视为一系列的命令集合,就是一组函数的顺序执行。具体,就是把函数切分成为子函数。
python中所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(class)的概念。
比如class是student, 是泛指学生这个概念,instance则是一个个具体的student, 张三李四。
所以,面向对象的设计思想是抽象出class,根据class创建instance。
面向对象的抽象成都又比函数要高,因为一个class既包含数据,又包含操作数据的方法。
面向对象三大特点:数据封装、继承和多态
是时候祭出这张图了!当当当当!
类和实例
类是抽象的模板,比如student类。实例是根据类创建出来的一个个具体的对象,每个对象只是数据不一样而已,方法是相同的。
定义一个class
class Student(object):
pass
注意:类名通常是首字母大写的单词,接下来是(object),表示该类是从哪个类继承下来的。如果没有,就继承自object类,这是所有类最终都会继承的类。
定义好了Student类,就可以根据Student类创建出Student的实例,创建实例是
barry = Student()
barry
<__main__.Student at 0x103d40d68>
后边的0x103d40d6是内存地址,每个object地址都不一样。
每个实例都可以自由绑定属性,比如
barry.name = 'barry li'
这种方法对于实例虽然自由,但是没有让class起到模板的作用。我们要把class更加规范化,从而让instance更加规范化,这个方法就是定义__init__
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
__init__方法的第一个参数永远是self,表示创建的实例本身。因此,在__init__方法内部,就可以把各种属性(name, score这种)绑定到self,因为self就指向创建的实例本身。
这样定义完毕了,就不能再barry = Studeng()了,比如传入
barry = Student('barry li', '100') 来定义
barry.name输出barry li
barry.score输出100
def __init__的过程,跟普通函数的定义只有一点不同,就是第一个参数永远是实例变量self,并且在调用时不用传递该参数。所以,默认参数、可变参数、关键字参数和命名关键字参数仍然是存在的。
数据封装
可以直接在class的内部定义访问数据的函数,而不必在外部定义,这样就把数据进行了封装。封装数据的函数和class本身是关联起来的,我们称之为类的方法(所以会经常看到“这个类没有这个方法”)
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
想要调用
barry.print_score() 得到barry li: 100
(注意, 这个时候print_score(barry)) 就不好用,想想为什么)
这样,封装的巨大意义就出现了:创建实例只需要给出score, name这些,而打印只需要调用print_score,这些都是在Student类内部定义的,这些数据和逻辑封装起来了,调用很容易,但却不用知道内部实现的细节。
(注意print_score仍然是函数,只不过是class内部的函数,称为Method(?), 所以return)
封装的另一个巨大好处是,可以给一个定义好的class增加新的方法,比如get_grade
总结下,类是创建实例的模板,实例是一个个具体的对象,比如你要批量创造键盘,那么要先规定好,键盘(作为class)的特征是什么,(要传入的参数是什么,比如尺寸,颜色,轴体,品牌等),然后创造出来的具体的键盘比如filco圣手二代(实例)。但其实跟创建的第二个键盘Cherry MX2.0没啥关系,但是他们都是键盘呀(摊手)。
class Keyboard(object):
def __init__(self, size, color, switch, barry)
self.switch = switch
...
def print_switch(self):
print('%s' % self.switch)
如果我们要知道,某个键盘是什么轴体的,那么可以直接在class内部定义好,怎样从外部获取:直接定义访问轴体get_switch访问switch这个参数的内容就好。
通过实例调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。
同样的,对一个实例的操作,并不影响其他实例。比如指着A说“你除了一个鼻子,两个眼睛,是‘人’这个类之外,你还有有个特性:A.feature = '傻逼') 定义了A的feature是个傻逼,但是你如果尝试调用B.feature,就会报错:B连feature这个属性都没有,更别提B是不是傻逼了。
访问限制
外部代码还是可以自由修改一个实例的name, score属性。如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在python中,实例的变量如果以__开头,就变成了一个私有变量(private),外部不能访问,只能通过内部定义的方法来访问,这样就保证了代码的robust。
class定义好了规则,只能通过指定规则来访问指定数据,这种直接从底层拿的方法就被ban掉了。
一样,修改也是可以开放的, 可以在内部定义set_score这种。换句话说,修改这个权限也是在内部通过方法实现的。
继承和多态
在OOP中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为subclass。而被继承的class被称为基类、父类和超类(base class, super class)。
还说机械键盘的例子,Cherry这个subclass只pass了下,就获得了来自base class Keyboard的clean_keyboard方法。
当然,subclass也不是什么都要跟着base class走。完全可以定义自己的方法。当然,如果subclass创建了跟base class同名的方法,以subclass为主导(这和父母和孩子的关系很像:孩子的后天行为会覆盖掉先天影响,但是孩子来源于父母)
比如这个clean_keyboard方法,父类子类都有,以子类为主。代码运行,总是调用子类的。
这时候,就是继承的另一个好处:多态。啥是多态?
我们定义了一个class,我们就定义了一个数据类型,跟内置的tuple/list/dict/set/string/int没啥两样。如果我们用常规测类型的方法isinstance(obj, type)来测,就会发现cherry(作为实例,不是class) 确实是属于Cherry类型,同时也是Keyboard类型,当然更是object类型(这是根父类)。
所以这三个结果都是True
print(isinstance(cherry,Cherry))
print(isinstance(cherry,Keyboard))
print(isinstance(cherry,object))
理解多态的好处:
实际上,任何依赖Animal作为参数的函数或者方法不加修改地正常运行。
新增一个Animal的子类,不必对run_twice进行修改。也就是说不管传入的是猫是狗,我们只需要接收Animal类型就好。因为猫狗都是Animal类型,然后按照Animal类型进行操作即可。由于Animal类型有run()方法,因此传入的任意数据类型,只要是Animal的类或子类,就会自动调用实际类型的run()方法,这就是多态的意思:
对于一个变量,我们只需要知道它是Animal类型,无需确切知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在Animal、Dog还是cat对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用只管调用,不管细节。当我们新增一种Animal子类的时候,只要确保子类的run()编写正确(前提是run()还是要写到子类里边的),不用管原来的代码是如何调用的。这就是著名的”开闭“原则:
对扩展开放:允许新增Animal子类;
对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。
继承还还可以一级一级地继承狭隘,就好比从爷爷到爸爸、再到儿子这样的关系。而任何类,最终都可以追溯到根类object,这些继承关系看上去就像一颗倒着的树。
从这个意义上讲,在任何一个更根的类里定义的方法,都可以被这个分支上的所有子类继承。
看到这里,一定有人像我一样晕了
dog.run()
run_twice(Dog())
为什么一个dog在前面
一个Dog()在里面?
经过测试,这两个写法居然不是通用的,那是怎么回事?
dog.run() 是方法的使用范例
run_twice(Dog()) 是函数的使用范例
方法和函数的关系是,
函数(function)就相当于一个数学公式,它理论上不与其它东西关系,它只需要相关的参数就可以
方法(method)是与某个对象相互关联的,也就是说它的实现与某个对象有关联关系.也就是说,在Class定义的函数就是方法.
简而言之,方法和对象相关,函数和对象无关。
静态语言 vs 动态语言
对于静态语言(如Java),如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则将无法调用run()方法。
对于Python这样的动态语言来说,不一定需要传入Animal类型。我们保证传入的对象有一个run()方法就可以了。鸭子类型和多态详解
http://blog.csdn.net/shangzhihaohao/article/details/7065675
以前写过一篇文章讲了一下python中的多态,最后得出结论python不支持多态,随着对python理解得加深,对python中得多态又有了一些看法。
首先Python不支持多态,也不用支持多态,python是一种多态语言,崇尚鸭子类型。以下是维基百科中对鸭子类型得论述:
在程序设计中,鸭子类型(英语:duck typing)是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定。这个概念的名字来源于由James Whitcomb Riley提出的鸭子测试,“鸭子测试”可以这样表述:
“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为鸭的对象,并调用它的走和叫方法。在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的走和叫方法。如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有这样的正确的走和叫方法的对象都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。
鸭子类型通常得益于不测试方法和函数中参数的类型,而是依赖文档、清晰的代码和测试来确保正确使用。从静态类型语言转向动态类型语言的用户通常试图添加一些静态的(在运行之前的)类型检查,从而影响了鸭子类型的益处和可伸缩性,并约束了语言的动态特性。
毫无疑问在python中对象也是一块内存,内存中除了包含属性、方法之外,还包含了对象得类型,我们通过引用来访问对象,比如a=A(),首先python创建一个对象A,然后声明一个变量a,再将变量a与对象A联系起来。变量a是没有类型得,它的类型取决于其关联的对象。a=A()时,a是一个A类型的引用,我们可以说a是A类型的,如果再将a赋值3,a=3,此时a就是一个整型的引用,但python并不是弱类型语言,在python中'2'+3会报错,而在PHP中'2'+3会得到5。可以这么理解,在python中变量类似与c中的指针,和c不同的是python中的变量可以指向任何类型,虽然这么说不太准确,但是理解起来容易点。
因此,在python运行过程中,参数被传递过来之前并不知道参数的类型,虽然python中的方法也是后期绑定,但是和Java中多态的后期绑定却是不同的,java中的后期绑定至少知道对象的类型,而python中就不知道参数的类型。
还引用上次的例子:
[python]view plaincopy
classA:
defprt(self):
print"A"
classB(A):
defprt(self):
print"B"
classC(A):
defprt(self):
print"C"
classD(A):
pass
classE:
defprt(self):
print"E"
classF:
pass
deftest(arg):
arg.prt()
a = A()
b = B()
c = C()
d = D()
e = E()
f = F()
test(a)
test(b)
test(c)
test(d)
test(e)
test(f)
输出结果:
[python]view plaincopy
A
B
C
A
E
Traceback (most recent call last):
File"/Users/shikefu678/Documents/Aptana Studio 3 Workspace/demo/demo.py", line33,in
test(a),test(b),test(c),test(d),test(e),test(f)
File"/Users/shikefu678/Documents/Aptana Studio 3 Workspace/demo/demo.py", line24,intest
arg.prt()
AttributeError: F instance has no attribute'prt'
a,b,c,d都是A类型的变量,所以可以得到预期的效果(从java角度的预期),e并不是A类型的变量但是根据鸭子类型,走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子,e有prt方法,所以在test方法中e就是一个A类型的变量,f没有prt方法,所以f不是A类型的变量。
以上是从java的角度分析的,其实上边都是一派胡言,只是为了说明python中的运行方法。没有谁规定test方法是接收的参数是什么类型的。test方法只规定,接收一个参数,调用这个参数的prt方法。在运行的时候如果这个参数有prt方法,python就执行,如果没有,python就报错,因为abcde都有prt方法,而f没有,所以得到了上边得结果,这就是python的运行方式。
获取对象信息
当我们拿到一个对象的引用时,如何知道这个对象是什么类型,有哪些方法可以调用?
type()
判断对象类型,基本类型肯定都可以判断,变量或者类也能判断。
In[7]: type(Cat())
Out[7]: __main__.Cat
In[5]: type(abs)
Out[5]: builtin_function_or_method
可见,type()返回的是对应的class类型。也可以通过if语句来生成布尔值
type('abc') == str
type('abc') == type(123)
也可以判断一个对象是否为函数,但是需要调用types(别忘了s啊!!)
import types
def fn():
pass
type(fn) == types.FunctionType
type(abs) == types.BuiltinFunctionType
type((x for x in range(10)) == types.GeneratorType
isinstance()
type()直接判断没问题,判断继承就很麻烦——我们想知道的是Dog是否属于Animal,type()只能告诉我Dog()(实例)属于Dog
一句话:isinstance可以帮你判断一个对象是否是该类型本身,或是位于该类型的父继承链上。
也可以判断多个,关系是any(或)
isinstance([1, 2, 3], (list, tupe))
>>>True
使用dir() (重头戏来了)
如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list。(这也是针对”某对象没有这个方法“报错的最有效方法——而不是盲目尝试或是贸然百度,需要学会调试错误)
比如dir('ABC') # 查看一个string所有的属性和方法,以“__"开头的暂且不提
'capitalize','casefold','center','count','encode','endswith','index','isdecimal','isdigit','isidentifier','islower',isnumeric','isprintable','isspace','lower','lstrip',maketrans','partition','replace','rindex','just','rpartition','rsplit','rstrip','splitlines', (复制的不全)
要清楚一件事情,len('ABC') 也是通过'ABC'.__len__()实现的
(想想为啥一个写'ABC'.len()不行。因为一个字符串并没有内部的方法叫len(),不信可以自己去看。只有内部确有这个方法可以用object.method())
剩下的都是普通属性方法,比如lower()返回小写的字符串:'ABC'.lower()
�进阶:
可以使用hasattr(object, name) 判断object是否有这个属性
可以用setattr(object, name, value) 给object加上name这个属性,value是xxx
也可以用getattr(object, name)来获取name的value 等价于obj.name
报错提醒:如果试图获取不存在的属性,会抛出AttributeError(最常见的错误之一)
可以通过限制getattr(obj, name, 404) 这种方法来限定错误返回——即不返回AttriuteError,而返回指定的value, 比如404
所以,通过内置的type(), isintance(),dir(),我们可以对任意一个python对象进行解析,拿到其内部的数据。只有不知道不确定对象内部信息的时候,我们才会尝试去获取对象信息。如果你知道可以
sum = obj.x + obj.y没问题,就不要sum = getattr(obj, 'x') + getattr(obj, 'y')
但是话说回来,根据鸭子类型原理,只要有x方法,就可以用调用。有x方法的不一定是同一类对象——但是鸭子根本不在乎。
实例属性和类属性
就一点:在写程序的时候,千万不要把实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性。(如果删除类型属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性)
我觉得好像从现在开始,知识的抽象程度就直接升了一个档次。每一块花的时间都要比前面的几块加起来还要多——但这也正是意义所在,简单的东西稍微下功夫就学得会,但终究价值不大。
面向对象高级编程
数据封装、继承和多态只是面向对象程序设计中的基础三概念。高级功能还有很多。
使用__slots__
先说一个小技巧,可以直接给一个实例绑定一个方法,通过
from types import MethodType
instance.function = MethodType(function, instance)
但是一般我们不给instance绑定方法,我们都直接给class绑。有两个方法,要么直接在class内绑定,要么通过instance.function = MethodType(function, instance)来绑定方法
内部绑定的方法是常规方法,基本上所有语言都能实现;
外部绑定因为是后绑定上去的,或者说随时想绑就绑的,称之为动态绑定,只有动态语言才能实现。
针对instance和class, 如果用这种方法来绑定属性的话,基本上想绑啥就绑啥。其实是很多时候需要限制的,这时候就需要在class内部定义的__slots__。
class Student(object):
__slot__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
这时候尝试去绑定其他属性就会返回错误
但是!这仅仅限于这个class, 继承class的其他subclass并不受__slot__限制,除非subclass自己也定义上__slot__。
使用@property(和装饰器相连,实在是没看懂)
多重继承
只有通过多重继承,一个子类就可以同时获得多个父类的所有功能。
这样,就避免了继承的过分的复杂性。
在设计class的继承关系时,通常都是“一脉相承”。如果需要额外混入其他功能,通过多重继承就可以实现。就如同Dog继承Mammal,同时也继承Runnable。这种设计通常称之为MixIn。MixIn的目的就是给一个类增加多个功能。这样我们会优先考虑通过多重继承来组合多个MixIn功能,而不是设计多层次的复杂的继承关系。(JAVA这种只允许单一继承的语言不能使用MixIn的设计)
定制类、枚举类、元类(略,等待填坑)
6天时间复习到这。元动力消耗完毕,任务挂起,切换至pandas。