机器学习基础之Python总结

第一篇博客是关于Python 3.0的一些总结,主要参考了廖雪峰和Python基础教程的一些内容,工欲兴其事,必先利其器,目前,主流的深度学习框架都提供了Python接口,所以,对Python的熟悉和掌握可以算是机器学习基础中的基础了。

python基础

字符串和编码

字符编码

  • python3中,字符串以Unicode编码
  • 由于Python的字符串类型是str,在内存中以Unicode表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把str变为以字节为单位的bytes
  • 要注意区分'ABC'b'ABC',前者是str,后者虽然内容显示得和前者一样,但bytes的每个字符都只占用一个字节。
  • enter image description here
  • 文本总是Unicode,由str类型表示,二进制数据则由bytes类型表示。Python3不会以任意隐式的方式混用str和bytes,正是这使得两者的区分特别清晰。你不能拼接字符串和字节包,也无法在字节包里搜索字符串(反之亦然),也不能将字符串传入参数为字节包的函数(反之亦然)。

数据结构

  • tuple所谓的“不变”是说,tuple的每个元素,指向永远不变。即指向'a',就不能改成指向'b',指向一个list,就不能改成指向其他对象,但指向的这个list本身是可变的

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

  • >>> a = 'abc'
    >>> b = a.replace('a', 'A')
    >>> b
    'Abc'
    >>> a
    'abc'

    当我们调用a.replace('a', 'A')时,实际上调用方法replace是作用在字符串对象'abc'上的,而这个方法虽然名字叫replace,但却没有改变字符串'abc'的内容。* replace方法创建了一个新字符串'Abc'并返回 ,如果我们用变量b指向该新字符串,就容易理解了,变量a仍指向原有的字符串'abc',但变量b却指向新字符串'Abc'

函数

普通函数

  • 在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值,所以,Python的函数返回多值其实就是返回一个tuple
  • 数据类型检查可以用内置函数isinstance()实现
  • 定义默认参数要牢记一点:默认参数必须指向不变对象!
  • 关键字参数**para,可以扩展函数的功能。比如,在person函数里,我们保证能接收到nameage这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。

迭代

  • Python的for循环抽象程度要高于C的for循环,因为Python的for循环不仅可以用在list或tuple上,还可以作用在其他可迭代对象上。
  • for循环其实可以同时使用两个甚至多个变量

生成器

  • 在Python中,这种一边循环一边计算的机制,称为生成器:generator。

  • 生成器的创建:

    1. 列表生成式的[]改成(),就创建了一个generator
    2. 这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator
  • 生成器的引用:

    1. generator保存的是算法,每次调用next(g)g.next(),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
    2. for 循环
  • generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

  • 但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIterationvalue

    except StopIteration as e:
      print('Generator return value:', e.value)
  • 普通函数调用直接返回结果;generator函数的“调用”实际返回一个generator对象

迭代器

  1. 凡是可作用于for循环的对象都是Iterable类型;isinstance(obj,iterable)
  2. 凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
  3. 集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

高阶函数

map/reduce
  1. map()函数接收两个参数,一个是函数,一个是Iterablemap将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。

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

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

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

sorted

sorted(list,key,reverse)函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。

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

返回函数

def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum

我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。

匿名函数
>>> f = lambda x: x * x
>>> f
<function <lambda> at 0x101c6ef28>
>>> f(5)
25

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

装饰器
  1. 在函数调用后增强函数的功能但又不希望修改原先函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。

  2. def log(func):
       def wrapper(*args, **kw):
           print('call %s():' % func.__name__)
           return func(*args, **kw)
       return wrapper

    log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处

  3. def log(text):
       def decorator(func):
           def wrapper(*args, **kw):
               print('%s %s():' % (text, func.__name__))
               return func(*args, **kw)
           return wrapper
       return decorator
    如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。
    

面向对象编程OOP

  1. OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
    • 面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。
    • 而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。

类和实例

  • 类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;
  • 方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;
  • 通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。
  • 和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同

访问限制

  • 如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问;不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量
  • 以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的

实例属性和类属性

  • 实例属性属于各个实例所有,互不干扰;
  • 类属性属于类所有,所有实例共享一个属性;
  • 不要对实例属性和类属性使用相同的名字,否则将产生难以发现的错误。

高级编程

使用slots

class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

类属性方法

Python内置的@property装饰器就是负责把一个方法变成属性调用的

@property的实现比较复杂,我们先考察如何使用。把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值

定制类

iter
  • 如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
  • 实例本身就是迭代器,故返回自己return self
class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1 # 初始化两个计数器a,b

    def __iter__(self):
        return self # 实例本身就是迭代对象,故返回自己

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b # 计算下一个值
        if self.a > 100000: # 退出循环的条件
            raise StopIteration()
        return self.a # 返回下一个值

getattr

  • 当我们调用类的方法或属性时,如果不存在,就会报错。

  • 写一个__getattr__()方法,动态返回一个属性,返回函数也是完全可以的

    class Student(object):
    
      def __getattr__(self, attr):
          if attr=='age':
              return lambda: 25
  • 只有在没有找到属性的情况下,才调用__getattr__,已有的属性,比如name,不会在__getattr__中查找。

call

  • 一个对象实例可以有自己的属性和方法,当我们调用实例方法时,我们用instance.method()来调用;

  • 我们也可以在实例本身上调用属性

    class Student(object):
      def __init__(self, name):
          self.name = name
    
      def __call__(self):
          print('My name is %s.' % self.name)
    >>> s = Student('Michael')
    >>> s() # self参数不要传入
    My name is Michael.
  • __call__()还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样

使用元类

动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。

异常和调试

错误

try

  • 当我们认为某些代码可能会出错时,就可以用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except语句块,执行完except后,如果有finally语句块,则执行finally语句块

调用栈

$ python3 err.py
Traceback (most recent call last):   # 告诉我们这是错误的跟踪信息。
  File "err.py", line 11, in <module>#调用main()出错了,在代码文件err.py的第11行代码,但原因是第9行
    main()
  File "err.py", line 9, in main #调用bar('0')出错了,在代码文件err.py的第9行代码,但原因是第6行:
    bar('0')
  File "err.py", line 6, in bar #原因是return foo(s) * 2这个语句出错了,但这还不是最终原因
    return foo(s) * 2
  File "err.py", line 3, in foo #原因是return 10 / int(s)这个语句出错了,这是错误产生的源头
    return 10 / int(s)
ZeroDivisionError: division by zero #错误类型ZeroDivisionError

抛出错误

  • 如果要抛出错误,首先根据需要,可以定义一个错误的class,选择好继承关系,然后,用raise语句抛出一个错误的实例

调试

I/O编程

序列化

  1. 原因

    • 在程序运行的过程中,所有的变量都是在内存中
    • 可以随时修改变量,但是一旦程序结束,变量所占用的内存就被操作系统全部回收。
  2. 我们把变量从内存中变成可存储或传输的过程称之为序列化,把变量内容从序列化的对象重新读到内存里称之为反序列化。

  3. Python提供了pickle模块来实现序列化。

    • pickle.dumps()方法把任意对象序列化成一个bytes,然后,就可以把这个bytes写入文件。或者用另一个方法pickle.dump()`直接把对象序列化后写入一个file-like Object:
    >>> import pickle
    >>> d = dict(name='Bob', age=20, score=88)
    >>> pickle.dumps(d)
    b'\x80\x03}q\x00(X\x03\x00\x00\x00ageq\x01K\x14X\x05\x00\x00\x00scoreq\x02KXX\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00Bobq\x04u.'
    
    >>> f = open('dump.txt', 'wb')
    >>> pickle.dump(d, f)
    >>> f.close()
    • 当我们要把对象从磁盘读到内存时,可以先把内容读到一个bytes,然后用pickle.loads()方法反序列化出对象,也可以直接用pickle.load()方法从一个file-like Object中直接反序列化出对象。

python高级编程

进程和线程

概念

  • 对于操作系统来说,一个任务就是一个进程(Process);在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。
  • 多任务的实现有3种方式:
    • 多进程模式;
    • 多线程模式;
    • 多进程+多线程模式。

多进程

系统调用
  1. Unix/Linux操作系统提供了一个fork()系统调用,它非常特殊。普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。

    1. 子进程永远返回0,而父进程返回子进程的ID。
    2. 一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。
  2. Python的os模块封装了常见的系统调用

    import os
    
    print('Process (%s) start...' % os.getpid())
    
    # Only works on Unix/Linux/Mac:
    
    pid = os.fork()
    if pid == 0:
       print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))
    else:
       print('I (%s) just created a child process (%s).' % (os.getpid(), pid))
    multiprocessing

multiprocessing模块提供了一个Process类来代表一个进程对象,创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动,这样创建进程比fork()还要简单。join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。

from multiprocessing import Process
import os

# 子进程要执行的代码
def run_proc(name):
    print('Run child process %s (%s)...' % (name, os.getpid()))

if __name__=='__main__':
    print('Parent process %s.' % os.getpid())
    p = Process(target=run_proc, args=('test',))
    print('Child process will start.')
    p.start()
    p.join()
    print('Child process end.')
pool
    p = Pool(4)
    for i in range(5):
        p.apply_async(long_time_task, args=(i,))
    print('Waiting for all subprocesses done...')
    p.close()
    p.join()

Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process

进程间通信

Python的multiprocessing模块包装了底层的机制,提供了QueuePipes等多种方式来交换数据。

  1. multiprocessing.Queue()

以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据:

multiprcessing.Queue.put() 为 入队操作

multiprcessing.Queue.get() 为 出队操作

  1. multiprocessing.Pipe()

    • Pipe()函数返回一对由管道连接的连接对象,默认情况下是双工(双向)。
    • Pipe()返回的两个连接对象代表管道的两端。 每个连接对象都有send()和recv()方法(等等)。 请注意,如果两个进程(或线程)尝试同时读取或写入管道的同一端,管道中的数据可能会损坏。 当然,同时使用管道不同端的过程也不会有风险。
    • 返回表示管道末端的一对Connection(conn1,conn2)对象。
    • 如果duplex为True(默认),则管道是双向的。
    • 如果duplex是False,那么管道是单向的:conn1只能用于接收消息,conn2只能用于发送消息。

多线程

threading

启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行

t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
lock

多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。

  1. 创建一个锁就是通过threading.Lock()来实现
  2. 由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁
  3. 当多个线程同时执行lock.acquire()时,只有一个线程能成功地获取锁,然后继续执行代码,其他线程就继续等待直到获得锁为止。

常用內建模块

virtualenv

virtualenv就是用来为一个应用创建一套“隔离”的Python运行环境。

  1. 我们用pip安装virtualenv

  2. 创建一套独立的python运行环境

    1. 创建目录

    2. 创建一个独立的Python运行环境

      virtualenv (--no-site-packages(不复制所有第三方包))  dirname
    3. 进入该环境中source dirname/bin/acivate,此时,pip安装的包全部在dirname这个环境下

    4. deactivate退出当前环境

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值