sqlalchemy源码阅读(1)

来源:互联网 发布:实现数据库的增删改查 编辑:程序博客网 时间:2024/06/05 09:23

code

先上代码,今天要解释的是下面这个代码,位置在sqlalchemy/util/langhelpers.py

def public_factory(target, location):    """Produce a wrapping function for the given cls or classmethod.    Rationale here is so that the __init__ method of the    class can serve as documentation for the function.    """    if isinstance(target, type):        fn = target.__init__        callable_ = target        doc = "Construct a new :class:`.%s` object. \n\n"\        "This constructor is mirrored as a public API function; see :func:`~%s` "\        "for a full usage and argument description." % (                    target.__name__, location, )    else:        fn = callable_ = target        doc = "This function is mirrored; see :func:`~%s` "\                "for a description of arguments." % location    location_name = location.split(".")[-1]    spec = compat.inspect_getfullargspec(fn)    del spec[0][0]    metadata = format_argspec_plus(spec, grouped=False)    metadata['name'] = location_name    code = """\def %(name)s(%(args)s):    return cls(%(apply_kw)s)""" % metadata    env = {'cls': callable_, 'symbol': symbol}    exec(code, env)    decorated = env[location_name]    decorated.__doc__ = fn.__doc__    if compat.py2k or hasattr(fn, '__func__'):        fn.__func__.__doc__ = doc    else:        fn.__doc__ = doc    return decorated

为什么要阅读这个代码

这个要从我今天研究的一个问题说起,之前没有特别研究过sqlalchemy的executemany的实现过程,因为工作需要,还是需要研究一下sqlalchemy的内部实现。

使用sqlalchemy的测试代码如下,

from sqlalchemy import (    create_engine,)from sqlalchemy.orm import (    sessionmaker)from sqlalchemy.ext.declarative import declarative_basefrom sqlalchemy import Column, Integerfrom sqlalchemy.dialects.postgresql import JSONengine = create_engine("postgresql://root:root@127.0.0.1:5432/testdb")DBSession = sessionmaker(bind=engine)Base = declarative_base(name="Alex")class Books(Base):    __tablename__ = 'books'    id = Column(Integer, primary_key=True)    data = Column(JSON)def simple_query():    new_book = Books(id=1, data={"hello": 123})    session = DBSession()    session.add(new_book)    session.commit()def test_many():    session = DBSession()    data_list = [        {"id": 200, "data": {"hello": 123}},        {"id": 300, "data": {"hello": 123}},        {"id": 400, "data": {"hello": 123}},        {"id": 500, "data": {"hello": 123}},    ]    session.execute(Books.metadata.tables['books'].insert(), data_list)  #1    session.commit()simple_query()test_many()

根据python的DBAPI,python是不支持JDBC的prepared statement的,但是DBAPI提供executemany的接口,可以让你一次执行插入多行,具体见DBAPI。

但是在网上可以找到的SO来看,在#1这里所使用的都是一个Table的insert()可以执行expr,并不是我们现在使用的declarative的表。废了一些功夫才找到上面这个用法,不知道是不是正规的用法,如果有了解的还请告诉我,要不只能等到以后某天自己发现改过来了。

在查找这个问题的时候,碰到了sqlalchemy里面是如何实现declarative的问题, 首当其冲的就是这个函数。

好了,自己工作的事情写这么多,下面来分析一下我们上面要分析的这个函数,为什么是它呢?

分析

Base = declarative_base(name="Alex")class Books(Base):    __tablename__ = 'books'    id = Column(Integer, primary_key=True)    data = Column(JSON)    def __init__(self, *args, **kwargs):        super.__init__(self, *args, **kwargs)

如上,看到我们使用declarative方式使用sqlalchemy的声明过程。首先我们使用declarative_base定义了我们要使用的Base类,注意,这出来的是一个类哦,因为declarative_base里面使用了DeclarativeMeta这个metaclass。

...............................from ...orm import mapper, class_mapper, synonym...............................class DeclarativeMeta(type):    def __init__(cls, classname, bases, dict_):        if '_decl_class_registry' not in cls.__dict__:            _as_declarative(cls, classname, cls.__dict__)        type.__init__(cls, classname, bases, dict_)    def __setattr__(cls, key, value):        _add_attribute(cls, key, value)def _as_declarative(cls, classname, dict_):.......    if defer_map:        cfg_cls = _DeferredMapperConfig    else:        cfg_cls = _MapperConfig    mt = cfg_cls(mapper_cls,                       cls, table,                       inherits,                       declared_columns,                       column_copies,                       our_stuff,                       mapper_args_fn)    if not defer_map:        mt.map()

当我们定义完Book这个类,python解释器扫描完这个类,就会为我们创建这个类对象,相应的也会调用_as_declarative这个路径进来。注意在最后,默认实际上是调用了_MapperConfig.map. 而在这个函数中,使用mapper_cls来创建我们的mapper对象。那么这个mapper_cls就是我们简单认为的是Mapper吗?no no no

实际上,它是sqlalchemy/orm/init.py中的mapper对象。而这个对象则是由下面来生成的

mapper = public_factory(Mapper, ".orm.mapper")

那么现在,我们的public_factory就出现了,为什么会有它呢?而不是直接使用Mapper?

从它的注释中可以看出,这个函数的功能就是将一个类的init 方法暴露出来,暴露成一个函数对象。那么问题是为什么要暴露成函数对象呢?注释里又说了,这个是为了生成文档用的。好吧,看到这里,个人认为,sqlalchemy中的命名规则还是比较随意的,只是从函数的名字还不能清晰的得知这个函数的用途或者说目的。

收获

这里的收获其实不止可以把一个类的init 方法暴露出来用来生成文档用。这里想说的是exec这个函数。

exec(object[, globals[, locals]])

这个是exec这个函数的原型,对照上面的代码可以看到,它是把env这个dict当做了这里的globals来使用的,并且根据exec的文档,exec执行后,会把__builtins__ 插入到这个env里面来,通过运行可以证明,这个是对的。尤为重要的一点是经过exec,它把上面的code字符串编程了function对象decorated,经过一番处理后,返回了出去。而这个函数就是_MapperConfig.map中所使用的mapper_cls

令人奇怪的是,它虽然带有cls但是时间上它并不是一个class对象,而是一个function对象。有点误导人。

0 0
原创粉丝点击