python基础总结

一等对象

条件:

  • 运行时创建
  • 能赋值给变量或数据结构中的元素
  • 能作为参数传给函数
  • 能作为函数的返回结果

在python中,所有的函数都是一等对象

可调用对象

可以使用调用运算符(‘()’)的对象, 可以使用callable()判断对象是否可以调用

  1. 用户自定义函数
  2. 内置函数
  3. 内置方法
  4. 类的定义体中定义的函数
  5. 类:
  6. 类的实例:如果类定义了__call__,它的实例可以作为函数调用
  7. 生成器函数:使用yield关键字的函数或方法,调用生成器函数返回的生成器对象

注:不仅python函数是真正的对象,任何python对象都可以表现的像函数,为此,只需实现实例的__call__方法

规避GIL

  • 使用multiprocessing创建一个进程池,把它当作协处理器使用
  • 把重点放在C语言扩展编程上,主要思想就是将计算密集型的任务转移到C语言中,在C代码中释放GIL
  • 用ctypes封装C代码

全局变量

  • 在基本的python语法当中,一个函数可以随意读取全局数据,但是要修改全局数据的时候有两种方法:
    a. global 声明全局变量
    b. 全局变量是可变类型数据的时候可以修改
  • global用法
    • 局部作用域可以读取外部作用域的变量,但如果改写就会抛出异常
    • 经测试,只要在用到外部变量的地方都需要使用global来声明
    • 在闭包中使用很不同,在外函数声明的变量,在内部使用global,在做修改,还是会报错

列表推导式

列表推导式和生成器表达式除了能筛选数据之外,还可以用新值替换不满足标准的值,条件判断分别放在前后即可满足这两个功能

mylist = [1,4,-5,10,-7,2,3,-1]
clip = [n if n > 0 else 0 for n in mylist]
## clip值为:
[1,4,0,10,0,2,3,0]
clip2 = [n for n in mylist if n > 0]
## 输出为:
[1,4,10,2,3]

迭代器和生成器

所有的生成器都是迭代器,因为生成器完全实现了迭代器接口
一个自定义的容器对象内部持有一个列表或元组等其他可迭代对象,想让自己的新容器完成迭代操作可实现委托迭代

 def __iter__(self):
    iter(self._inter_list)    # self._inter_list is a list

StopIteration 用来指示迭代的结尾
生成器只有在响应迭代操作(next)时才会运行
如果想编写生成器用来把其他的生成器当做子例程调用,可以选择yield from见cookbook4.14;yield from的作用?
想在自己的类中实现生成器,需要实现__iter__并在其中实现逻辑
可迭代对象和迭代器之间的关系:python从可迭代对象中获取迭代器
在python语言内部,迭代器用于支持:
○ for循环
○ 构建和扩展集合类型——如将该可迭代对象传递给list函数构建一个列表等
○ 逐行遍历文本文件
○ 列表推导,字典推导,和集合推导
○ 元组拆包
○ 调用函数时,使用*拆包实参
• 可迭代对象与迭代器之间的关系:python从可迭代对象中获取迭代器
• 标准的迭代器对象实现两个方法:
next: 返回下一个可用元素,如果没有元素了,抛出StopIteration异常
iter:返回self, ——
• 构建可迭代对象和迭代器时经常会出现错误:混淆二者:要知道可迭代对象有个__iter__,每次实例化一个新的迭代器;而迭代器要实现__next__方法,返回单个元素,此外还要实现__iter__方法,返回迭代器本身;可迭代的对象一定不能是自身的迭代器,也就是说,可迭代对象要实现__iter__,但不能实现__next__:这么做是有原因的,所以必须这么做——fluent14.3; 当然有替代方式,那就是fluent14.4,用生成器代替迭代器,在可迭代对象的__iter__中操作生成器,不用再单独定义一个迭代器
○ *可以用来处理可迭代对象
• 生成器:函数中只要出现了yield语句就是一个生成器函数,调用生成器函数会返回一个生成器对象,也就是说,生成器函数是生成器工厂
• 生成器函数的定义体中通常都有循环,不过不是必要条件
• 可以用生成器创建新的迭代模式
• 和普通函数不同的是,生成器只能用于迭代操作——是不是只能用于for循环迭代?——不是,也可以用于next方法
• 生成器函数的定义体执行完毕后,生成器对象会抛出StopIteration异常
• 生成器表达式可以理解为列表推导的懒惰版本
○ yield from iterable本质上等于for item in iterable: yield item的缩写版;除了代替循环外,yield from还会创建通道,把内层生成器直接和外层生成器的客户端联系起来
• 标准库中的生成器函数:
• 用于过滤的生成器函数:从输入的可迭代对象中产生元素的子集,但不修改元素本身

协程

是一个过程,这个过程与调用方协作,产出由调用方提供的值
• 定义:使用生成器函数定义:定义体中有yield关键字
• yield在表达式中的应用:如果协程只需从客户那里接收数据,那么产出的值是None,这个值是隐式指定,因为yield关键字右边没有表达式
• 与创建生成器函数一样,调用函数得到生成器函数
• 使用协程的步骤:
○ 预激:使用next(xx)或者x.send(None)(x为协程对象)
○ 给协程发送:x.send(y), 这个方法使生成器前进到下一个yield语句;还允许使用生成器的客户把数据发给自己,即不管传给.send()方法什么参数,那个参数都会成为生成器函数定义体中对应的yield表达式的值——这个值给yield表达式=左边的变量值
○ 关闭协程:x.close()
• 注意:使用yield from句法调用协程时会自动预激
• 终止协程和异常处理
○ throw(exc_type[, exc_value, [, traceback]]):致使生成器在暂停的yield表达式处抛出指定的异常,如果生成器处理了异常,代码会向前执行到下一个yield表达式,而产出的值会成为调用throw方法得到的返回值(经实测),如果不处理异常,会在调用端抛出异常,协程终止,状态为GEN_CLOSED
• yield from:会在内部自动捕获StopIterable异常
• yield from链条必须由客户驱动,在最外层委派器上调用next函数或send方法,也可以隐式调用如for循环
• yield from表达式的值是子生成器终止时传给StopIteration异常的第一个参数
• 协程的状态(四个状态),当前状态可以使用inspect.getgeneratorstate()函数确定

函数

python中的函数是一等对象
• 高阶函数:接受函数为参数,或者把函数作为结果返回的函数是高阶函数
• 参数中"*""**"的作用: 接受任意数量的位置参数使用以开头的参数,接受任意数量的关键字参数,这个参数的类型实际上是元组,会被存入到元组中;以开头的参数只能作为最后一个位置参数出现,而以**开头的参数只能作为最后一个参数出现,参数类型是字典,就是说,这个键值会被存入到字典中。

def a(x, *args, y):
     pass
def b(x, *args, y, *kwargs):
     pass
## y只能作为关键字参数使用,所以,要编写只通过关键字的形式接受特定的参数,可以将关键字参数放置在以*打头的参数或者单独一个*之后:
def recv(maxsize, *, block):     # 不代表任何参数,只用来表示其后的参数为关键字参数
     pass
  • 在变量前加单星号表示将元组(列表,集合)拆分为单个元素
  • 字典前加单星号可以得到“键”
  • 函数注解:函数注解只会保存在函数的__annotations__属性中(cookbook 7.3):这个函数注解只是参数类型,默认值,返回值类型,添加字符串说明并不会出现在.__annotations__中
    • 可以利用函数注解实现函数重载:cookbook9.20
    • 在:之后增加注解表达式;如果参数有默认值,注解放在参数名和=之间
    • 注解最常用的类型是类和字符串
    • 注解不会做任何处理,不检查,不强制,不验证
>>>def add(x:int, y:str)->int:
     return x+y
>>>add.__annotations__
{'x':<class 'int'>, 'b':<class 'str'>, 'return':<class 'int'>}
>>>def func(x:int, y:'int > 20'=30)->int
    return x+y
{'x':<class 'int'>, 'y': 'int > 20', 'return': <class 'int'>}     

• 函数中返回多个值:只需要返回一个元组即可
• 定义带有默认参数的函数:
• 确保默认参数在最后出现
• 不存在值传递,唯一支持共享传参(引用传递);传进去的值都会改变
• 对默认参数的赋值只会在函数定义的时候绑定一次,如(cookbook7.5):——和lambda表达式不同,一个是在定义时绑定,一个是在运行时绑定
• 默认参数是否可以这这样理解:如果传入的默认参数是列表,默认列表只在函数被定义的那一刻创建一次(在函数定义时就已经分配好地址了),后面的改动都只是在这个可变对象上做改动,其他对象也都是用同一个列表;如果默认为None,在实例化其他对象时总是会重新创建一个列表,不担心会互相影响——默认值在定义函数时计算(通常在加载模块时)——类的init的方法中的默认值还没有执行怎么确定下来的?

>>> x = 42
>>> def spam(a, b=x):
>>>      print(a,b)
>>> spam(1)
1 42
>>> x = 23
>>> spam(1)
1 42     ## 值没有改变

• 如果默认值是可变容器如列表,集合或字典,应该使用None作为默认值
• 给默认参数的赋值的应该总是不可变对象,如None,True/False,数字或者字符串,因为默认值如果在函数体外被修改了,这种修改会在之后的函数调用中对参数的默认值产生持续影响(cookbook7.5):
• 如果要避免这种情况,就将默认参数设置为None,在函数体中给这个参数给一个列表

>>> def spam(a, b=[]):
         print(b)
>>> x = spam(1)
>>> x
[   ]
>>> x.append(99)
>>> x.append('yow')
>>> x
[99, 'yow']
>>> spam(2)
[99, 'yow']          # 这个例子就是因为列表是可变对象才出现这样的问题吗?

lambda表达式

表达式的计算结果就是返回值
• 最适合使用的场合:参数列表中;除了作为参数传给高阶函数之外,python很少使用匿名函数
• 在运行时绑定而不是定义时绑定,如果想在定义的时候绑定就将该值作为默认参数(cookbook7.7)
(这个例子是定义时就绑定了)

>>> x=10
>>> a=lambda y,x=x:x+y——lambda表达式已经运行了?
>>> x=20
>>> b=lambda y,x=x:x+y
>>> a(10)
20
>>> b(10)
30
## 高级应用:通过列表推导创建一系列lambda表达式
funcs = [lambda x:x+n for n in range(5)]————??
funcs = [lambda x,n=n:x+n for n in range(5)]
for i in funcs:
     print(i(0))     # 打印结果不同

• 看这段代码的输出结果:

def multipliers():
    return [lambda x : i * x for i in range(4)]
print [m(2) for m in multipliers()]
输出的结果是[6,6,6,6]而不是[0,2,4,6]
                    怎样解决这个问题?

• 使用生成器:

def multipliers():
    for i in range(4): yield lambda x : i * x

• 使用闭包,利用默认函数绑定

def multipliers():
    return [lambda x, i=i : i * x for i in range(4)]

• 第三种方案是使用偏函数

from functools import partial
from operator import mul
def multipliers():
    return [partial(mul, i) for i in range(4)]

• 函数有多个参数,但要求只能带少数个参数,可以使用functools中的partial
• 闭包:延伸了作用域的函数。闭包的一个作用就是可以记住额外的变量后续使用,例子见cookbook7.9;
• 为什么闭包可以访问外面函数定义的局部变量?——调用闭包后局部变量已经初始化,外面的函数返回了,本地作用域一去不返了,这个局部变量就成了自由变量了,自由变量是指未在本地作用域中绑定的变量
• 可以使用闭包代替只有一个方法(不包含init)的类
• 无论何时,在编写代码中遇到需要附加额外的状态给函数时考虑使用闭包
• 访问定义在闭包内的变量:
• nonlocal声明
• 编写存取函数,并将它们作为函数属性附加到闭包上来提供对内层变量的访问支持

def sample():
     n = 0
     def func():
          print('n=', n)
     def get_n():
          return n
     def set_n(value):
          nonlocal n
          n = value
     func.get_n = get_n     # 函数也可以有属性
     func.set_n = set_n
     return func

• 尽量用异常来表示特殊情况,不要返回None
• 与用户定义的常规类一样,函数使用__dict__属性存储赋予它的用户属性
• __defaults__属性:值为元组,保存着位置参数和关键字参数的默认值;仅限关键字参数的默认值在__kwdefaults__属性中;参数的名称在__code__属性中
• 如果一个函数或方法对对象进行的是就地改动,就应该返回None,好让调用者知道传入的参数发生了变动,而且并未产生新的对象
• 函数注解:

特殊变量

file:表示显示文件的当前位置,但是:
• 如果当前文件包含在sys.path里,返回一个相对路径
• 如果不包含在sys.path里,返回绝对路径

文件操作

open(file, mode=‘r’, buffer=-1, enocding=None, erros=None, newline=None, closefd=True, opener=None):打开文件,返回对应的文件对象,默认打开方式为只读;
打开文件时应当始终明确传入encoding=参数,因为不同设备使用的默认编码可能不同

  • 参数
    • mode:指定打开文件的方式,默认为‘r’表示以只读的方式打开,具体有以下几种方式:
      • ‘r’: 只读
      • ‘w’:写,如果有内容会直接截断
      • ‘a’:写,如果文件已存在会追加内容
      • ‘x’:——未知
      • ‘b’: 二进制方式
      • ’t’: 文本模式(默认该模式)
      • ‘+’: 打开更新一个disk file(读和写)
      • ‘U’:——未知
      • ‘r+’ 相当于 r+w(可读可写,文件若不存在就报错(IOError))
      • ‘w+’ == w+r(可读可写,文件若不存在就创建),使用w模式在打开文件前就已经清空文件内容了
      • ‘a+’ ==a+r(可追加可写,文件若不存在就创建)
      • 对应的,如果是二进制文件,就都加一个b就好了:‘rb’/‘wb’/‘ab’/‘rb+’/‘wb+’/‘ab+’
  • 如果打不开文件,抛出OSError
  • 如果打开的是一个二进制文件,返回的是没有任何decode的的bytes对象
  • 方法:
    • f.readline():只读一行,用\n分割;如果到达结尾EOF,一个空字符串返回
    • f.read(n):如果不带参数读出从当前位置到文件尾的所有内容,以string或bytes的方式返回,带参则返回从当前位置开始的n个字节
    • f.tell():返回文件当前位置距离文件头的字节数
    • f.seek(n):跳到文件从头开始的第n个字节处
    • f.write(s):将s写到文件,返回写入的字节数
    • f.close():关闭文件,释放资源
  • 问题
    • a或a+模式修改文件指针对于文件的写入没有影响,都会在结尾追加写入
    • 对文件的某一行添加内容单纯靠文件操作貌似行不通,只能通过将文件内容读入某种类型操作完后再整体写入文件

技巧总结

• 让自己的程序在终止时向标准错误输出打印一条消息并返回一个非零的状态码,可以发出一个SystemExit的异常:raise SystemExit(‘It failed’), 这条信息会被打印到sys.stderr上,且程序的退出状态码为1——返回状态码为1有什么用?
• 在运行时提供密码输入:使用标准库中的getpass模块,有两个方法,一个异常:
getpass.getpass(prompt=‘Password’, stream=None): 默认提示为’Password’,输入的内容在命令行是看不到的;在Windows下stream默认是忽略的
getpass.getuser(): 返回的是默认的用户名
execption getpass.GetPassWarning:这个异常是在什么情况下抛出?
• 在脚本中打开浏览器:使用模块webbrowser

规避GIL

  1. 使用multiprocessing模块创建进程池
  2. C语言扩展编程上,将计算密集型任务转移到C语言中,使其独立于python,在C代码中释放GIL。具体怎么实现?cookbook12.9;使用其他工具如ctypes或Cython可能不需要做处理,ctypes默认会在调用c代码时自动释放GIL

拷贝操作

  1. ‘=’:是引用,一个改变会引起其他的改变
  2. 列表切片操作是一个深拷贝,复制后的新对象为一个独立存在,不会相互影响 ——列表的构造方法和切片操作都是浅拷贝
  3. copy模块:两个方法copy和deepcopy和一个异常copy.error
    • copy(x):浅拷贝,拷贝父对象,不会拷贝对象内部的子对象;构造一个新的复合对象后,会尽可能的在原始对象中找到对象的引用插入新对象中
    • deepcopy(x):深拷贝;构造一个复合对象后,会递归地将在原始对象中找到的对象的副本插入新对象中
    • 注意:
    • 对于简单对象来说,深拷贝和浅拷贝并没什么区别,改变原始队列,赋值队列并不会改变;但是对于嵌套队列来说,改变了子对象的值,浅拷贝会改变,深拷贝不会变;但问题是,简单对象和复杂对象怎么区分?——包含其他序列

命名空间

名称到对象的映射。命名空间是一个字典的实现,键为变量名,值是变量对应的值。各个命名空间是独立没有关系的,一个命名空间中不能有重名,但是不同的命名空间可以重名而没有任何影响。
• 分类: python程序执行期间会有2个或3个活动的命名空间(函数调用时有3个,函数调用结束后2个)。按照变量定义的位置,可以划分为以下3类
• Local,局部命名空间,每个函数所拥有的命名空间,记录了函数中定义的所有变量,包括函数的入参、内部定义的局部变量
• Global,全局命名空间,每个模块加载执行时创建的,记录了模块中定义的变量,包括模块中定义的函数、类、其他导入的模块、模块级的变量与常量。
• Built-in,python自带的内建命名空间,任何模块均可以访问,放着内置的函数和异常。
• 生命周期:各命名空间创建顺序:python解释器启动 ->创建内建命名空间 -> 加载模块 -> 创建全局命名空间 ->函数被调用 ->创建局部命名空间
• Local(局部命名空间)在函数被调用时才被创建,但函数返回结果或抛出异常时被删除。(每一个递归函数都拥有自己的命名空间)。
• Global(全局命名空间)在模块被加载时创建,通常一直保留直到python解释器退出。
• Built-in(内建命名空间)在python解释器启动时创建,一直保留直到解释器退出。
• 各命名空间销毁顺序:函数调用结束 -> 销毁函数对应的局部命名空间 -> python虚拟机(解释器)退出 ->销毁全局命名空间 ->销毁内建命名空间
• python解释器加载阶段会创建出内建命名空间、模块的全局命名空间,局部命名空间是在运行阶段函数被调用时动态创建出来的,函数调用结束动态的销毁的。
• module的一些内置属性:
name:直接运行本文件时这个值为"main",在其他程序中导入这个文件时__name__值为该文件名。因此通过判断__name__值就可以区分py文件是直接被运行的还是被引入到其他文件中
file : 当前module的绝对路径
dict
doc
package
path

else语句块

• if/else
• for/else: 仅当for循环运行完毕时(for循环没有被break语句中止)才运行else块
• while/else: 仅当while循环因为条件为假值(没有被break中止)而退出才运行else块
• try/else: 仅当try中没有异常抛出时才运行else

多线程:

• actor:一个actor就是一个并发执行的任务,只是简单的执行发送给它的消息任务。作为对这些消息的响应,actor会决定是否要对其他的actor发送进一步的消息。actor任务之间的通信是单向而且异步的,因此,消息的发送者并不知道消息何时才会实际传递,当消息已经处理完毕时也不会接收到响应或者确认。

其他信息

鸭子类型:
白鹅类型:只要cls是抽象基类,即cls的元类是abc.ABCMeta,就可以使用isinstance(obj, cls)——没理解
. 其他信息:
• nonlocal用法(py3中引入,不是局部变量,也不是全局变量):作用是把变量标记为自由变量;闭包外的变量如果直接用是可以的,但要修改值就不行了(可变类型如list是可以的,数字,字符串,元组等不可变类型只能读取不能更新),需要使用nonlocal表明:如果在闭包内给该变量赋值,修改的是闭包外的那个作用域中的变量
• 建议:只在简单的函数中使用这种机制,副作用很难追踪,如果复杂,就将相关的状态封装成辅助类辅助类:参考effective Python p33
• 因为:python不要求声明变量,但是假定在函数体中赋值的变量是局部变量
• 如果有多个变量,可以连续使用 如:nonlocal a, b
• 注意:仅仅是用在闭包中,如果不是在闭包中而是在函数中修改全局变量,需要使用global
• global:如果在函数中赋值时想让解释器把局部变量当成全局变量,就使用global声明;global的作用就是把局部变量提升为全局变量
• 关于ASCII编码的错误,前加:#–coding:utf-8 --,这样就在脚本中使用了unicode UTF-8编码
• 将单引号或双引号转义(可以打印出来)使用转义符“",或者使用三引号,可以在一组三引号之间放入任意多行字符串。三个双引号可以用三个单引号替代
• 单斜线除法执行正常的除法,双斜线除法取整数
==is的区别:

  • is判断的是id值,比较对象的标识
  • ==:比较两个对象的值
    示例:
class WTFpass
>>>WTF()==WTF()
False
>>>WTF() is WTF()
False
>>>hash(WTF())==hash(WTF())
True
>>>id(WTF())==id(WTF())
True

当调用 id 函数时, Python 创建了一个 WTF 类的对象并传给 id 函数。然后 id 函数获取其id值 (也就是内存地址), 然后丢弃该对象。该对象就被销毁了。
当我们连续两次进行这个操作时, Python会将相同的内存地址分配给第二个对象。因为 (在CPython中) id 函数使用对象的内存地址作为对象的id值, 所以两个对象的id值是相同的。
综上, 对象的id值仅仅在对象的生命周期内唯一. 在对象被销毁之后, 或被创建之前, 其他对象可以具有相同的id值。
那为什么 is 操作的结果为 False 呢? 让我们看看这段代码。

class WTF(object):
  def __init__(self): print("I")
  def __del__(self): print("D")
>>> WTF() is WTF()
I
I
D
D
False
>>> id(WTF()) == id(WTF())
I
D
I
D
True
     正如你所看到的, 对象销毁的顺序是造成所有不同之处的原因

• 三引号(三个单引号或三双引号)可以实现长字符串跨多行
• 运算符:
• /:除法,会取到小数点
• //:除法,只取整数部分
• 在控制台操作中,未命名的可以用‘’代替,默认赋值给
• 需要在可迭代对象中分解出n个元素,但这个对象的长度可能超过n,这时候使用 *表达式可用在首,中间,尾,是一个列表;
• 测试程序性能:
• 轻量级:linux下可以使用 time python3 somepragram.py
• 重量级:需要详细报告,可以使用cProfile模块
• 介于这两者之间:写装饰器包装函数
• 使用上下文管理器?——cookbook14.13

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值