python note

python liao文档

list

一种有序的集合,可以随时添加和删除其中的元素
    classmates = ['Michael', 'Bob', 'Tracy']
     print classmates

        ['Michael', 'Bob', 'Tracy'] 
    用len()函数就可以获得list元素的个数, 用索引访问list中的每一个位置的元素,如果要取最后一个元素,除了计算索引位置外,还可以用-1做索引,直接获取最后一个元素:## classmates[-1]
    或者倒数第二个classmates[-2]...以此类推
list是一个可变的有序表,所以,可以往list中追加元素到末尾:
    classmates.append("saf")
可以把元素插入到指定的位置,比如索引号为1的位置:
    classmates.insert(1, 'Jazck')
要删除list末尾的元素,用pop()方法:
    classmates.pop()
要删除指定位置的元素,用pop(i)方法,其中i是索引位置:
    classmates.pop(1)
要把某个元素替换成别的元素,可以直接赋值给对应的索引位置:
list里面的元素的数据类型也可以不同,参杂各种类型
list元素也可以是另一个list,比如:
    s = ['python', 'java', ['asp', 'php'], 'scheme']
或者p = ['asp', 'php']
    s = ['python', 'java', p, 'scheme']
如果一个list中一个元素也没有,就是一个空的list,它的长度为0:

tuple

有序列表 --元组
与list十分类似,但是一旦初始化就不得修改,
    classmates = ('Michael', 'Bob', 'Tracy')
没有append(),insert()这类的方法,
其他获取元素的方法和list是一样的,你可以正常地使用classmates[0],classmates[-1],但不能赋值成另外的元素。
但是,要定义一个只有1个元素的tuple,不能这样定义:
    t = (1)
而应该:
    t = (1,)
而可以通过在tuple里添加list元素,使得tuple"可改变",(改变list元素,而tuple元素实质上并没有改变),

input

s = input('num :')
if s >1:
    print('s >1')
程序以报错告终,因为input返回的是str,不能与int比较。故而使用之前要进行强制类型转换

循环

1. for in 循环
    for x in [1,2,3,4]:
        sum +=x

    python提供range()函数,在使用list()函数将其转换为list
2.while循环
    ...break,continue

dict

就是c++中的map,使用键值存储,dict用{}表示
    d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
    d = dict(name = 'string')
Python字典还有一种初始化方式,就是使用字典的fromkeys方法可以从列表中获取元素作为键并用None或fromkeys方法的第二个参数初始化
d = {}.fromkeys([1,2,3,4,5],'all this string')

把数据放入dict的方法,除了初始化时指定外,还可以通过key放入:
    d['Adam'] = 67
由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉
Python字典的update方法也可以更新和添加字典,update方法使用一个字典更新字典,
查找时 如果key不存在,dict就会报错,为了避免报错,可以如下:
    in 判断key是否存在
        'Thomas' in d
    或者通过dict提供的get()方法,第二个参数是指定的不存在的返回值
        d.get('Thomas', -1)  -1可以不设置

要删除一个key,用pop(key)方法,对应的value也会从dict中删除:
可以调用Python内置关键字del来删除一个键值
    del info['name']
要保证hash的正确性,作为key的对象就不能变
而list是可变的,就不能作为key

set

不存储value的类似dict的东西
元素unique
通过add(key)方法可以添加元素到set中,可以重复添加,但不会有效果
通过remove(key)方法可以删除元素
同样不可以放入可变对象,因为无法判断两个可变对象是否相等,也就无法保证set内部“不会有重复元素”。

不可变对象

list是可变对象,对list进行操作改变list内部的value
str是不可变对象,str有一个replace()方法,
    a = 'abc'       #a是变量,'abc'是str
    a.replace('a', 'A')     #当调用a.replace(),实际上调用了函数是作用在字符串对象'abc'上的,且并没有改变变量的值
    b = a.replace('a', 'A')   #replace方法创建了一个新的字符串常量'Abc'并返回,这里用b指向
所以,对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回,这样,就保证了不可变对象本身永远是不可变   的。

函数

调用函数

        调用函数的时候,如果传入的参数数量不对,会报TypeError的错误,并且Python会明确地告诉你:abs()有且仅有1个参数,但给出了两个
        数据类型转换
                int('123')

定义函数

        定义函数使用def
        空函数
                定义一个什么都不做的空函数,使用pass函数作为占位符,
                pass也可以用在if语句中
        参数检查
                数据类型检查可以用内置函数isinstance()实现:
                        if not isinstance(x, (int, float)):
                                raise TypeError('bad operand type')
                    x只允许是int 或者float
        返回多个值       
            def move(...):
                pass
                return nx, ny    #其实这是返回了一个tuple (nx,ny)
            回收返回值
                    x,y = move(...)

函数的参数

        可以使用默认参数,可变参数,关键字参数,,
默认参数
        Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。

         定义默认参数要牢记一点:默认参数必须指向不变对象!
         我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。这样在多任务下也不用担心枷锁的问题
可变参数
        就是传入的参数个数是可变的,
            def calc(*numbers):  #  在参数的前面加一个*,在函数内部,参数numbers接收到的是一个tuple,也就是说 numbers就是一个tuple
        Python允许你在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去:
            即   a = [1,2,3,4]
                calc(*a)  # 则将a中的全部元素以可变参数的形式传入到函数中
关键字参数
        关键字参数允许你传入0个    或者任意个含参数名的参数,这些关键字参数在函数内部自动组成一个dict,
        其作用在于可以扩展函数的功能,可以先组装出一个dict,然后把dict转换为关键字参数传进去,**extra 表示把extra的全部key-value用关键字参数传到函数的**kw参数里,
命名关键字参数
        用于限制关键字参数的名字,如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:
        def person(name, age, *, city, job):  #city job 已经被限定了
        def person(name, age, *args, city, job):  #args是可变参数,city job被限定。命名关键字参数可以有缺省值,从而简化调用:
        def person(name, age, *, city='Beijing', job):
        使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个*作为特殊分隔符。如果缺少*,Python解释器将无法识别位置参数和命名关键字参数:
参数组合
        但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
        def f1(a, b, c=0, *args, **kw):
        def f2(a, b, c=0, *, d, **kw):
        最神奇的是通过一个tuple和dict,你也可以调用上述函数
        args = (1, 2, 3, 4)
        kw = {'d': 99, 'x': '#'}
        f1(*args, **kw)
        所以,对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的。
    可变参数既可以直接传入     又可以先组装list或tuple再通过*args传入
    关键字参数既可以直接传入    又可以先组装dict,再通过**kw传入

递归函数

优点是定义简单,逻辑清晰。

切片

取list或者tuple的一部分元素是非常常见的操作
slice操作
    L[0:3] 取L[0] L[1] L[2]
    如果第一个索引是0,则省略0
    L[:3] 取L[0] L[1] L[2]
    L[-2:] 或者 L[-2:-1]
    可以通过切片轻松取出某一段数列。
    前10个数,每两个取一个:L[0:10:2] 2表示每两个元素取一个
    甚至什么都不写,只写[:]就可以原样复制一个list:
    tuple也是一种list,唯一区别是tuple不可变,
    字符串'xxx'也可以看成一种list,每个元素就是list里的一个元素,可以通过切片操作实现其他语言中类似substring的功能

迭代

使用for循环来遍历一个list或者tuple
Python的for循环抽象程度要高于C的for循环因为Python的for循环不仅可以用在list或tuple上,还可以作用在其他可迭代对象上。
只要是可迭代对象,无论有无下标,都可以迭代,比如dict就可以迭代:
默认情况下,dict迭代的是key。
如果要迭代value,可以用for value in d.values(),
如果要同时迭代key和value,可以用for k, v in d.items()
由于字符串也是可迭代对象,因此,也可以作用于for循环:
如何判断一个对象是可迭代对象呢?
    方法是通过collections模块的Iterable类型判断:
        from collections import Iterable
        isinstance('abc', Iterable)
enumerate:
    把一个list变成索引-元素对
    for i, value in enumerate(['A', 'B', 'C']):

        >>> for x, y in [(1, 1), (2, 4), (3, 9)]:
        ...     print(x, y)
        ...
        1 1
        2 4
        3 9

列表生成式

即List Comprehensions,Python内置的非常简单却强大的可以用来创建list的生成式。
要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]可以用list(range(1, 11)):
[x * x for x in range(1, 11)]   把要生成的元素x * x放到前面,后面跟for循环,就可以把list创建出来,
for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方
[x * x for x in range(1, 11) if x % 2 == 0]
还可以使用两层循环,可以生成全排列
    [m + n for m in 'ABC' for n in 'XYZ']
        ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
列表生成式也可以使用两个变量来生成list:

生成器

一边循环一边计算的机制  称为生成器:generate
创建list 和generate的区别仅在于最外层的[],()
可以通过next()函数获得generator的下一个返回值 next(g).
每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,
没有更多的元素时,抛出StopIteration的错误
更优雅的方法当然是使用for循环    generator也是可迭代对象
如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。比如,著名的斐波拉契数列(Fibonacci),
    赋值语句:
        a,b = b,a+b
        相当于 t = (b,a+b)
                a = t[0]
                b = t[1]
    也就意味着,在这个语句中,a的值改变不会影响此时b的值
可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。
要把fib函数变成generator,只需要把print(b)改为yield b就可以了:
    def fib(max):
        n, a, b = 0, 0, 1
        while n < max:
            yield b  #本来是print (b)
            a, b = b, a + b
            n = n + 1
        return 'done'
如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:
    最难理解的就是generator和函数的执行流程不一样 变成generator的函数:
        在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
但是用for循环调用generator时,发现拿不到generator的return语句的返回值。
如果想要拿到返回值,必须捕获StopIteration错误
    使用while循环:
        while 1:
            try:
                pass
            except StopIteration as e:
                pass
                break

迭代器

可以直接用于for循环的数据类型有以下几种:
    集合数据类型
    generator
这些可以直接作用于for循环的对象称为可迭代对象:Iterable
使用isinstance()判断一个对象是否是Iterable对象:
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
list、dict、str虽然是Iterable,却不是Iterator。
把list、dict、str等Iterable变成Iterator可以使用iter()函数:
Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。
可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
python的for循环本质上就是通过不断的next()实现的:
    对于list等Iterable,获得一个Iterator对象  iter(list)

函数式编程

函数式编程是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量, 任意一个函数,只要输入是确定的,输出就是确定的。这种纯函数称之为没有副作用,
而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!

高阶函数

函数本身也可以赋值给变量,即:变量可以指向函数。
函数名也是变量--
    函数名其实就是指向函数的变量,改变其指向,就可以改变他的行为
    由于abs函数实际上是定义在import builtins模块中的,所以要让修改abs变量的指向在其它模块也生效,要用import builtins; builtins.abs = 10。
传入函数
    接受一个函数作为参数的函数称为高阶函数
    函数式编程就是指这种高度抽象的编程范式。

map/reduce

map:
    map()函数接受两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
    由于结果是一个IteratorIterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list。
    map()作为高阶函数,事实上它把运算规则抽象了
reduce:
    reduce()函数将一个函数作用在一个序列上,参数函数必须接受两个参数,
    reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
    如果考虑到字符串str也是一个序列,对上面的例子稍加改动,配合map(),我们就可以写出把str转换为int的函数:
        >>> from functools import reduce
        >>> def fn(x, y):
        ...     return x * 10 + y
        ...
        >>> def char2num(s):
        ...     digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
        ...     return digits[s]
        ...
        >>> reduce(fn, map(char2num, '13579'))
        13579
使用lambda函数:
    ...

filter

用于过滤序列
filter()也接收一个函数和一个序列。
filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。
例如,在一个list中,删掉偶数,只保留奇数,可以这么写
把一个序列中的空字符串删掉,可以这么写:
    def not_empty(s):
        return s and isinstance(s,str) and s.strip()
关键在于正确实现一个“筛选”函数。
filter()函数返回的是一个Iterator也就是一个惰性序列
需要用list()函数获得所有结果并返回list

用filter求素数
    埃氏筛法
        用python实现,先构造一个奇数序列,然后定义一个筛选函数,最后定义一个生产器,不断返回下一个素数...

sorted

通过函数抽象得出比较的过程
python内置的sorted()函数可以对list进行排序,sorted函数还是个高阶函数,可以通过接受key函数来实现自定义的排序,例如:按绝对值大小排序:
    sorted([36, 5, -12, 9, -21], key=abs)
key指定的函数将作用于list上的每一个元素,并根据key函数返回的结果进行排序,
对于字符串排序,默认情况下,是根据ASCLL的大小进行排序的,
若要忽略大小写的话,使用自定义
sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True
自定义的key函数应该是接受一个参数的函数,

返回函数

函数作为返回值
    还可以把函数作为结果返回
    详见liao官网
    闭包(closure)
    我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
    当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:
    >>> f1 = lazy_sum(1, 3, 5, 7, 9)
    >>> f2 = lazy_sum(1, 3, 5, 7, 9)
    >>> f1==f2
    False
    f1 f2 功能相同,但不是一个对象

闭包

接着上面的返回函数,返回的函数在其定义内部引用了上层局部变量args,所以上层函数返回时,局部变量却还没有被释放,(被返回的函数调用着)
另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。我们来看一个例子:
def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f)
    return fs

f1, f2, f3 = count()

    >>> f1()
    9
    >>> f2()
    9
    >>> f3()
    9
原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9。
** 返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。 **
如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
def count():
    def f(j):
        def g():
            return j*j
        return g
    fs = []
    for i in range(1, 4):
        fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
    return fs

匿名函数

lambda表达式 有时候,不需要显示的定义函数,直接传入匿名函数更方便
在python中    ,对匿名函数提供了有限的支持
list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。
此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:f = lambda x: x * x
同样,也可以把匿名函数作为返回值返回,比如:
    def build(x, y):
        return lambda: x * x + y * y

装饰器 Decorator 语法糖 (面向切面的编程(Aspect-Oriented Programming))

函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。
函数对象有一个__name__属性,可以拿到函数的名字:
这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
decorator就是一个返回函数的高阶函数,
def log(func):
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper
观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处
    @log
    def now():
        print('2015-3-25')
这是本质上是把now这个变量指向了log(now), (now = log(now))
wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。 因为在这里wrapper()是要替代原来的now()函数接收参数的,但是他可能是now(x) 也可能是now(*x),所以我们用可以接受任意参数的....
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:
    def log(text):
        def decorator(func):
            def wrapper(*args, **kw):
                print('%s %s():' % (text, func.__name__))
                return func(*args, **kw)
            return wrapper
        return decorator
用法如下:
    @log('execute')
    def now():
        print('2015-3-25')
3层嵌套的效果是这样的:now = log('execute')(now)
首先执行log('execute'),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。
因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数的__name__等属性复制到wrapper()函数中,
不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,
在定义wrapper()的前面加上@functools.wraps(func)即可
如果是带参数的decorator 就必须要在wrapper()之前写入这句
请编写一个decorator,能在函数调用的前后打印出'begin call'和'end call'的日志。
再思考一下能否写出一个@log的decorator,使它既支持:

    @log
    def f():
        pass
又支持:

    @log('execute')
    def f():
        pass
def logall(x):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args,**kw):
            if callable(x):
                pass
            else:
                pass
            return func(*args,**kw)
        return wrapper
    if callable(x):
        return decorator(x)
    else :
        return decorator
@logall('asf')
def pp():
    pass
@logall
def ppp():
    pass

偏函数

functools模块,很多有用的功能。其中之一就是偏函数,
int()函数可以把字符串转换成整数,默认按十进制转换,如果传入base参数,就可以做N进制的转换。
假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:
    def int2(x, base=2):
        return int(x, base)
        >>> int2('1000000')
        64
        >>> int2('1010101')
        85
        functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:
    int2 = functools.partial(int, base=2)
简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
最后,创建偏函数时,实际上可以接收函数对象、*args和**kw这3个参数
这就意味着,偏函数可以任意接受参数,
当传入:
    max2 = functools.partial(max, 10)
实际上会把10作为*args的一部分自动加到左边,也就是:
    max2(5, 6, 7)
相当于:
    args = (10, 5, 6, 7)
    max(*args)

模块

为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里
在Python中,一个.py文件就称之为一个模块(Module)
使用模块还可以避免函数名和变量名冲突
为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package)。
mycompany
├─ __init__.py
├─ abc.py
└─ xyz.py
引入了包以后,只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突
abc.py模块的名字就变成了mycompany.abc
每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包
__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块,而它的模块名就是mycompany。

使用模块

如何编写一个模块:
    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-

    ' a test module '

    __author__ = 'Michael Liao'
第1行和第2行是标准注释,第1行注释可以让这个hello.py文件直接在Unix/Linux/Mac上运行,第2行注释表示.py文件本身使用标准UTF-8编码;

第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;

第6行使用__author__变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名;
argv至少有一个元素,因为第一个参数永远是该.py文件的名称,
最后,注意到这两行代码:

    if __name__=='__main__':
        test()
当我们在命令行运行hello模块文件时,Python解释器把一个特殊变量__name__置为__main__  
而如果在其他地方导入该hello模块时,if判断将失败,因此,这种if测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。
作用域
    在一个模块中,有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用
    在Python中,是通过_前缀来实现的。
    正常的函数和变量名是公开的(public),可以被直接引用,比如:abc,x123,PI等;
    类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途比如上面的__author__,__name__就是特殊变量.hello模块定义的文档注释也可以用特殊变量__doc__访问,
    类似_xxx和__xxx这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc,__abc等;
    之所以我们说,private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量。
    外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public。

面向对象编程

类和实例

class and instance,
    class Student(object):
        pass
bart = Student()
class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的
如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。
其中,变量bart指向的就是一个Student的实例
可以自由地给一个实例变量绑定属性,比如,给实例bart绑定一个name属性:
由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去 通过定义一个特殊的__init__方法,在创建实例的时候,就把name,score等属性绑上去:
    class Student(object):
        def __init__(self, name, score):
            self.name = name
            self.score = score
self不需要传,Python解释器自己会把实例变量传进去:
和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数
除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。
数据封装

访问限制

外部代码还是可以自由地修改一个实例的name、score属性:
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__
实例的变量名如果以__开头,就变成了一个私有变量
因为在方法中,可以对参数做检查,避免传入无效的参数:
变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__这样的变量名。
_name 单下划线定义的变量,约定俗成的是为私有变量
对于__name , 仍然可以通过_Student__name来访问__name变量(在__name前面加上_Student))

继承 多态

当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。
当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。
判断一个变量是否是某个类型可以用isinstance()判断:
>>> isinstance(a, list)
True
>>> isinstance(b, Animal)
True
>>> isinstance(c, Dog)
True
子类对象继承父类的数据类型,并坐拥自己本身的数据类型
多态的好处就是,当我们需要传入Dog、Cat、Tortoise……时,我们只需要接收Animal类型就可以了,因为Dog、Cat、Tortoise……都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法,这就是多态的意思:
这就是著名的“开闭”原则:
    对扩展开放:允许新增Animal子类;
    对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。
静态语言 vs 动态语言
    动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个read()方法,返回其内容。但是,许多对象,只要有read()方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了read()方法的对象。

获取对象信息

当我们拿到一个对象的引用时,如何知道这个对象是什么类型、有哪些方法呢?
使用type()
    type()函数返回对应的Class类型,
判断基本数据类型可以直接写int,str等,但如果要判断一个对象是否是函数怎么办?可以使用types模块中定义的常量:
     import types
     包括 types.FunctionType      types.BuiltinFunctionType   types.LambdaType    types.GeneratorType
使用isinstance()
    应该总是优先使用isinstance()判断类型,可以将指定类型及其子类“一网打尽”。
使用dir()
    如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法
    >>> dir('ABC')
    ['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill'] 
类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度
>>> len('ABC')
3
>>> 'ABC'.__len__()
3
我们自己写的类,如果也想用len(myObj)的话,就自己写一个__len__()方法:
配合getattr()、setattr()以及hasattr(),我们可以直接操作一个对象的状态:
 hasattr(obj, 'x') # 有属性'x'吗?
 setattr(obj, 'y', 19) # 设置一个属性'y',默认19
 getattr(obj, 'y',n = 404) # 获取属性'y',若没有y,则默认返回404
 可以传入一个default参数,如果属性不存在,就返回默认值
如果可以直接写:

sum = obj.x + obj.y
就不要写:

sum = getattr(obj, 'x') + getattr(obj, 'y')

实例属性和类属性

给实例绑定属性的方法是通过实例变量,或者通过self变量:
如果Student类本身需要绑定一个属性?可以直接在class中定义属性,这种属性是类属性,归Student类所有:
    class Student(object):
        name = 'Student'
当我们定义了一个类属性后,这个属性虽然归类所有,但类的所有实例都可以访问到。
如果实例没有name变量,则s.name访问的就是类的name,类似于c中的静态变量
由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性,
可以通过Student.name必然访问到类属性name
在类内使用类属性,应该Student.name(带上类名)

面向对象高级编程

    使用__slots__
    正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。
    可以给实例绑定一个属性,也可给实例绑定一个方法
    >>> def set_age(self, age): # 定义一个函数作为实例方法
    ...     self.age = age
    ...
    >>> from types import MethodType
    >>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
    >>> s.set_age(25) # 调用实例方法
    >>> s.age # 测试结果
    25
    同时,就像属性一样,给一个属性绑定方法,对另一个实例不起作用
    为了给所有实例都绑定方法,可以给class绑定方法
    >>> def set_score(self, score):
    ...     self.score = score
    ...
    >>> Student.set_score = set_score
    注意:若为class绑定了某函数,而函数中定义了一个实例变量,则在未调用该函数时,该实例变量未定义
    使用__slots__
    但是,如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加name和age属性。
    为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:
    class Student(object):
        __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
    未被放到__slots__中的属性,不能被绑定,试图绑定将得到AttributeError的错误。
    使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:
    除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__。
    使用@property
    在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改:
        s = Student()
        s.score = 9999
    为了限制score的范围,可以通过一个set_score()方法来设置成绩,再通过一个get_score()来获取成绩,这样,在set_score()方法里,就可以检查参数
    但是,上面编写出的调用方法又略显复杂,没有直接用属性这么直接简单。
    @property既能检查参数,又可以用类似属性这样简单的方式来访问类的变量
    还记得装饰器(decorator)可以给函数动态加上功能吗?对于类的方法,装饰器一样起作用
    Python内置的@property装饰器就是负责把一个方法变成属性调用的:
    class Student(object):

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

        @score.setter
        def score(self, value):
            if not isinstance(value, int):
                raise ValueError('score must be an integer!')
            if value < 0 or value > 100:
                raise ValueError('score must between 0 ~ 100!')
            self._score = value
    @property的实现比较复杂,我们先考察如何使用。把一个getter方法变成属性,只需要加上@property就可以了   此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:
    s.score = 60 # OK,实际转化为s.set_score(60)
    s.score = 9999
    Traceback (most recent call last):
      ...
    ValueError: score must between 0 ~ 100!
    还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性
?为何一定要写成self._xxx这样才能成功,self.xxx为什么不行呢。
    (_xxx     不能用于’from module import *’ 以单下划线开头的表示的是protected类型的变量。即保护类型只能允许其本身与子类进行访问。)
    (__xxx    双下划线的表示的是私有类型的变量。只能是允许这个类本身进行访问了。连子类也不可以)
    (__xxx___ 定义的是特列方法。像__init__之类的)

多重继承

继承  通过继承,子类就可以扩展父类的功能。
对于复杂的继承关系:
                    ┌───────────────┐
                    │    Animal     │
                    └───────────────┘
                            │
               ┌────────────┴────────────┐
               │                         │
               ▼                         ▼
        ┌─────────────┐           ┌─────────────┐
        │   Mammal    │           │    Bird     │
        └─────────────┘           └─────────────┘
               │                         │
         ┌─────┴──────┐            ┌─────┴──────┐
         │            │            │            │
         ▼            ▼            ▼            ▼
    ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐
    │  MRun   │  │  MFly   │  │  BRun   │  │  BFly   │
    └─────────┘  └─────────┘  └─────────┘  └─────────┘
         │            │            │            │
         │            │            │            │
         ▼            ▼            ▼            ▼
    ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐
    │   Dog   │  │   Bat   │  │ Ostrich │  │ Parrot  │
    └─────────┘  └─────────┘  └─────────┘  └─────────┘
要多重继承
    大类:
class Mammal(Animal):
    pass
class Bird(Animal):
    pass

class Runnable(object):
    def run(self):
        print('Running...')
class Flyable(object):
    def fly(self):
        print('Flying...')
对于需要Runnable功能的动物,就多继承一个Runnable,例如Dog:
class Dog(Mammal, Runnable):
    pass

对于需要Flyable功能的动物,就多继承一个Flyable,例如Bat:
class Bat(Mammal, Flyable):
    pass
MixIn

定制类

__str__()
只需要定义好__str__()方法,返回一个好看的字符串
>>> class Student(object):
...     def __init__(self, name):
...         self.name = name
...     def __str__(self):
...         return 'Student object (name: %s)' % self.name
...
>>> print(Student('Michael'))
Student object (name: Michael)
__repr__()
两者的区别是__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说,__repr__()是为调试服务的。
可以再定义一个__repr__()
    但是通常__str__()和__repr__()代码都是一样的,所以,有个偷懒的写法
    在定义完__str__之后 直接 __repr__ = __str__
__iter__()
如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法
该方法返回一个迭代对象
Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环
可以用这个功能 用类实现一个斐波拉契数列
感觉这个更像一个yield那个东西(迭代器)

__getitem__()
    Fib实例(__iter__)虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,比如,取第5个元素:

    要表现得像list那样按照下标取出元素,需要实现__getitem__()方法:
    class Fib(object):
        def __getitem__(self, n):
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
    但是list有个神奇的切片方法  __getitem__()传入的参数可能是一个int,也可能是一个切片对象slice,所以要做判断
        if isinstance(n, slice)
    此外,如果把对象看成dict,__getitem__()的参数也可能是一个可以作key的object,例如str。
    与之对应的是__setitem__()方法,把对象视作list或dict来对集合赋值
    最后,还有一个__delitem__()方法,用于删除某个元素。
__getattr__()
    当一个类中没有定义某个属性时,访问就会报错
    要避免这个错误,除了可以加上一个score属性外,Python还有另一个机制,那就是写一个__getattr__()方法,动态返回一个属性。
        def __getattr__(self, attr):
            if attr=='score':
                return 99
    当调用不存在的属性时,比如score,Python解释器会试图调用__getattr__(self, 'score')来尝试获得属性
    返回函数也是完全可以的:
        class Student(object):
            def __getattr__(self, attr):
                if attr=='age':
                    return lambda: 25
    这意味着调用方式的变化:
        s.age()
    注意,只有在没有找到属性的情况下,才调用__getattr__,已有的属性,比如name,不会在__getattr__中查找。
    注意到任意调用如s.abc都会返回None,这是因为我们定义的__getattr__默认返回就是None。
    要让class只响应特定的几个属性,我们就要按照约定,抛出AttributeError的错误:class Student(object):
        def __getattr__(self, attr):
            if attr=='age':
                return lambda: 25
            raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
    这实际上可以把一个类的所有属性和方法调用全部动态化处理了,不需要任何特殊手段。
    __call__()
        一个对象实例可以有自己的属性和方法,当我们调用实例方法时,我们用instance.method()来调用
            >>> s = Student('Michael')
            >>> s() # self参数不要传入
        任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用。
        class Student(object):
            def __init__(self, name):
                self.name = name

            def __call__(self):
                print('My name is %s.' % self.name)

    我们就模糊了对象和函数的界限。
    能被调用的对象就是一个Callable对象

使用枚举类

Enum可以把一组相关常量定义在一个class中,且class不可变,而且成员可以直接比较。
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
这样我们就获得了Month类型的枚举类,可以使用Month.Jan来引用一个常量,或者枚举它所有的常量
value属性则是自动赋给成员的int常量,默认从1开始计数。
@unique装饰器可以帮助我们检查保证没有重复值。
自定义的由Enum派生出来的枚举类
from enum import Enum, unique
@unique
class Weekday(Enum):
    Sun = 0 # Sun的value被设定为0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6
既可以用成员名称引用枚举常量,又可以直接根据value的值获得枚举常量。

使用元类

type()
    动态语言和静态语言最大的不同 就是函数和类的定义,不是编译时定义的,而是运行时动态创建的
    type()函数可以查看一个类型或变量的类型,
    一个class的类型就是type    一个实例的类型就是class
    而创建class的方法就是使用type()函数
    type()函数既可以返回一个对象的类型,又可以创建出新的类型
    比如,我们可以通过type()函数创建出Hello类,而无需通过class Hello(object)...的定义:
    定义一个fn函数 注意参数要符合类方法的(带着self)
    Hello = type('Hello', (object,), dict(hello=fn))
要创建一个class对象,type()函数依次传入3个参数
    class的名称;
    继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
    class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。
    Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。
metaclass
    metaclass,直译为元类,简单的解释就是
        先定义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)
        有了ListMetaclass,我们在定义类的时候还要指示使用ListMetaclass来定制类,传入关键字参数metaclass:
            class MyList(list, metaclass=ListMetaclass):
                pass

        当我们传入关键字参数metaclass时,魔术就生效了,它指示Python解释器在创建MyList时,要通过ListMetaclass.__new__()来创建,
        在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。
        __new__()方法接收到的参数依次是:

            当前准备创建的类的对象;

            类的名字;

            类继承的父类集合;

            类的方法集合。
        正常情况下,会直接在类中写入add方法,但是总会遇到需要通过metaclass修改类定义的   ORM就是一个典型的例子
        ORM全称“Object Relational Mapping”,即对象-关系映射   
        就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作SQL语句。
        要编写一个ORM框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。
        编写底层模块的第一步,就是先把调用接口写出来
        编写具体过程 见liao官网

错误调试与测试

Python的pdb可以让我们以单步方式执行代码。
编写测试也很重要
有了良好的测试,就可以在程序修改后反复运行,确保程序输出符合我们编写的测试。

错误处理

高级语言通常都内置了一套try...except...finally...的错误处理机制,Python也不例外。
try
    当我们认为某些代码可能出错时,就可以用try运行这段代码,如果执行出错,则后续代码都不会执行,而是直接跳转到错误处理代码(except语句块),执行完,如果有finally语句块,则执行该
    finally语句如果有,则一定会执行
    int()函数可能会抛出ValueError
    此外,如果没有错误发生,可以在except语句块后面加一个else,当没有错误发生时,会自动执行else语句
    Python的错误其实也是class,所有的错误类型都继承自BaseException
    同时一定要注意几个except并列时的前后关系,
    在使用except时需要注意的是,它不但捕获该类型的错误,还把其子类也“一网打尽”。
    不需要在每个可能出错的地方去捕获错误,只要在合适的层次去捕获错误就可以了
调用栈
    如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序退出。
    解读错误信息是定位错误的关键。我们从上往下可以看到整个错误的调用函数链
记录错误
    既然我们能捕获错误,就可以把错误堆栈打印出来,然后分析错误原因,同时,让程序继续执行下去。
    Python内置的logging模块可以非常容易地记录错误信息
抛出错误
    错误并不是凭空产生的,而是有意创建并抛出的
    我们自己编写的函数也可以抛出错误
    用raise语句抛出一个错误的实例
    尽量使用python内置的错误类型
    看下面一种情况
    def bar():
        try:
            foo('0')
        except ValueError as e:
            print('ValueError!')
            raise
    在bar()函数中,我们明明已经捕获了错误,但是,打印一个ValueError!后,又把错误通过raise语句抛出去了,捕获错误目的只是记录一下,便于后续追踪。
    raise语句如果不带参数,就会把当前错误原样抛出。
    此外,在except中raise一个Error,还可以把一种类型的错误转化成另一种类型:
    except ZeroDivisionError:
        raise ValueError('input error!')

调试

断言
    凡是可以用print()来辅助查看的地方,都可以用断言替代。
    def foo(s):
        n = int(s)
        assert n != 0, 'n is zero!'
        return 10 / n

    def main():
        foo('0')
    assert内的逻辑如果是false 就会触发断言,
    如果断言失败,assert语句本身就会抛出AssertionError:
    程序中如果到处充斥着assert,和print()相比也好不到哪去。不过,启动Python解释器时可以用-O参数来关闭assert
    关闭后,你可以把所有的assert语句当成pass来看。
logging 
    把print()替换为logging是第3种方式,
    logging的好处在于它允许你指定记录信息的级别,有debug,info,warning,error等几个级别
    当我们指定level=INFO时,logging.debug就不起作用了。同理,指定level=WARNING后,debug和info就不起作用了。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。
pdb
    python -m pdb err.py
    输入命令n可以单步执行代码:
    任何时候都可以输入命令p 变量名来查看变量
    输入命令q结束调试,退出程序
    pdb.set_trace()
        这个方法也是用pdb,但是不需要单步执行,我们只需要import pdb,然后,在可能出错的地方放一个pdb.set_trace(),就可以设置一个断点
    运行代码,程序会自动在pdb.set_trace()暂停并进入pdb调试环境,可以用命令p查看变量,或者用命令c继续运行

单元测试

用来对一个模块,一个函数或者一个类来进行正确性检验的测试工作
如果单元测试通过,说明我们测试的这个函数能够正常工作
单元测试通过后有什么意义呢?
    如果我们对abs()函数代码做了修改,只需要再跑一遍单元测试,如果通过,说明我们的修改不会对abs()函数原有的行为造成影响,如果测试不通过,说明我们的修改与原有行为不一致,要么修改代码,要么修改测试。

转载于:https://www.cnblogs.com/simonsqd/p/8698440.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值