Python知识

  • 命名关键字参数

    用于指定关键字
def f(a, *, b, c):
    pass
# 参数b, c是命名关键字参数,必须以关键字参数的形式传入(命名关键字参数不可缺省,也不能出现额外的关键字参数)

def f(a, *, b=1, c):
    pass
# 命名关键字参数可以指定默认值(此时该命名关键字参数可以缺省)

def f(a, *b, c, d):
    pass
# a-位置参数
# b-可变参数
# c-命名关键字参数

参数定义的顺序必须是:位置参数、默认参数(关键字参数)、可变参数、命名关键字参数、可变关键字参数


  • 普通函数vs生成器函数

    普通函数是顺数执行,遇到return语句或者最后一行函数语句就返回。
    生成器函数在每次调用next()函数时执行,遇到yield语句返回;再次执行next()函数是,执行至下一个yield处,直到引发StopIteration异常(python中,右边语句先执行)

    Iterable(可迭代对象):可用于for循环中的对象;
    Iterator(迭代器):可被next()函数调用的对象。
    (list、dict、str等是Iterable,但不是Iterator)


  • 装饰器函数

    保留被装饰函数名称的方法
import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('call {}.()'.format(func.__name__)
        return func(*args, **kwargs)
    return wrapper

  • 偏函数

import functools

# 相当于将int()函数的base参数固定为2
int2 = functools.partial(int, base=2)

# 相当于对max()函数增加一个位置参数10
max10 = functools.partial(max, 10)

# partial()函数接口
partial(func, *args, **kwargs)

  • 鸭子类型

    实现了某种协议,而没有继承相关类型的对象。称之为“类X对象”

  • 元类

    主要借鉴自廖雪峰教程
# 以ORM为例

class Field(object):

    def __init__(self, name, column_type):
        self.name = name
        self.column_type = column_type

    def __str__(self):
        return '<{}: {}>'.format(self.__class__.__name__, self.name)


class StringField(Field):

    def __init__(self, name):
        super(StringField, self).__init__(name, 'varchar(100)')


class IntegerField(Field):

    def __init__(self, name):
        super().__init__(name, 'bigint')


class ModelMetaclass(type):
    
    # 主要是对attrs参数的修改
    def __new__(cls, name, bases, attrs):
        if name == 'Model':
            return super().__new__(cls, name, bases, attrs)
        # 主要的定义工作是针对Model的子类进行的(子类自动继承父类的元类)
        print('Found model: %s' % name)
        mappings = {}
        # 将Field类从attrs中取出并放在同一个字典中(ORM中的'M')
        for key, value in attrs.items():
            if isinstance(value, Field):
                print('Found mappings: %s ==> %s' % (key, value))
                mappings[key] = value
        for key in mappings:
            # 从attrs中将Field类取出
            attrs.pop(key)
        # 添加attrs中的映射
        attrs['__mappings__'] = mappings
        attrs['__table__'] = name
        return super().__new__(cls, name, bases, attrs)


# 关键字参数metaclass不是继承的意思,而表示创建自某个元类(元类都是直接继承type类)
class Model(dict, metaclass=ModelMetaclass):

    def __init__(self, **kw):
        # 继承dict类,因此以字典的方式处理初始参数
        super().__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError('{!r} object has no attribute {!r}'.format(self.__class__.__name__, key)) from None

    def __setattr__(self, key, value):
        self[key] = value

    def save(self):
        fields = []
        params = []
        args = []
        for key, value in self.__mappings__.items():
            # value是Field类的实例
            # Django中则直接以key作为相应列的名称
            fields.append(value.name)
            params.append('?')
            # key是Field类实例的引用(可能会在Model或者子类实例的创建中被初始化)
            args.append(getattr(self, key, None))
        sql = 'INSERT INTO {} ({}) VALUES ({})'.format(self.__table__, ', '.join(fields), ','.join(params))
        print('SQL: {}'.format(sql))
        print('ARGS: {}'.format(args))


# 默认继承父类的元类,因此在编译(?)时,会通过ModelMetaclass类来实现
# 类在导入(或者运行而不被调用)时,解释器会先执行类的属性(包括方法)以构建类。
# 因此OrmTest类中的类属性(Filed实例对象)会在编译时直接被ModelMetaclass元类调用(即保存在attrs并进行相关处理)
# 在程序被作为模块导入(或者运行)时会自动运行ModelMetaclass中相关的输出语句
class OrmTest(Model):
    name = StringField('name')
    age = IntegerField('age')
    email = StringField('Email_test')

需要注意Model类的save()方法定义中

        for key, value in self.__mappings__.items():
            fields.append(value.name)
            params.append('?')
            args.append(getattr(self, key, None))
# 在创建Model子类的实例并初始化过程中,SQL语句用到的是OrmTest类中Field子类的实例
# 而相关类属性(Field子类实例)通过自定义元类保存在类的__mappings__属性中,从而可以通过__mappings__属性筛选出需要在SQL语句中使用的数据(根据key值)

使用ORM的目的应该是对实例初始化时数据的检验:

def __setattr__(self, key, value):
    if key in self.__mappings__:
        column_type = self.__mappings__[key].column_type
        if isinstance(value, column_type):
            self.key = value
        else:
            raise TypeError()
    else:
        self[key] = value
# 不过这种方法无法在实例初试化时就进行检验
# 应该需要定义描述符类来进行检验

描述符对数据类型检验的一个简单实现(主要参考《Fluent Python》)

import re


class Field(object):
    __counter = 0

    def __init__(self, column_type, blank=False, default=None):
        cls = type(self)
        prefix = cls.__name__
        index = cls.__counter
        self.storage_name = '_{}#{}'.format(prefix, index)
        cls.__counter += 1
        self.column_type = column_type
        self.blank = blank
        # 应该增加一个对default类型检验的方法
        self.default = default

    def __get__(self, instance, owner):
        return self

    def __set__(self, instance, value):
        value = self.validate(instance, value)
        setattr(instance, self.storage_name, value)

    # 在赋值前,对实例的值进行类型检查
    def validate(self, instance, value):
        var = re.search(r'#(\w+)$', self.storage_name).group(1)
        if value:
            if isinstance(value, self.column_type):
                return value
            else:
                raise ValueError('{!r} must be {}'.format(var, self.column_type.__name__))
        else:
            if self.blank:
                return self.default
            else:
                raise ValueError('{!r} cannot be None'.format(var))


class StringField(Field):

    def __init__(self, blank=False, default=None):
        super(StringField, self).__init__(str, blank=blank, default=default)


class IntegerField(Field):

    def __init__(self, blank=False, default=None):
        super().__init__(int, blank=blank, default=default)


class ModelMetaclass(type):
    
    # 主要是对attrs参数的修改
    def __new__(cls, name, bases, attrs):
        if name == 'Model':
            return super().__new__(cls, name, bases, attrs)
        mappings = []
        # 将Field类从类的attrs中取出并放在同一个列表中,以方便之后使用
        for key, value in attrs.items():
            if isinstance(value, Field):
                # 给描述符类中的storage_name起名,使其同实例的属性名称区分开来
                value.storage_name = '_{}#{}'.format(value.__class__.__name__, key)
                mappings.append(key)      
        attrs['__mappings__'] = mappings
        attrs['__table__'] = name
        return super().__new__(cls, name, bases, attrs)



class Model(metaclass=ModelMetaclass):

    def __init__(self, **kw):
        for k, v in kw.items():
            setattr(self, k, v)
        # 对未初始化的描述符类属性进行初始化(以检验该属性是否blank==True)
        for i in self.__class__.__mappings__:
            if i not in kw.keys():
                setattr(self, i, None)

    def save(self):
        fields = []
        params = []
        args = []
        for item in self.__mappings__:
            fields.append(item)
            params.append('?')
        # 此处未能剔除多余的初始变量
        args = [value for value in self.__dict__.values()]
        # args = [getattr(self, getattr(self, item).storage_name) for item in self.__mappings__]
        sql = 'INSERT INTO {} ({}) VALUES ({})'.format(self.__table__, ', '.join(fields), ','.join(params))
        print('SQL: {}'.format(sql))
        print('ARGS: {}'.format(args))


class OrmTest(Model):
    name = StringField()
    age = IntegerField(blank=True, default=0)
    email = StringField(blank=True, default='')

  • 调试方法(参考网站)

  1. 在可能引发异常的地方使用print()语句
def foo(s):
    n = int(s)
    print('>>> n = %d' % n)
    # 此处可能引发异常,因此在此前使用print()函数打印出可能原因
    return 10/n

foo('0')
  1. 在可能引发异常的地方使用断言(assert)
def foo(s):
    n = int(s)
    assert n != 0, 'n is zero!'
    return 10 / n

foo('0')
# 使用断言,如断言为True,则跳过;否则,引发AssertionError
# 可以在启动解释器时使用 -O 参数,跳过断言
  1. 使用logging来记录错误
import logging
logging.basicConfig(level=logging.INFO)

def foo(s):
    n = int(s)
    logging.info(n = %d' % n)
    return 10 / n

foo('0')
# 相当于将第一种方法里面的print()函数都替换成logging
# logging有四个等级:debug,info,warning,error;脚本运行后只会显示出指定及之后的信息
  1. 启动Python的pdb(python debug?)(1)
# err.py
s = '0'
n = int(s)
print(10/n)
# 启动方式
$ python -m pdb err.py

# pdb命令
(Pdb) l                       # (字母l)查看源代码
(Pdb) n                       # 单步执行代码
(Pdb) p  <变量名>              # 查看变量(但似乎只能查看全局变量)
(Pdb) q                       # 结束调试
  1. 使用Python的pdb(2)
import pdb

s = '0'
n = int(s)
# 在可能引发异常的地方设置断点
pdb.set_trace()
print(10/n)
# 在正常运行脚本后,脚本会自动在pdb.set_trace()处暂停,并进入调试环境,此时可以使用 p 查看变量,或者 c 继续程序运行

  • 单元测试&文档测试

    参考教程廖雪峰

你可能感兴趣的:(Python知识)