kuroの覚え書き

96の個人的覚え書き

sqlalchemy or sqlalchemy.sql or sqlalchemy.sql.expression

黙々とデバッグ&コードの整理を行っているわけだが・・・

コードをツギハギで書いていると
from sqlalchemy import func
from sqlalchemy.sql import func
from sqlalchemy.sql.expression import func
と同じfuncを3箇所からimportしていたりする。

これらは別物なのか?別物だったとして、名前は同じで大丈夫なのか?

という疑問が沸々と湧いてきた。

ということでモジュールに含まれている関数を確認する方法をしらべたところ

>>> import sqlalchemy
>>> dir(sqlalchemy)
['ARRAY', 'BIGINT', 'BINARY', 'BLANK_SCHEMA', 'BLOB', 'BOOLEAN', 'BigInteger', 'Binary', 'Boolean', 'CHAR', 'CLOB', 'CheckConstraint', 'Column', 'ColumnDefault', 'Constraint', 'DATE', 'DATETIME', 'DDL', 'DECIMAL', 'Date', 'DateTime', 'DefaultClause', 'Enum', 'FLOAT', 'FetchedValue', 'Float', 'ForeignKey', 'ForeignKeyConstraint', 'INT', 'INTEGER', 'Index', 'Integer', 'Interval', 'JSON', 'LargeBinary', 'MetaData', 'NCHAR', 'NUMERIC', 'NVARCHAR', 'Numeric', 'PassiveDefault', 'PickleType', 'PrimaryKeyConstraint', 'REAL', 'SMALLINT', 'Sequence', 'SmallInteger', 'String', 'TEXT', 'TIME', 'TIMESTAMP', 'Table', 'Text', 'ThreadLocalMetaData', 'Time', 'TypeDecorator', 'Unicode', 'UnicodeText', 'UniqueConstraint', 'VARBINARY', 'VARCHAR', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__go', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', 'alias', 'all_', 'and_', 'any_', 'asc', 'between', 'bindparam', 'case', 'cast', 'collate', 'column', 'cprocessors', 'create_engine', 'cresultproxy', 'cutils', 'delete', 'desc', 'dialects', 'distinct', 'engine', 'engine_from_config', 'event', 'events', 'exc', 'except_', 'except_all', 'exists', 'extract', 'false', 'func', 'funcfilter', 'insert', 'inspect', 'inspection', 'interfaces', 'intersect', 'intersect_all', 'join', 'lateral', 'literal', 'literal_column', 'log', 'modifier', 'not_', 'null', 'or_', 'outerjoin', 'outparam', 'over', 'pool', 'processors', 'schema', 'select', 'sql', 'subquery', 'table', 'tablesample', 'text', 'true', 'tuple_', 'type_coerce', 'types', 'union', 'union_all', 'update', 'util', 'within_group']

このようにdir()で調べることになっているらしい。
で、例えばsqlalchemyにはfuncという関数があるわけだが

>>> dir(sqlalchemy.func)
['_FunctionGenerator__names', '__call__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'opts']

一方でsqlalchemy.sqlを調べると

>>> dir(sqlalchemy.sql)
['Alias', 'ClauseElement', 'ClauseVisitor', 'ColumnCollection', 'ColumnElement', 'CompoundSelect', 'Delete', 'False_', 'FromClause', 'Insert', 'Join', 'Select', 'Selectable', 'TableClause', 'TableSample', 'True_', 'Update', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__go', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'alias', 'all_', 'and_', 'annotation', 'any_', 'asc', 'base', 'between', 'bindparam', 'case', 'cast', 'collate', 'column', 'compiler', 'crud', 'ddl', 'default_comparator', 'delete', 'desc', 'distinct', 'dml', 'elements', 'except_', 'except_all', 'exists', 'expression', 'extract', 'false', 'func', 'funcfilter', 'functions', 'insert', 'intersect', 'intersect_all', 'join', 'label', 'lateral', 'literal', 'literal_column', 'modifier', 'naming', 'not_', 'null', 'operators', 'or_', 'outerjoin', 'outparam', 'over', 'schema', 'select', 'selectable', 'sqltypes', 'subquery', 'table', 'tablesample', 'text', 'true', 'tuple_', 'type_api', 'type_coerce', 'union', 'union_all', 'update', 'util', 'visitors', 'within_group']

こうなっており、たしかにここにもfuncがある。なのでsqlalchemy.sql.funcも調べてみると

>>> dir(sqlalchemy.sql.func)
['_FunctionGenerator__names', '__call__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'opts']

このように、どうやらsqlalchemy.funcと同じ内容になっているのではないかと予想される。
ちなみにsqlalchemy.sql.expression.funcも

>>> dir(sqlalchemy.sql.expression.func)
['_FunctionGenerator__names', '__call__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'opts']

と同一っぽいので、これらはどれか一つをインポートしておけば良さそうな気がするわけだ。

念のためmoduleを直接調べてみる。

>>> import sqlalchemy
>>> print(sqlalchemy.__file__)
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/sqlalchemy/__init__.py

sqlalchemyのありかをこのように調べて、lsで覗いてみると、まんまpythonのプログラムなんで

__init__.py				ext
__pycache__				inspection.py
connectors				interfaces.py
cprocessors.cpython-36m-darwin.so	log.py
cresultproxy.cpython-36m-darwin.so	orm
cutils.cpython-36m-darwin.so		pool.py
databases				processors.py
dialects				schema.py
engine					sql
event					testing
events.py				types.py
exc.py					util

こんな感じに構成がわかる。ここを見るとsqlはあるがfuncは見当たらない。
で、__init__.pyをlessすると

# sqlalchemy/__init__.py
# Copyright (C) 2005-2017 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php


from .sql import (
    alias,
    all_,
    and_,
    any_,
    asc,
    between,
    bindparam,
    case,
    cast,
    collate,
    column,
    delete,
    desc,
    distinct,
    except_,
    except_all,
    exists,
    extract,
    false,
    func,
    funcfilter,
    insert,
    intersect,
    intersect_all,
    join,
    lateral,
:

こんな感じにsqlからいろいろインポートしており、そこにfuncも存在している。でもってfuncはsql/functions.pyの中で定義されていることがわかった。要するに同じものを読み込んでいると考えて間違いなさそうだな。

sqlalchemyをimportする際に個別の関数だけをimportせずにまるごとimportして使うことが想定されているのかもしれない。ややこしいな。結局同じモジュール内の同名の関数は1つだけimportしておけばいいということで間違いなさそうだ。