python3 基础学习笔记

记录学习廖雪峰的python教程,摘要重点

1、基础

1.1数据类型和变量

l  字符串:

转义字符\可以转义很多字符,比如\n表示换行,\t表示制表符,字符\本身也要转义,所以\\表示的字符就是\

为了简化,Python还允许用r''表示''内部的字符串默认不转义

如果字符串内部有很多换行,用\n写在一行里不好阅读,为了简化,Python允许用'''...'''的格式表示多行内容

 

l  空值

空值是Python里一个特殊的值,用None表示。None不能理解为0,因为0是有意义的,而None是一个特殊的空值。

变量

Python中,等号=是赋值语句,可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量

理解变量在计算机内存中的表示也非常重要,如写 a = 'ABC'时,Python解释器干了两件事情: 1、在内存中创建了一个'ABC'的字符串;2、在内存中创建了一个名为a的变量,并把它指向'ABC'

a = 'ABC'

b = a

a = 'XYZ'

print(b)  b的值是’ABC’

常量

所谓常量就是不能变的变量,比如常用的数学常数π就是一个常量。在Python中,通常用全部大写的变量名表示常量

小结

Python支持多种数据类型,在计算机内部,可以把任何数据都看成一个“对象”,而变量就是在程序中用来指向这些数据对象的,对变量赋值就是把数据和变量给关联起来。

对变量赋值x = y是把变量x指向真正的对象,该对象是变量y所指向的。随后对变量y的赋值不影响变量x的指向。

注意:Python的整数没有大小限制,而某些语言的整数根据其存储长度是有大小限制的,例如Java32位整数的范围限制在-2147483648-2147483647

Python的浮点数也没有大小限制,但是超出一定范围就直接表示为inf(无限大)。

 

1.2、字符串和编码

Python字符串

在最新的Python 3版本中,字符串是以Unicode编码的,也就是说,Python的字符串支持多语言。   

print('包含中文的str')

对于单个字符的编码,Python提供了ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符

由于Python的字符串类型是str,在内存中以Unicode表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把str变为以字节为单位的bytes

Pythonbytes类型的数据用带b前缀的单引号或双引号表示

要注意区分'ABC'b'ABC',前者是str,后者虽然内容显示得和前者一样,但bytes的每个字符都只占用一个字节

Unicode表示的str通过encode()方法可以编码为指定的bytes,例如

反过来,如果我们从网络或磁盘上读取了字节流,那么读到的数据就是bytes。要把bytes变为str,就需要用decode()方法

格式化

%运算符就是用来格式化字符串的。

小结

Python 3的字符串使用Unicode,直接支持多语言。

strbytes互相转换时,需要指定编码。最常用的编码是UTF-8Python当然也支持其他编码方式,比如把Unicode编码成GB2312

>>> '中文'.encode('gb2312')
b'\xd6\xd0\xce\xc4'

但这种方式纯属自找麻烦,如果没有特殊业务要求,请牢记仅使用UTF-8编码。

1.3list tuple

List:(可以使用list来模拟堆栈和队列

Python内置的一种数据类型是列表:listlist是一种有序的集合,可以随时添加和删除其中的元素。

追加元素: List.append(‘xxx’)   

插入元素:list.insert(1,’xxxx’)   

删除元素:list.pop(i) 删除索引为i的元素。

替换元素: list(i)=’xxxx’

 

l  Tuple:元组

tuplelist非常类似,但是tuple一旦初始化就不能修改,因为tuple不可变,所以代码更安全

看一个可变的”tuplet = ('a', 'b', ['A', 'B'])  t[2][0]=’X’   t变为('a', 'b', ['X', 'B'])

变得是list的元素,tuple指向的list没有变

小结

listtuplePython内置的有序集合,一个可变,一个不可变。根据需要来选择使用它们。

1.4、dict和set

Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度。

这种key-value存储方式,在放进去的时候,必须根据key算出value的存放位置,这样,取的时候才能根据key直接拿到value

一个key只能对应一个value,如果key不存在,dict就会报错;

要避免key不存在的错误,有两种办法,一是通过in判断key是否存在,(注意:返回None的时候Python的交互环境不显示结果。)

二是通过dict提供的get()方法,如果key不存在,可以返回None,或者自己指定的value

要删除一个key,用pop(key)方法,对应的value也会从dict中删除:

dict是用空间来换取时间的一种方法。

dictkey必须是不可变对象要保证hash的正确性,作为key的对象就不能变。在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key

 

l  set

setdict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key

要创建一个set,需要提供一个list作为输入集合

set可以看成数学意义上的无序和无重复元素的集合,因此,两个set可以做数学意义上的交集、并集等操作

不可变对象

所以,对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回,这样,就保证了不可变对象本身永远是不可变的。

 

函数

2.1 函数调用

l  小结

定义函数时,需要确定函数名和参数个数;

如果有必要,可以先对参数的数据类型做检查;

函数体内部可以用return随时返回函数结果;

函数执行完毕也没有return语句时,自动return None

函数可以同时返回多个值,但其实就是一个tuple

2.2函数的参数

Python的函数定义非常简单,但灵活度却非常大。除了正常定义的必选参数(位置参数)外,还可以使用默认参数、可变参数和关键字参数,使得函数定义出来的接口,不但能处理复杂的参数,还可以简化调用者的代码。

默认参数

定义默认参数要牢记一点:默认参数必须指向不变对象!

可变参数

可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。      

定义可变参数和定义一个listtuple参数相比,仅仅在参数前面加了一个*号。在函数内部,参数numbers接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数:

所以Python允许你在listtuple前面加一个*号,把listtuple的元素变成可变参数传进去。

def calc(*numbers):xxxxx

>>>nums = [1, 2, 3]

>>>calc(*nums)

关键字参数

可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict

def person(name, age, **kw):

    print('name:', name, 'age:', age, 'other:',kw)

>>>extra = {'city': 'Beijing', 'job': 'Engineer'}

>>>person('Jack', 24, **extra)

name: Jack age:24 other: {'city': 'Beijing', 'job': 'Engineer'}

**extra表示把extra这个dict的所有key-value用关键字参数传入到函数的**kw参数,kw将获得一个dict,注意kw获得的dictextra的一份拷贝,对kw的改动不会影响到函数外的extra

命名关键字参数

def person(name, age, *, city,job):

    print(name, age, city, job)

和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符**后面的参数被视为命名关键字参数。

如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:

defperson(name,age, *args, city, job):

    print(name, age, args, city, job)

命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:

参数组合

Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

小结

Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。

默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!

要注意定义可变参数和关键字参数的语法:

*args是可变参数,args接收的是一个tuple

**kw是关键字参数,kw接收的是一个dict

以及调用函数时如何传入可变参数和关键字参数的语法:

可变参数既可以直接传入:func(1, 2,3),又可以先组装listtuple,再通过*args传入:func(*(1, 2, 3))

关键字参数既可以直接传入:func(a=1,b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})

使用*args**kwPython的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。

命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。

定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数。

3  高级特性

3.1切片

L = list(range(100))

请写出:L[:10]    L[2:10]    L[:10:2]    L[:]      L[: : 5]

tuple也是一种list,唯一区别是tuple不可变。因此,tuple也可以用切片操作,只是操作的结果仍是tuple

>>> (0, 1, 2, 3, 4, 5)[:3]

(0, 1, 2)

字符串'xxx'也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串:

>>> 'ABCDEFG'[::2]

'ACEG'

3.2 迭代

如果给定一个listtuple,我们可以通过for循环来遍历这个listtuple,这种遍历我们称为迭代(Iteration)。List, tuple, dict对象均可迭代。

默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同时迭代keyvalue,可以用for k, v in d.items()

如何判断一个对象是可迭代对象呢?方法是通过collections模块的Iterable类型判断:

from collections import Iterable
isinstance('abc', Iterable) # str是否可迭代

如果要对list实现类似Java那样的下标循环怎么办?Python内置的(枚举)enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:

for i, value in enumerate(['A', 'B', 'C']):
...     print(i, value)

3.3 列表生成式

列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。如:list(range(1, 11))

For循环前,写元素表达式,For循环后,还可以加上if判断

>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]

还可以使用两层循环,可以生成全排列:

>>> [m + n for m in'ABC'for n in'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

import os # 导入os模块,模块的概念后面讲到

>>>[d for d in os.listdir('.')] # os.listdir可以列出文件和目录

 

 

for循环其实可以同时使用两个甚至多个变量,比如dictitems()可以同时迭代keyvalue

>>>d = {'x': 'A', 'y': 'B', 'z': 'C' }

>>>[k + '=' + v for k, v in d.items()]

['y=B', 'x=A','z=C']

最后把一个list中所有的字符串变成小写:

>>>L = ['Hello', 'World', 'IBM', 'Apple']

>>>[s.lower() for s in L]  #小写     /////         [s.upper()for s in L]    #大写

['hello','world', 'ibm', 'apple']

使用内建的isinstance函数可以判断一个变量是不是字符串:isinstance(x, str)

3.4 生成器

如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator

只要把一个列表生成式的[]改成(),就创建了一个generator

g = (x * x forx in range(10))

>>>g

<generatorobject <genexpr> at 0x1022ef630>

可以通过next()函数获得generator的下一个返回值。一次次调用next()太变态,正确的方法是使用for循环,因为generator也是可迭代对象

generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。如斐波那契数列。

        

要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator

这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

3.5 迭代器

凡是可作用于for循环的对象都是Iterable类型;

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator它们表示一个惰性计算的序列;

生成器都是Iterator对象,集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

Pythonfor循环本质上就是通过不断调用next()函数实现的,例如

4、函数式编程

函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!

Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。

高阶函数

函数本身也可以赋值给变量,即:变量可以指向函数

那么函数名是什么呢?函数名其实就是指向函数的变量

传入函数:既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数

小结

把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。

Map/reduce

所以,map()作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的f(x)=x2,还可以计算任意复杂的函数,比如,把这个list所有数字转为字符串:

>>> list(map(str, [1, 2, 3, 4, 5, 6,7, 8, 9]))

['1', '2', '3', '4', '5', '6', '7', '8', '9']

 

reduce的用法。reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2),x3), x4)

如把序列[1, 3, 5, 7, 9]变换成整数13579

>>> from functools import reduce

>>> def fn(x, y):

...     returnx * 10 + y

...

>>> reduce(fn, [1, 3, 5, 7, 9])

13579

filter

Python内建的filter()函数用于过滤序列。

map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

注意到filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list

Sorted 排序函数

Python内置的sorted()函数就可以对list进行排序:此外,sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序:

 

要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True

 

返回函数

当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数

def lazy_sum(*args):

    defsum():

       ax = 0

        forn in args:

           ax = ax + n

        returnax

    returnsum

 

当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:

闭包

注意到返回的函数在其定义内部引用了局部变量args,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。

另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。

返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

 

匿名函数

关键字lambda表示匿名函数,冒号前面的x表示函数参数。

匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。

用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:

#将匿名函数作为返回值

def build(x,y):

   return lambda: x*x+y*y

q=build(5,8)()

print(q)

输出为89.                              

装饰器

假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为装饰器Decorator)。

本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:

import functools
deflog(func):
    @functools.wraps(func)
    defwrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

@log

def now():

   print('2015-3-25')

只需记住在定义wrapper()的前面加上@functools.wraps(func)即可。

偏函数

         int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换:还提供额外的base参数,默认值为10。如果传入base参数,就可以做N进制的转换:

>>> int('12345', base=8)

5349

>>> int('12345', 16)

74565

假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去

defint2(x, base=2):
    return int(x, base)

functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2

>>> import functools

>>> int2 = functools.partial(int,base=2)

>>> int2('1000000')

64

>>> int2('1010101')

85

简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

创建偏函数时,实际上可以接收函数对象、*args**kw3个参数

当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。

 

模块

文件www.py的模块名就是mycompany.web.www,两个文件utils.py的模块名分别是mycompany.utilsmycompany.web.utils

 自己创建模块时要注意命名,不能和Python自带的模块名称冲突。例如,系统自带了sys模块,自己的模块就不可命名为sys.py,否则将无法导入系统自带的sys模块。

使用内置模块

使用sys模块的第一步,就是导入该模块:

导入sys模块后,我们就有了变量sys指向该模块,利用sys这个变量,就可以访问sys模块的所有功能。

sys模块有一个argv变量,用list存储了命令行的所有参数。argv至少有一个元素,因为第一个参数永远是该.py文件的名称,例如:

运行python3 hello.py获得的sys.argv就是['hello.py']

运行python3 hello.py Michael获得的sys.argv就是['hello.py','Michael]

 

if __name__=='__main__':

    test()

__name__ 是当前模块名,当模块被直接运行时模块名为 __main__ 。这句话的意思就是,当模块被直接运行时,以下代码块将被运行,当模块是被导入时,代码块不被运行。

作用域

在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过_前缀来实现的

类似_xxx__xxx这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc__abc等;

 

6、面向对象编程

 

所以,面向对象的设计思想是抽象出Class,根据Class创建Instance

面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。

数据封装、继承和多态是面向对象的三大特点,我们后面会详细讲解。

类和实例

class Student(object):

    pass

class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。

bart =Student()                创建实例是通过类名+()实现的

 

由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,在创建实例的时候,就把namescore等属性绑上去:

注意到__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。

 

数据封装

面向对象编程的一个重要特点就是数据封装。在上面的Student类中,每个实例就拥有各自的namescore这些数据。我们可以通过函数来访问这些数据,比如打印一个学生的成绩:

>>>bart.print_score()

BartSimpson: 59

方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;

通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。

print(lisa.name, lisa.get_grade())

print(bart.name, bart.get_grade())

和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同:

6.2 访问限制

但是,从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的namescore属性:

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private,只有内部可以访问,外部不能访问

双下划线结尾变量:变量名类似__xxx__的是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name____score__这样的变量名

单下划线变量:比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,虽然我可以被访问,但是,请把我视为私有变量,不要随意访问

双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量

6.3 继承和多态(重难点)

https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431865288798deef438d865e4c2985acff7e9fad15e3000

由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的开闭原则:

对扩展开放:允许新增Animal子类;

对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。

静态语言 vs 动态语言

对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。

对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了:

这就是动态语言的鸭子类型,它并不要求严格的继承体系,一个对象只要看起来像鸭子,走起路来像鸭子,那它就可以被看做是鸭子。

小结

继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。

动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。

6.4 获取对象信息

当我们拿到一个对象的引用时,如何知道这个对象是什么类型、有哪些方法呢?

使用type()

使用isinstance()

 总是优先使用isinstance()判断类型,可以将指定类型及其子类一网打尽

使用dir():获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度。剩下的都是普通属性或方法,

小结

通过内置的一系列函数,我们可以对任意一个Python对象进行剖析,拿到其内部的数据。要注意的是,只有在不知道对象信息的时候,我们才会去获取对象信息。

6.5 实例属性和类属性

在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性

 

 

猜你喜欢

转载自blog.csdn.net/kllkzl3/article/details/79663069