Python 基础

303 阅读15分钟

Python 基础

字符串编码
最早的计算机设计采用8个bit 等于1byte,一个字节表示的最大的整数是255。最早只有127个字符被编码到计算机里(包括大小写英文字母、数字和一些符号,这个被称为ASCII编码)。
处理中文需要两个字节,并且不能合ASCII编码冲突,所以中国制定了GB2312,用来把中文编进去。
Unicode把所有语言都统一到一套编码里,就解决了乱码问题,ASCII编码是1个字节,而Unicode编码通常是两个字节。 为了优化存储空间,出现了把Unicode编码转化成“可变长编码”的UTF-8编码,UTF-8编码把一个Unicode字符根据不同的数字大小编码成1-6个字节,常用的英文字母被编码成1个字节,汉子通常是3个字节,UTF-8编码能节省空间。
在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换成UTF-8编码。用记事本编辑时,从文件读取UTF-8字符被转换成Unicode字符到内存中,编辑完成后,保存时再把Unicode转换成UTF-8保存文件。

使用list 和tuple
list是python中内置的一种数据类型,是有序的集合,可以随时添加(append) 和删除(pop)其中的元素。并且list里面可以放不同的数据类型,list的元素也可以是另一个list类型。
tuple也是一种有序列表,但是tuple一旦初始化就不能修改,没有添加和删除操作。值得注意的是Python在显示只有一个元素的tuple时,必须加一个逗号来消除歧义(t=(1,)

高阶特性

一.切片
L = ['aaa', 'bbb', 'ccc', 'ddd', 'fff']
L[1:3]表示从索引0开始取,直到索引3但不包括3为止。 L[-1] 表示取倒数第一个元素。

二.迭代
对list或tuple循环遍历称为迭代(Iteration),如何判断一个对象是可迭代对象呢,方法是通过collections模块中的Iteration类型判断。

from collections import Iterable
print('abc  %s ' % isinstance('abc', Iterable))

如果想要实现下标循环的话,使用Python内置的enumerate函数可以把一个list变成一个索引-元素对。

三.列表生成式

L = [x * x for x in range(1, 11) if x % 2 == 0] 
# os.listdir 可以列出文件和目录
import os
dirs = [d for d in os.listdir()]
[s.lower() for s in dirs] #把字符串变成小写

四.生成器
在Python中,一边循环一边计算的机制被称为生成器generator。

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

与list不同的是打印list的每一个元素的方式不同,需要通过next() 函数获得generator的返回值。
生成器不但可以作用于for循环,还可以被next() 函数不断调用并返回下一个值,知道最后抛出StopIteration错误无法继续返回下一个值。被next() 函数调用并不断返回下一个值的对象称为迭代器:Iterator,可以用isinstance()判断一个对象是否是Iterator对象。
生成器都是Iterator对象,但list、dict、str虽然是Iterable,但不是Iterator,Iterator表示一个惰性计算的序列。

函数式编程

一.高阶函数
Python 内建了map() 和reduce() 函数。map函数接收两个参数,一个函数是Iterator,map将传入的函数依次作用到序列的每一个元素并把结果作为新的Iterator返回。

from functools import reduce
def f(x):
  return x * x

arr = map(f, [1, 2, 3, 4]) 
print('arr_map = %s' % list(arr)) 

def add(x, y):
  return x + y

arr1 = reduce(add, [1, 2, 3, 4])
print('arr_reduce = %s' % arr1)

reduce是把一个函数作用在一个序列上,同样接收两个参数,reduce把结果继续和序列的下一个元素做累计计算,返回一个累计计算的结果。
filter用于过滤序列,filter把传入的函数依次作用于每一个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。
sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序。

二.函数作为返回值
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。

def lazy_sum(*args):
      def sum():
            ax = 0
           for n in args:
                 ax = ax + n
          return ax
    return sum
print(lazy_sum(1, 2, 4))

在这个例子中,函数lazy_sum中定义了函数sum,并且函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回值是函数sum时,相关参数和变量都保存在返回的函数中,这种称为闭包(Closure)。
闭包 返回的函数在其定义内部引用了局部变量args,所以当一个函数返回一个函数后,其内部的局部变量还被新函数引用。

三.装饰器
在代码运行期间动态增加功能的方式,称为装饰器(Decorator)。

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

#把@log放到now()函数定义处,相当于执行了语句     now=log(now)
 @log
 def now():
        print('2018-10-10')
        
 print('%s' % now())

由于log()是一个decorator,返回一个函数,所以原来的now() 函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新的函数,即在log()函数中返回的wrapper()函数。

模块

mac是自带安装pip ,若要安装第三方模块的话,pip install xxx.

面向对象编程

一.访问限制
如果不想外部访问class的属性,需要在属性的名称前加上两个下划线__,在Python中实例的变量名如果以双下划线开头,就变成了私有属性。
在Python中,变量名以双下划线开头并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量。

class Person(object):
"""docstring for Person"""
def __init__(self, name, age):
	super(Person, self).__init__()
	self.__name = name
	self.age = age

def print_info(self):
	print('name=%s, age=%s' % (self.__name, self.age))
	
p1 = Person('liuting', 23)
p1.print_info()	
p1.__name = "cathy2"

print('1-----age=%s' % (p1.age))
print('2-----name=%s' % p1._Person__name)

print('3-----__name=%s' % p1.__name)     

以双划线开头的实例变量也是可以从外部访问的,不能直接访问 __ name是因为Python解释器对外把__ name变量改成了_ Person __ name,所以仍然可以通过_ Person __ name来访问 __ name变量。 虽然在外部成功地设置了__ name变量,但实际上这个__name变量和class内部的__name变量不是同一个变量了,内部的变量如上面所述已经被Python解释器自动改写了,而外部代码给p1这个实例新增了一个__ name的变量。

二.使用__ slots __
创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的特性。

from types import MethodType
class Student(object):
   """docstring for Student"""
   def __init__(self):
	   super(Student, self).__init__()


 s = Student()
 def set_age(self, age):
     self.age = age


#给所有class都绑定方法
def set_name(self, name):
    self.name = name

 Student.set_name = set_name

#给实例变量绑定方法
s.set_age = MethodType(set_age, s)

s.set_age(25)
s.set_name('liuting')
print(s.age, s.name)

使用slots可以限定对class实例添加属性,只有放到slots里面的属性才能被绑定。

__slots__ = ('name', 'age')

三.使用@property
@property 主要是在属性设置的时候对属性进行检查,一下方式设置的属性读写属性,当然也可以定义只读属性。

class Student(object):

@property
def score(self):
    return self._score

@score.setter
def score(self, value):
    self._score = value
# 定制类 str返回用户看到的字符串,默认情况下返回的是 __repr__() 返回程序开发者看到的字符串,是为调试服务的。
def __str__(self):
    return 'Student oject (score: %s)' % self._score

s = Student()
s.score = 60
print(s.score)

四.多重继承
在设计类的继承关系中,通常,主线都是单一继承,但若想混入额外的功能,通过多重继承就可以实现,这种设计通常称之为MixIn。
定制类

__ str __ 和 __ repr __ ()
__ iter __ ()方法返回的是一个迭代对象,Python的for循环就会不断调用该迭代对象的__ next __ ()方法拿到循环的下一个值,知道遇到StopIteration错误时推出循环。
Fib 实例虽然能作用于for循环,但跟list不同的是不能像list那样直接取出list[0]元素来,需要实现 __ getitem __ 方法

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 > 100: # 退出循环的条件
        raise StopIteration()
    return self.a # 返回下一个值

def __getitem__(self, n):
    a, b = 1, 1
    for x in range(n):
        a, b = b, a + b
    return a

五.使用枚举类
unique可以保证枚举的value不会重复

from enum import Enum, unique

@unique
class WeekDay(Enum):
        Sun = 0
        Mon = 1
        Tue = 2
        Wed = 3
        Thu = 4
        Fri = 5
        Sat = 6

 for name, member in WeekDay.__members__.items():
          print(name, '=>', member, "=>", member.value)

六.使用元类

def fn(self):
print('hi, %s' % self)

Hello = type('Hello', (object,), dict(hello=fn))
h = Hello()
h.hello()

通过type()的方式创建class,需要传入三个参数: 1.class的名称; 2.继承的父类的集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法; 3.class的方法名称与函数绑定,函数fn绑定到方法名hello方法上。

metaclass (元类) 除了使用type()动态创建类以外,要控制类的创建行为,还可以使用metaclass。 metaclass允许创建类或者修改类,即可以把类看成是metaclass创建出来的实例。

# metaclass 是类的模板,所以必须从type类型派生
class ListMetaclass(type):

    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

class MyList(list, metaclass=ListMetaclass):
         pass

 l = MyList()
 l.add(1)
 print(l)

创建class的时候,传入metaclass参数,它指示Python解释器在创建MyList时,需要通过ListMetaclass的__ new __ 来创建。
__ new __ ()方法接收的参数依次是: 1.当前准备创建的类的对象; 2.类的名字; 3.类继承的父类的集合; 4.类的方法集合。

ORM全称“Object Relational Mapping”,即对象映射,就是把关系数据库的一行映射为一个对相关,也就是一个类对应的一个表,这样写代码更简单,不用直接操作SQL语句。
编写一个一个ORM框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。

错误、调试和测试

一.异常

一般语言都会内置一套try...except...finally 的错误处理机制。

try:
  r = 10 / int('w');
  print('r: ' , r)
    #except ZeroDivisionError as e:
 except ValueError as e:
   print('except:', e)
finally:
  print('finally')

Python的错误其实也是class,所有的错误类型都继承自BaseException,值得注意的是,它不但捕获该类型的错误,还会截获子类的错误。如果错误没有被捕获,它会一直往上抛,最后被Python解释器捕获,打印一个错误的信息,然后程序退出。

import logging
def foo(s):
      return 10 / int(s)
def bar(s):
      return foo(s) * 2
def main():
    try:
	  bar('0')
     except Exception as e:
	   logging.exception(e)
	   
  main()

Python内置的logging模块可以非常容易地记录错误信息,并且还可以把错误记录到日志文件里。

二.调试
pdb是python的调试器,让程序以单步方式运行,可以随时查看运行的状态。
-m pdb 启动pdb调试器
l 查看代码
n 单步执行代码
p 变量名 查看变量
q 结束调试
c 继续运行代码

pdb.set _ trace()
这个方法不需要单步执行,只需要import pdb ,然后在可能出错的地方放一个pdb.set _ trace(),就可以设置断点了,执行python xxx.py,程序会自动在pdb.set _ trace()暂定并进入pdb调试环境。

IO编程

一.文件读写
读写问价是最常见的IO操作,Python内置了读写问价的函数,用法和C是兼容的。
os模块
os.name 操作系统的类型
os.uname 获取系统详细的系统的信息
os.environ 环境变量
os.path 操作文件和目录
os.path.abspath('.') 当前目录的绝对路径
os.path.join('/Users/xxx/testdir') 拼接路径
os.mkdir('path') 创建目录
os.rmdir('path') 删除目录
os.path.split('/Users/xxx/testdir') 拆分路径很文件

二.序列化
Python提供了pickle模块来实现序列化,pickle.dumps()和pickle.dump()直接把对象序列化后写入一个文件中。

import pickle
d = dict(name='Bob', age=20, score=88)
f = open('/Users/Cathy/Desktop/test.txt', 'wb')
pickle.dump(d, f)
# 反序列化出对象
d = pickle.load(f)

f.close() 

Python中内置了json模块提供了非常完善的Python对象到Json格式的转换。

import json

class Student(object):
     def __init__(self, name, age, score):
           self.name = name
           self.age = age
           self.score = score

s = Student('Bob', 20, 88)
#将任意一个对象变成可序列为JSON的对象
print(json.dumps(s, default=lambda obj:obj.__dict__))  

进程和线程

Unix/Linux 操作系统提供了一个fork() 系统调用,fork()调用一次,返回两次,因为操作系统自动把当前进程复制一份(称为子进程),然后分别在父进程和子进程返回。

多任务可以由多进程完成,也可以由一个进程的多线程完成。Python的标准库提供了两个模块:_ thread 和threading ,threading是基于_ thread封装的,一般都使用threading。

Lock
多线程和多进程最大的区别在于,多个进程中,同一个变量,各自有一份拷贝存储在每一个进程中, 互不影响。而线程中,所有变量都有。
当多个线程同时执行lock.acquire时,只有一个线程可以获取锁,然后继续执行代码,其他线程就继续等待直到获取到锁为止。不同的线程可能会持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁。

import threading
balance = 0

def chane_it(n):
      global balance
      balance = balance + n
      balance = balance - n

lock = threading.Lock()
def run_thread(n):

    for i in range(10000000):
         lock.acquire()
         try:
             chane_it(i)
         finally:
             lock.release()

t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))

t1.start()
t2.start()

t1.join()
t2.join()

print(balance)

Python提供了 ThreadLocal 变量,它本身是一个全局变量,但是每一个线程都可以利用它来保存属于自己私有数据。 ThreadLocal最常用的地方就是为每一个线程绑定一个数据库连接,HTTP请求,用户身份信息等。

import threading
local_shcool = threading.local()
def process_student():
     std = local_shcool.student
     print('hello: %s (in %s)' % (std,     threading.current_thread().name))

def process_thread(name):
      local_shcool.student = name
      process_student()


t1 = threading.Thread(target=process_thread, args=('AAA', ), name='Thread——A')
t2 = threading.Thread(target=process_thread, args=('BBB', ), name='Thread——B')

t1.start()
t2.start()

t1.join()
t2.join()

常用的内建模块

collections 是Python内建的一个集合模块,提供了许多有用的集合类。
namedtuple 是一个函数,它用来创建一个自定义的tuple对象,并且规定tuple元素的个数。

from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(1, 2)
print(p)

deque 使用list存储数据时,按索引访问元素很快,但插入和删除元素就很慢,list是线性存储,数据量的时候插入和删除效率很低。deque是为了搞笑实现插入和删除操作的双向列表,适合用于队列和栈。

from collections import deque
q = deque(['a', 'b', 'c'])
q.append('x')
q.appendleft('y')
print(q)

q.popleft()
print(q)

defaultdict 提供了如果要访问的dict的key不存在的时候,返回一个默认的值。

from collections import defaultdict
d = defaultdict(lambda :'N/A')
print(d['key'])

OrderedDict 可以保证key的顺序,OrderedDict的key会按照插入的顺序排列,不是key本身排序。OrderedDict可以实现一个FIFO的dict,当容量超出限制时,先删除最早添加的key。

from collections import defaultdict
d = defaultdict(lambda :'N/A')
print(d['key'])

ChainMap 可以把一组dict串起来并组成一个逻辑上的dict,ChainMap本身也是一个dict,查找的时候会按照顺序在内部的dict一次查找。

Counter 是一个简单的计数器,比如统计字符出现的个数。

协程

一.协程 协程又称微线程,子程序或者称为函数,在所有语言中都是层级调用,子程序调用是通过栈实现的,一个线程就是执行一个子程序,只有一个调用入口,协程看上去也是一个子程序,但执行过程中,在子程序内部可中断,转而执行别的程序,在适当的时候再返回到接着执行。
协程最大的优势是执行效率很高,因为子程序不是线程切换,而是由程序自身控制的,因此没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势越明显。
第二个优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态。

Python对协程的支持通过generator实现的,在generator中,我们不但可以通过for循环来实现迭代,还可以不断调用next() 函数获取yield语句返回下一个值。

二.asynclo
asynclo 是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。asynclo的编程模型是一个消息循环,我们从asynclo中直接获取EventLoop的引用,然后把需要执行的协程扔到EventLoop中,就实现了异步IO。

import asyncio
import threading

@asyncio.coroutine
def hello():
     print("hello world  ----0 %s" %threading.currentThread())
     r = yield from asyncio.sleep(5)
     print('hello ----1 %s' %threading.currentThread())

loop = asyncio.get_event_loop()
tasks = [hello(), hello()]

loop.run_until_complete(asyncio.wait(tasks))
loop.close()   

@asyncio.coroutine 把一个generator标记为coroutine类型。 yield from 语法可以方便的调用另一个generator,asyncio.sleep也是一个coroutine,所以线程不会等待sleep,而是直接中断并执行下一个 消息循环。当sleep()返回的时候,线程可以从field from中拿到返回值(None)然后执行吓一跳语句。

控制台打印结果:

hello world  ----0 <_MainThread(MainThread, started 140736051241856)>
hello world  ----0 <_MainThread(MainThread, started 140736051241856)>
hello ----1 <_MainThread(MainThread, started 140736051241856)>
hello ----1 <_MainThread(MainThread, started 140736051241856)>

由上面可以看出两个coroutine是由同一个线程并发执行的。

三.async/await
在Python3.5开始引入新的语法 async和await ,可以让coroutine的代码更简洁

async def hello():
print("hello world  ----0 %s" % threading.currentThread())
r = await asyncio.sleep(2)
print('hello ----1 %s' % threading.currentThread())