跟学廖雪峰python教程新学新知

廖雪峰python教程新学新知

python基础

  1. r'' 表示''内部的字符串默认不转义。

  2. ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符。

  3. bytes类型的数据用带b前缀的单引号或双引号表示:
    x = b'ABC'

  4. encode('编码类型')方法可以编码为指定的bytes,decode('编码类型')方法将bytes转换为str

  5. decode('编码类型', errors='ignore')中,传入errors='ignore'忽略错误的字节。

  6. 保存文件时指定编码类型以及增加运行头文件。

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    
  7. 输出格式化有三种方法:

    • % 运算符 :同c语言
    >>> 'Hello, %s' % 'world'
    'Hello, world'
    >>> 'Hi, %s, you have $%d.' % ('Michael', 1000000)
    'Hi, Michael, you have $1000000.'
    
    • format函数 :传入的参数依次替换字符串内的占位符{0}、{1}……
    >>> 'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
    'Hello, 小明, 成绩提升了 17.1%'
    
    • f-string

    小明的成绩从去年的72分提升到了今年的85分,请计算小明成绩提升的百分点,并用字符串格式化显示出'xx.x%',只保留小数点后1位:

    s1 = 72
    s2 = 85
    r = (s2-s1)/s1
    print('%004.1f%%' % r)
    
  8. tuple元素不可变,但tuple中的list可变。

  9. 模式匹配可匹配复杂语句和列表。

    age = 15  
    
    match age:  
        case x if x < 10:  
            print(f'< 10 years old: {x}')  
        case 10:  
            print('10 years old.')  
        case 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18:  
            print('11~18 years old.')  
        case 19:  
            print('19 years old.')  
        case _:  
            print('not sure.')  
    
    args = ['gcc', 'hello.c', 'world.c']  
    # args = ['clean']  
    # args = ['gcc']  
    
    match args:  
        # 如果仅出现gcc,报错:  
        case ['gcc']:  
            print('gcc: missing source file(s).')  
        # 出现gcc,且至少指定了一个文件:  
        case ['gcc', file1, *files]:  
            print('gcc compile: ' + file1 + ', ' + ', '.join(files))  
        # 仅出现clean:  
        case ['clean']:  
            print('clean')  
        case _:  
            print('invalid command.')  
    
    
  10. dict创建有三种方法:d = {'a': 6,},d = dict(a=6),d={} d['a']=6

  11. .get()函数,第一个参数是key,第二个参数是key无效时返回的value,默认是None

  12. set创建: s = set([1,2,3,])

  13. set.add(), set.remove(), s1&s2, s1|s2,set是数学中的集合。

  14. eval函数将字符串转为python语句然后执行转化后的语句。

  15. -17//4的值时-5

函数

  1. hex()函数转换16进制。

  2. 函数名是一个指向函数对象的引用,可以把函数赋给一个变量。

  3. 函数返回值是一个单一值,当有多个值返回时,实际上是返回一个tuple。

  4. 默认参数必须指向不变对象。

    def add_end(L=[]):
    L.append('END')
    return L
    >>> add_end([1, 2, 3])
    [1, 2, 3, 'END']
    >>> add_end()
    ['END']
    >>> add_end()
    ['END', 'END']
    

    修改后

    def add_end(L=None):
    if L is None:
        L = []
    L.append('END')
    return L
    >>> add_end()
    ['END']
    >>> add_end()
    ['END']
    
  5. 可变参数*argv,可传入数量是任意的,在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去。

  6. 关键字参数**kw,传入的是dict内容,**dict可以将dict中内容传入函数,同时**kw获得的参数则是**dict的一份拷贝。

  7. 命名关键词参数特殊分隔符**后面的参数被视为命名关键字参数。其作用是限制关键字参数。

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

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

    def person(name, age, *args, city, job):
    print(name, age, args, city, job)
    
  8. 定义函数可以组合使用这5种参数:必选参数、默认参数、可变参数、关键字参数和命名关键字参数。
    参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

  9. 对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的。

  10. 对于递归来说,不需要展开思考的它的中间过程,这违背了递归的初心。
    按照递归的思路,只需要考虑n和n-1的关系就好,至于n-2,n-3...完全没必要考虑。

高级特性

  1. 代码越少,开发效率越高。

  2. enumerate函数可以把一个list变成 索引-元素 对。

  3. 在一个列表生成式中,for前面的if ... else是表达式(所以必须有else),而for后面的if是过滤条件,不能带else

    >>> [x for x in range(1, 11) if x % 2 == 0]
    [2, 4, 6, 8, 10]
    >>> [x if x % 2 == 0 else -x for x in range(1, 11)]
    [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10]
    
  4. 创建一个generator有两种办法:

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

      >>> L = [x * x for x in range(10)]
      >>> L
      [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
      >>> g = (x * x for x in range(10))
      >>> g
      <generator object <genexpr> at 0x1022ef630>
      
    • 如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator函数,调用一个generator函数将返回一个generator。

      def fib(max):
          n, a, b = 0, 0, 1
          while n < max:
              yield b
              a, b = b, a + b
              n = n + 1
          return 'done'
      >>> f = fib(6)
      >>> f
      <generator object fib at 0x104feaaa0>
      
  5. generator函数和普通函数的执行流程不一样。
    普通函数是顺序执行,遇到return语句或者最后一行函数语句就返回。
    而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

    def odd():
        print('step 1')
        yield 1
        print('step 2')
        yield(3)
        print('step 3')
        yield(5)
    
    >>> o = odd()
    >>> next(o)
    step 1
    1
    >>> next(o)
    step 2
    3
    >>> next(o)
    step 3
    5
    >>> next(o)
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    StopIteration
    
  6. 调用generator函数会创建一个generator对象,多次调用generator函数会创建多个相互独立的generator。

    接上
    >>> next(odd())
    step 1
    1
    >>> next(odd())
    step 1
    1
    >>> next(odd())
    step 1
    1
    
  7. 解杨辉三角,有三种解法(至少会一种,且理解第一种解法):

    def triangles():
        L = [1]
        while True:
            yield L
            L = [sum(i) for i in zip([0]+L, L+[0])]
    
    def triangles():
        ret = [1]
        while True:
            yield ret
            for i in range(1, len(ret)):
                ret[i] = pre[i] + pre[i - 1]
            ret.append(1)
            pre = ret[:]
    
    def YangHui (num = 10):
        LL = [[1]]
        for i in range(1,num):
            LL.append([(0 if j== 0 else LL[i-1][j-1])+ (0 if j ==len(LL[i-1]) else LL[i-1][j]) for j in range(i+1)])
        return LL
    
  8. 通函数调用直接返回结果:

    >>> r = abs(6)
    >>> r
    6
    

    generator函数的调用实际返回一个generator对象:

    >>> g = fib(6)
    >>> g
    <generator object fib at 0x1022ef948>
    

    一般用for循环来调用generator:

    >>> g = (x * x for x in range(10))
    >>> for n in g:
    ...     print(n)
    
    >>> for n in fib(6):
    ...     print(n)
    
  9. 可以直接作用于for循环的对象统称为可迭代对象:Iterable, 包括集合数据类型、generator
    可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator

  10. 可以用isinstance()函数判断是否是IterableIterator

  11. 生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator

  12. Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
    Iterator甚至可以表示一个无限大的数据流,如全体自然数。
    总结:

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

    • 凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;

    • 集合数据类型是Iterable但不是Iterator,可以通过iter()函数获得一个Iterator对象。

  13. zip函数将参数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表,返回值是zip对象。如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用 * 号操作符,可以将元组解压为列表。

函数式编程

  1. 一个函数可以接收另一个函数作为参数,这种函数称之为高阶函数:

    def add(x, y, f):
        return f(x) + f(y)
    
    推导过程:
    x = -5
    y = 6
    f = abs
    f(x) + f(y) ==> abs(-5) + abs(6) ==> 11
    return 11
    
  2. map(f,Iterable)函数接受两个参数,一个是函数f,一个是Iterable
    作用是将函数f作用到Iterable中的每个元素上,返回类型是Iterator

  3. reduce(g,Iterable)函数接受两个参数,一个是函数g,一个是Iterable
    作用是将函数g作用到Iterable中的每个元素上并把结果继续和序列的下一个元素做累积计算,返回类型是Iterator

    reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
    
  4. filter(h,Iterable)函数接受两个参数,一个是函数h,一个是Iterable
    作用是将函数f作用到Iterable中的每个元素上,然后根据返回值是True还是False决定保留还是丢弃该元素(true保留)。返回类型是Iteratorh=None会自动将None解释为函数(非空且非0为True)。

  5. filter()函数应用于求素数的埃氏筛法:

    def _odd_iter():
        n = 1
        while True:
            n = n + 2
            yield n
    
    def _not_divisible(n):
        return lambda x: x % n > 0
    
    def primes():
        yield 2
        it = _odd_iter() # 初始序列
        while True:
            n = next(it) # 返回序列的第一个数
            yield n
            it = filter(_not_divisible(n), it) # 构造新序列    
    
  6. sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序:

    >>> sorted([36, 5, -12, 9, -21], key=abs)
    [5, 9, -12, -21, 36]
    
  7. 返回函数,把函数作为一个函数的返回值:

    def lazy_sum(*args):
        def sum():
            ax = 0
            for n in args:
                ax = ax + n
            return ax
        return sum
    
    >>> f = lazy_sum(1, 3, 5, 7, 9)
    >>> f
    <function lazy_sum.<locals>.sum at 0x101c6ed90>
    
    >>> f()
    25
    
  8. 调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数。

  9. 闭包:一个函数的相关参数和变量都保存在返回的函数中,称为闭包结构。

  10. 返回函数不要引用任何循环变量,或者后续会发生变化的变量:

    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
    
    >>> f1, f2, f3 = count()
    >>> f1()
    1
    >>> f2()
    4
    >>> f3()
    9
    
  11. 函数内部引用并修改全局变量需要global引用。
    函数内部引用并修改父函数的局部变量需要nonlocal引用。
    使用闭包时,对外层变量赋值前,需要先使用nonlocal声明该变量不是当前函数的局部变量。

  12. 在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator):

    def log(func):
        def wrapper(*args, **kw):
            print('call %s():' % func.__name__)
            return func(*args, **kw)
        return wrapper
    
    @log
    def now():
        print('2015-3-25')
    
    >>> now()
    call now():
    2015-3-25
    
    # 等价于now = log(now)
    

    如果decorator本身需要传入参数,则:

    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')
    
    >>> now()
    execute now():
    2015-3-25
    
    # 等价于now = log('execute')(now)
    
  13. 经过decorator装饰之后的函数,它们的__name__已经从原来的'now'变成了'wrapper'
    如需还原其__name__,则:

    '''不带参数'''
    import functools
    
    def log(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('call %s():' % func.__name__)
            return func(*args, **kw)
        return wrapper
    
    '''带参数'''
    import functools
    
    def log(text):
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args, **kw):
                print('%s %s():' % (text, func.__name__))
                return func(*args, **kw)
            return wrapper
        return decorator
    
  14. functools.partial创建一个偏函数,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数:

    >>> import functools
    >>> int2 = functools.partial(int, base=2)
    >>> int2('1000000')
    64
    >>> int2('1010101')
    85
    
    # 相当于
    # kw = { 'base': 2 }
    # int('10010', **kw)
    

面向对象编程(OOP, Objcet Oriented Programming)

  1. 数据封装、继承和多态是面向对象的三大特点。

  2. 隐藏对象的内部状态,只通过对象的方法进行访问修改,称为数据封装。

  3. 方法是与实例绑定的函数,可以直接访问实例的数据。

  4. 实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。

  5. 变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量。

  6. 以一个下划线开头的实例变量名,如_name,是约定俗成的private变量,无强制限制访问。

  7. Python解释器对外把__xxx变量改成了_ClassName__xxx,所以,可以private变量也可访问,但不同版本的Python解释器可能会把__xxx改成不同的变量名。

  8. 多态允许你使用相同的接口(方法名)来执行不同类型的操作:

    class Animal:  
        def speak(self):  
            pass  
    
    class Dog(Animal):  
        def speak(self):  
            return "Woof!"  
    
    class Cat(Animal):  
        def speak(self):  
            return "Meow!"  
    
    def let_animal_speak(animal):  
        print(animal.speak())  
    
    # 创建Dog和Cat对象  
    dog = Dog()  
    cat = Cat()  
    
    # 使用相同的接口调用不同对象的方法  
    let_animal_speak(dog)  # 输出: Woof!  
    let_animal_speak(cat)  # 输出: Meow!
    
  9. “鸭子类型”:不需要严格的继承体系,只要看起来像鸭子,走起路来像鸭子,就可以被视作鸭子,例如"file-like object",只要有read()方法就可以被视为"file-like object":

    class Animal(object):
        def run(self):
            print('Animal is running...')
    
    def run_twice(animal):
        animal.run()
        animal.run()
    
    class Timer(object):
        def run(self):
            print('Start...')
    
    timer = Timer()
    # run_time(timer) is true
    
  10. types模块中有type类型常量:

    • .FunctionType: 表示用户定义的函数类型;
    • .MethodType: 表示类的方法类型;
    • .BuiltinFunctionType: 表示内建函数类型;
    • .BuiltinMethodType: 表示内建方法类型;
    • .ModuleType: 表示模块类型;
    • .LambdaType: 表示Lambda函数类型;
    • .ClassType: 在Python 2中表示旧式类类型,但在Python 3中已不存在,因为所有类都是新式类;
    • .GeneratorType: 表示生成器类型;
    • .GetSetDescriptorType: 表示获取器/设置器描述符类型;
    • .MemberDescriptorType: 表示成员描述符类型;
    • .SimpleNamespace: 表示一个简单的命名空间,类似于一个动态创建的类,可以用来存储任意数量和类型的属性。
  11. 如果要获得一个对象的所有属性和方法,可以使用dir()函数。

  12. “开闭”原则:

    • 对拓展开放:允许新增子类;
    • 对修改封闭:不需要修改依赖父类的内部函数。
  13. 调用len()函数,其内部自动去调用该对象的__len__方法。

  14. getattr()setattr()以及hasattr()可以直接操作一个对象的状态:

    >>> class MyObject(object):
    ...     def __init__(self):
    ...         self.x = 9
    ...     def power(self):
    ...         return self.x * self.x
    ...
    >>> obj = MyObject()
    
    >>> hasattr(obj, 'x')
    True
    >>> setattr(obj, 'y', 19) # 设置一个属性'y'
    >>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
    
    >>> getattr(obj, 'power') # 获取属性'power'
    <bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
    
    >>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
    >>> fn # fn指向obj.power
    <bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
    
    >>> fn() # 调用fn()与调用obj.power()是一样的
    81
    
  15. 为了统计学生人数,可以给Student类增加一个类属性,每创建一个实例,该属性自动增加:

    class Student(object):
        count = 0
    
        def __init__(self, name):
            self.name = name
            self.count += 1
    
  16. 实例会继承类属性,当实例修改继承的属性后,会覆盖继承的属性,但一旦删除修改后的属性,原来继承的属性会自动还原。

面向对象高级编程

  1. 给实例绑定方法用instance.method = MethodType(instance, method),给类或全体实例绑定方法用class

  2. Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性,一般用tuple定义允许绑定的属性名称:

    >>> class Student(object):
    >>>    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
    
    >>> s = Student() # 创建新的实例
    >>> s.name = 'Michael' # 绑定属性'name'
    >>> s.age = 25 # 绑定属性'age'
    >>> s.score = 99 # 绑定属性'score'
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    AttributeError: 'Student' object has no attribute 'score'
    
  3. __slots__定义的属性(的限定)仅对当前类实例起作用,对继承的子类无效,在子类也定义__slots__即可继承限定。

  4. 在类中定义方法前加@property前缀,把一个方法变成属性调用(可读)。
    @xxx.getter@xxx.setter前缀,可直接读写xxx属性,并且property默认自带可读属性:

    class Student(object):
    
        @property
        def birth(self):
            return self._birth
    
        @birth.setter
        def birth(self, value):
            self._birth = value
    
        @property
        def age(self):
            return 2015 - self._birth
    
  5. @property的方法名称不要与实例变量/属性重名,负责会导致无限递归栈溢出。

  6. 一个子类可继承多个父类。

  7. MixIn:一种设计模式,主要思想时通过继承多个父类,使得拥有多个功能,即优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。

  8. __str__()可print()打印出自定义内容,而__repr__()直接返回自定义内容:

    >>> class Student(object):
    ...     def __init__(self, name):
    ...         self.name = name
    ...
    >>> print(Student('Michael'))
    <__main__.Student object at 0x109afb190>
    
    >>> class Student(object):
    ...     def __init__(self, name):
    ...         self.name = name
    ...     def __str__(self):
    ...         return 'Student object (name: %s)' % self.name
    ...     __repr__ = __str__
    ...
    >>> print(Student('Michael')) # __str__的功能
    Student object (name: Michael)
    >>> Student('Michael') # __repr__的功能
    Student object (name: Michael)
    
  9. __iter__()可返回迭代对象、__next__()可迭代迭代对象,当只定义__iter__但不定义__next__将不会使该类被python解释器识别为一个迭代器:

    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 # 返回下一个值
    
    >>> for n in Fib():
    ...     print(n)
    ...
    1
    1
    2
    3
    5
    ...
    46368
    75025
    
  10. __getitem__使得类的实例可以像list一样用下表调用,但如果想用切片、step、处理负数等list本身具有的功能需要在__getitem__中判断并自己写:

    class Fib(object):
        def __getitem__(self, n):
            if isinstance(n, int): # n是索引
                a, b = 1, 1
                for x in range(n):
                    a, b = b, a + b
                return a
            '''这里便是写切片'''
            if isinstance(n, slice): # n是切片
                start = n.start
                stop = n.stop
                if start is None:
                    start = 0
                a, b = 1, 1
                L = []
                for x in range(stop):
                    if x >= start:
                        L.append(a)
                    a, b = b, a + b
                return L
    
    >>> f = Fib()
    >>> f[0: 5]
    [1, 1, 2, 3, 5]
    >>> f[: 10]
    [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
    

    对应的是__setitem__()__delitem__方法,将对象视为list或dict对集合复制或删除元素,这是鸭子类型的功劳。

  11. __getter__()动态返回属性:

    class Student(object):
        def __init__(self):
           self.name = 'Michael'
    
    >>> s = Student()
    >>> s.score
    Traceback (most recent call last):
    ...
    AttributeError: 'Student' object has no attribute 'score'
    
    class Student(object):
        def __getattr__(self, attr):
            if attr == 'score':
                return 99
    
    >>> s = Student()
    >>> s.score
    99
    >>> s.age
    None
    # 如果想让class只响应特定的几个属性,只需要抛出AttributeError即可
    class Student(object):
        def __getattr__(self, attr):
            if attr=='age':
                return lambda: 25
            raise AttributeError(f'\'Student\' object has no attribute \'{attr}\'')
    

    只有在没有找到属性的情况下才会调用__getattr__attr指的是对象的属性。
    这样就可以把一个类的所有属性和方法调用全部动态化处理了,而不需要任何特殊手段。

  12. __getattr__()实现的链式调用:

    class Chain(object):
        def __init__(self, path=''):
            self._path = path
    
        def __getattr__(self, path):
            return Chain('%s/%s' % (self._path, path))
    
        def __str__(self):
            return self._path
    
        __repr__ = __str__
    
    >>> Chain().status.user.timeline.list
    '/status/user/timeline/list'
    
  13. __call__在实例本身上调用方法(对实例进行调用):

    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  
    
  14. 可以用callable()方法判断是否是Callable对象,即是否拥有__call__()方法。

  15. enum库中有Enum类可以实现枚举:

    from enum import Enum
    
    Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
    
    for name, member in Month.__members__.items():
        print(name, '=>', member, ',', member.value)
    
    #输出:
    Jan => Month.Jan , 1
    Feb => Month.Feb , 2
    ...
    Nov => Month.Nov , 11
    Dec => Month.Dec , 12
    
  16. 可以通过Enum派生出自定义类:

    from enum import Enum, unique
    
    # unique装饰器可以帮助检测保证没有重复值(有则会报错)
    @unique
    class Weekday(Enum):
        Sun = 0 # Sun的value被设定为0
        Mon = 1
        Tue = 2
        Wed = 3
        Thu = 4
        Fri = 5
        Sat = 6
    
    # 以下是多种访问方式
    >>> day1 = Weekday.Mon
    >>> print(day1)
    Weekday.Mon
    >>> print(Weekday.Tue)
    Weekday.Tue
    >>> print(Weekday['Tue'])
    Weekday.Tue
    >>> print(Weekday.Tue.value)
    2
    >>> print(Weekday(1))
    Weekday.Mon
    
  17. Enum可以把一组相关常量定义在一个class中,且class不可变,而且成员可以直接比较。

  18. 动态语言中函数和类是运行时动态创建,而非编译时定义创建。

  19. type()函数可以返回一个对象的类型,又可以创建出新的类型:

    >>> def fn(self, name='world'): # 先定义函数
    ...     print('Hello, %s.' % name)
    ...
    >>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class,dict中是将函数fn绑定到方法名hello上
    >>> h = Hello()
    >>> h.hello()
    Hello, world.
    >>> print(type(Hello))
    <class 'type'>
    >>> print(type(h))
    <class '__main__.Hello'>
    

    type传入的三个参数依次是:class名称、继承父类的tuple,方法名与函数绑定的dict。

  20. 通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。

  21. metaclass(元类):实例根据类创建,类根据metaclass创建。

  22. 简易ORM(Object Relational Mapping,对象-关系映射)框架的实现:

    '''ORM框架的调用,在这其中Model、StringField等全部由ORM框架提供,save等方法由Model自动完成'''  
    class User(Model):
        # 定义类的属性到列的映射:
        id = IntegerField('id')
        name = StringField('username')
        email = StringField('email')
        password = StringField('password')
    
    # 创建一个实例
    u = User(id=1234, name='Michael', email='test@ww.com', password='pw1')
    # 保存到数据库
    u.save()
    
    '''编写ORM框架'''
    # 定义Field
    class Field(object):
        def __init__(self, name, column_type):
            self.name = naem
            self.column_type = column_type
    
        def __str__(self):
            return f'<{self.__class__.__name__}:{self.name}>'
    
    # 定义各种类型的Field
    class StringField(Field):
        def __init__(self, name):
            super(StringField, self).__init__(name, 'varchar(100)')
    
    class IntegerField(Field):
    
        def __init__(self, name):
            super(IntegerField, self).__init__(name, 'bigint')
    
    # 编写ModelMetaclass
    class ModelMetaclass(type):
        def __new__(cls, name, bases, attrs):
            if name == 'Model':
            print(f'Found model: {name}')
            mappings = dict()
        
            for k, v in attrs.items():
                if isinstance(v, Field):
                    print(f'Found mapping: {k} ==> {v}')
                    mappings[k] = v
            for k in mappings.keys():
                attrs.pop(k)
            attrs['__mappings__'] = mappings # 保存属性和列的映射关系
            attrs['__table__'] = name # 假设表名和类名一致
            return tpe.__new__(cls, name, bases, attrs)
    
    class Model(dict, metaclass=ModelMetaclass):
        def __init__(self, **kw):
            super().__init__(**kw)
    
        def __getattr__(self, key):
            try:
                return self[key]
            except KeyError:
                raise AttributeError(f"\'Model\' object has no attribute \'{key}\'")
    
        def __setter__(self, key, value):
            self[key] = value
    
        def save(self):
            fields = []
            params = []
            args = []
            for k, v in self.__mappings__.items():
                fields.append(v.name)
                params.append('?')
                args.append(getattr(self, k, None))
            sql = f'insert into {self.__table__} ({','.join(fields)} values ({','.join(params)}))'
            print(f'SQL: {sql}')
            print(f'ARGS: {str(args)}')
    

错误、调试和测试

  1. try...except...finally...:错误在捕获后会立刻进入except,不再继续运行try,可以有多个except...as...:,并且可以用except:...else:...

  2. 常见的错误类型与继承关系:

    ErrorRelationship

  3. 出错的时候要分析调用栈,从第一句开始分析。

  4. 可以通过标准库logging模块记录错误信息:

    # 设置日志等级和格式(这里是时间戳-级别、实际内容)
    logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')
    
    try:
        # 可能会引发异常的代码
        num = 10 / 0
    except Exception as e:
        # 使用logging.exception记录异常
        logging.exception("An error occurred")
    
    # 输出示例:  
    # 2023-04-24 12:34:56,789 - ERROR - An error occurred
    
  5. 通过logging.exception记录信息后会继续执行,并正常退出。

  6. 抛出一个错误就是捕获到该class的一个实例,错误不是凭空产生的,而是有意创建并抛出的,自己编写的函数也可以抛出错误:

    class FooError(ValueError):
        pass
    
    def foo(s):
        n = int(s)
        if n==0:
            raise FooError(f'invalid value: {s}')
        return 10 / n
    
    foo('0')
    '''
    输出:  
    FooError                                  Traceback (most recent call last)
    Cell In[25], line 10
        7         raise FooError(f'invalid value: {s}')
        8     return 10 / n
    ---> 10 foo('0')
    
    Cell In[25], line 7, in foo(s)
        5 n = int(s)
        6 if n==0:
    ----> 7     raise FooError(f'invalid value: {s}')
        8 return 10 / n
    
    FooError: invalid value: 0
    '''
    
  7. raise语句不带参数会把错误原样抛出,层层上抛,也可以raise成其他符合逻辑的错误,从而将一种类型错误转化成另外一种类型。

  8. 一般可以用assert(断言)来代替print检查,,并且在运行时可以用-O参数关闭assert:

    def foo(s):
        n = int(s)
        # 断言n != 0是true,否则抛出'n is zero!'
        assert n != 0, 'n is zero!'
        return 10 / n
    
    def main():
        foo('0')
    
    '''
    Traceback (most recent call last):
    ...
    AssertionError: n is zero!
    '''
    
  9. 可以用logging代替,同时basicConfig可以指定起作用的logging类型:

    import logging
    logging.basicConfig(level=logging.INFO)
    
    s = '0'
    n = int(s)
    logging.info('n = %d' % n)
    print(10 / n)
    '''
    $ python err.py
    INFO:root:n = 0
    Traceback (most recent call last):
    File "err.py", line 8, in <module>
        print(10 / n)
    ZeroDivisionError: division by zero
    '''
    
  10. 可以使python调试器pdb:

    • python -m pdb xxx.py启动;

    • 命令l可以查看代码;

    • 命令n可以单步执行代码;

    • 命令p 变量名可以查看变量;

    • 命令q可以结束调试,退出程序。

    • 代码中pdb.set_trace()处是断点(需提前引入pdb库);

    • 命令c继续运行;

  11. 单元测试:对一个模块、一个函数或者一个类进行正确性检验的测试工作。

  12. 单元测试举例说明:

    """mydict.py"""
    class Dict(dict):
    
        def __init__(self, **kw):
            super().__init__(**kw)
    
        def __getattr__(self, key):
            try:
                return self[key]
            except KeyError:
                raise AttributeError(r"'Dict' object has no attribute '%s'" % key)
    
        def __setattr__(self, key, value):
            self[key] = value
    
    """mydict_test.py"""
    import unittest
    
    from mydict import Dict
    
    # 从unittest.TestCase继承测试类
    class TestDict(unittest.TestCase):
    
        # 以test开头的方法就是测试方法,否则不被认为是测试方法,测试时将不予执行  
        def test_init(self):
            d = Dict(a=1, b='test')
            # 这里是常用的断言assertEqual
            self.assertEqual(d.a, 1)
            self.assertEqual(d.b, 'test')
            self.assertTrue(isinstance(d, dict))
    
        def test_key(self):
            d = Dict()
            d['key'] = 'value'
            self.assertEqual(d.key, 'value')
    
        def test_attr(self):
            d = Dict()
            d.key = 'value'
            self.assertTrue('key' in d)
            self.assertEqual(d['key'], 'value')
    
        def test_keyerror(self):
            d = Dict()
            # 这里相当于语法糖
            with self.assertRaises(KeyError):
                value = d['empty']
            '''  
            相当于:
            try:
                value = d['empty']
            except KeyError:
                pass
            else:
                raise AssertionError("Expected a KeyError")  
    
            如果d['empty']尝试访问一个不存在的键时引发了一个KeyError异常,with语句将不会执行任何代码。相反,它会继续执行with语句之后的代码。如果d['empty']没有引发KeyError异常,则会抛出一个AssertionError,指出没有预期的异常被引发。  
            '''  
    
        def test_attrerror(self):
            d = Dict()
            with self.assertRaises(AttributeError):
                value = d.empty
    
    # 拥有这段代码可以把mydict_test.py当作正常的python脚本运行;
    if __name__ == '__main__':
        unittest.main()
    # 或者可以在命令行通过参数`-m unittest`直接运行单元测试
    python -m unittest mydict_test
    
  13. 单元测试中还有两个特殊的方法:setUp()tearDown(),这两个方法分别在每调用一个测试方法的前后分别被执行。

  14. python内置文档测试模块,可以直接提取注释中的代码并执行测试:

    import doctest
    doctest.testmod()
    

IO编程

  1. 同步IO和异步IO:

    • 同步IO:CPU等待,程序暂停执行代码,等待数据全部写入后再往下执行;
    • 异步IO:CPU不等待,程序代码可以在数据写入时同步执行。
  2. 在磁盘上读写文件的功能是由操作系统提供,现代操作系统不允许普通程序直接操作磁盘,读写文件是请求操作系统打开一个文件对象(通常称为文件描述符),然后通过操作系统提供的接口从此文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)。

  3. 读文件使用open()函数:

    '''
    参数说明:
    - file=path-like object,要打开文件的路径或者是封装文件对应的整数类型文件描述符。
    - mode=str,文件打开模式,常用r读w写x排他性创建a追加写入b二进制模式t文本模式+打开用于更新。  
    - buffering=int,设置缓冲策略,0关闭缓冲,1选择缓冲行,>1固定大小的块缓冲区的字节大小。  
    - encoding=str,选择编码格式。  
    - errors=str,错误处理模式,'strict'/'ignore'/'replace'...
    - newline=str,如何解析换行符,None时启用通用换行。  
    - closefd=Bool,保持文件打开状态,由file的值决定。  
    - opener,开启器。
    '''
    
    # 以下有值为默认参数  
    open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -> IO stream
    
  4. 如果文件不存在open函数会抛出IOError;如果文件存在f.read()方法可以一次读取文件的全部内容。

  5. 保证正确地关闭文件的方法:

    # method1:手动调用close
    try:
        f = open('path', 'r')
    finally:
        if f:
            f.close()
    
    # method2:自动调用close
    with open('path', 'r') as f:
        print(f.read)
    
  6. 文件过大时可以通过read(size)指定每次读取大小,或者调用readline()每次读取一行内容,或者调用readlines()一次读取所有内容并按行返回list。

    for line in f.readlines():
        print(line.strip()) # 把末尾的'\n'删掉
    
  7. file-like Object是鸭子类型,只要拥有read方法就是,StringIO是在内存中创建的file-like Object用作临时缓冲。

  8. 写模式时OS会将数据放到内存缓冲起来,所以为了防止丢失数据,需要在写入后close文件:

    with open('path', 'w') as f:
        f.write('xxx')
    
  9. StringIO是在内存中读写str,BytesIO是在内存中读写bytes:

    """StringIO"""
    >>> from io import StringIO
    # 写入StringIO:
    >>> f = StingIO()
    >>> f.write('xxx')
    3 # 返回输入长度
    # 获取写入后的IO
    >>> print(f.getvalue())
    xxx
    
    # 读取StringIO
    >>> f = StringIO('xxx\nxxx')
    >>> while True:
    ...     s = f.readline()
    ...     if s == '':
    ...         break
    ...     print(s.strip())
    xxx
    xx
    
    
    """BytesIO"""
    from io import BytesIO
    # 写入BytesIO,写入的不是str,而是经过utf-8编码的bytes:
    >>> f = BytesIO()
    >>> f.write('中文'.encode('utf-8'))
    6
    # 获取写入后的IO
    >>> print(f.getvalue())
    b'\xe4\xb8\xad\xe6\x96\x87'
    
    # 读取StringIO
    >>> f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')
    >>> f.read()
    b'\xe4\xb8\xad\xe6\x96\x87'
    
  10. 与OS进行交互import os

    • 获取操作系统类型:os.name
    • 获取详细的系统信息:os.uname()
    • 获取系统环境变量:os.environ
    • 获取环境变量的某个值:os.environ.get('key')
  11. 操作文件和目录:

    • 查看当前目录绝对路径:os.path.abspath('.')
    • 连接目录和文件名:os.path.join('dir1', 'dir2/filename', ...) -> dir1/dir2/filename
    • 拆分目录和文件名:os.path.split('dir1/dir2/filename') -> ('dir1/dir2', 'filename')
    • 获得文件拓展名:os.path.splitext(dir.filename) -> 文件拓展名
    • 创建目录:os.mkdir('dirpath')
    • 删除目录:os.rmdir('dirpath')
    • 对文件重命名:os.rename('old_name', 'new_name')
    • 删除文件:os.remove('filename')
    • 更多拓展操作例如copy等可以使用shutil模块,作为os模块的补充。
  12. 利用python特性来过滤文件:

    # 列出当前dir下所有目录
    >>> [x for x in os.listdir('.') if os.path.isdir(x)]
    ['.lein', '.local', '.m2', '.npm', '.ssh', '.Trash', '.vim', 'Applications', 'Desktop', ...]
    # 列出所有的.py文件
    >>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']
    ['apis.py', 'config.py', 'models.py', 'pymonitor.py', 'test_db.py', 'urls.py', 'wsgiapp.py']
    
  13. 把变量从内存中变成可存储或可运输的过程称之为序列化(pickling),序列化之后就可以把序列化后的内容写入磁盘或网络传输;把变量内容从序列化的对象重新读到内存里称之为反序列化(unpickling)。

  14. 把对象序列化并写入文件pickle.dumps()

    >>> import pickle
    >>> d = dict(age=20,)
    >>> pickle.dumps(d)
    # 返回对象序列化的bytes
    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()
    
  15. 将对象反序列化读取pickle.load()

    >>> import pickle
    >>> f = open('dump.txt', 'rb')
    >>> d = pickle.load(f)
    >>> f.close()
    >>> d
    {'age': 20, 'score': 88, 'name': 'Bob'}
    
  16. Python序列化的内容只能用于python。

  17. json模块中的dumpsloads可以将对象序列化成标准格式,其编码是utf-8:

    >>> import josn
    >>> d =dict(name='Bob', age=20, score=88)
    >>> json.dumps(d)
    '{"age": 20, "score": 88, "name": "Bob"}'
    
    >>> json_str = '{"age": 20, "score": 88, "name": "Bob"}'
    >>> json.loads(json_str)
    {'age': 20, 'score': 88, 'name': 'Bob'}
    
  18. 其他类型对象必须先转换为dict才可用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)
    
    def student2dict(std):
        return {
                'name': std.name,
                'age': std.age,
                'score': std.score
                }
    
    >>> json.dumps(s, default=student2dict)
    {"age": 20, "name": "Bob", "score": 88}
    
    >>> json.dumps(s, default=lambda obj: obj.__dict__)
    
    # 反序列化
    def dict2student(d):
        return Student(d['name'], d['age'], d['score'])
    
    >>> json_str = '{"age": 20, "score": 88, "name": "Bob"}'
    >>> json.loads(json_str, object_hook=dict2student)
    <__main__.Student object at 0x10cd3c190>
    

参考教程

Python教程-廖雪峰

posted @ 2024-02-07 00:11  LPF05  阅读(19)  评论(0编辑  收藏  举报