【问题标题】:Python: Generic/Templated gettersPython:通用/模板化 getter
【发布时间】:2016-08-31 08:54:57
【问题描述】:

我有一个简单地从数据库中获取用户的代码

class users:
    def __init__(self):
        self.engine = create_engine("mysql+pymysql://root:password@127.0.0.1/my_database")
        self.connection = self.engine.connect()
        self.meta = MetaData(bind=self.connection)
        self.users = Table('users', self.meta, autoload = true)

    def get_user_by_user_id(self, user_id):
        stmt = self.users.select().where(self.users.c.user_id == user_id)
        return self.connection.execute(stmt)

    def get_user_by_username(self, username):
        stmt = self.users.select().where(self.users.c.username == username)
        return self.connection.execute(stmt)

    def get_users_by_role_and_company(self, role, company)
        stmt = self.users.select().where(self.users.c.role == role).where(self.users.c.company == company)
        return self.connection.execute(stmt)

现在,我想做的是让 getter 像这样通用:

class users:
    def __init__(self):
        self.engine = create_engine("mysql+pymysql://root:password@127.0.0.1/my_database")
        self.connection = self.engine.connect()
        self.meta = MetaData(bind=self.connection)
        self.users = Table('users', self.meta, autoload = true)

    def get_user(self, **kwargs):
        '''How do I make this generic function?'''

所以,不要这样调用:

u = users()
u.get_user_by_user_id(1)
u.get_user_by_username('foo')
u.get_users_by_role_and_company('Admin', 'bar')

我会像这样调用通用函数:

u = users()
u.get_user(user_id=1)
u.get_user(username='foo')
u.get_user(role='Admin', company='bar')

到目前为止,这是我能想到的:

def get_user(**kwargs):
    where_clause = ''
    for key, value in kwargs.items():
        where_clause += '{} == {} AND '.format(key, value)
    where_clause = where_clause[:-5] # remove final AND
    stmt = "SELECT * FROM {tablename} WHERE {where_clause};".format(tablename='users', where_clause=where_clause)
    return self.connection.execute(stmt)

有什么方法可以使用 ORM 样式来创建语句?

【问题讨论】:

    标签: python python-3.x sqlalchemy metaprogramming


    【解决方案1】:

    完全通用,因此只要表中存在该名称的字段,就可以接受任何合法字段名称的组合。神奇之处在于getattr,它允许我们动态查找我们感兴趣的字段(如果使用不存在的字段名称调用,则会引发AttributeError):

    def get_user(self, **kwargs):
        # Basic query
        stmt = self.users.select()
    
        # Query objects can be refined piecemeal, so we just loop and
        # add new clauses, assigning back to stmt to build up the query
        for field, value in kwargs.items():
            stmt = stmt.where(getattr(self.users.c, field) == value)
    
        return self.connection.execute(stmt)
    

    【讨论】:

    • 谢谢!这就是我一直在寻找的。​​span>
    【解决方案2】:

    但是您完成了所有艰苦的工作。这只是组合您创建的函数、初始化输入变量并在混合中抛出一些 if 语句的问题。

    def get_user(self, user_id=0, username='', role='', company=''):
        if user_id:
            stmt = self.users.select().where(self.users.c.user_id == user_id)
            return self.connection.execute(stmt)
        elif username:
            stmt = self.users.select().where(self.users.c.username == username)
            return self.connection.execute(stmt)
        elif role and company:
            stmt = self.users.select().where(self.users.c.role == role).where(self.users.c.company == company)
            return self.connection.execute(stmt)
        else:
            print('Not adequate information given. Please enter "ID" or "USERNAME", or "ROLE"&"COMPANY"')
            return
    

    请注意,user_id 已初始化为 0,因此它的布尔值为 False。如果可以使用 0 id,请将其直接设置为 False。所以,既然输入不能是“随机的”,你有什么理由想用**kwargs来做吗?


    或者,如果组合的数量太多而无法编码,我会采用不同的路线(SQL-injection-valnurable 脚本传入!),如下所示:

    def get_user(self, query):
        form_query = 'SELECT user FROM {} WHERE {}'.format(table_name, query)
        # now execute it and return whatever it is you want returned
    

    您不再将变量传递给函数,而是将附加到查询并执行的字符串。 不用说你必须非常小心。

    【讨论】:

    • 但这让我编写了所有可能的组合。在users表中,至少有13个字段可以用来查询一个用户。
    【解决方案3】:

    尝试类似:

    def get_user(self, **kwargs):
        if 'user_id' in kwargs:
            (...)
        elif  'username' in kwargs:
            (...)
        elif all(a in ['role','company'] for a in kwargs):
            (...)
        else:
            (...)
    

    【讨论】:

    • 讨厌:如果你需要lambda,请不要使用map;如果您需要求助于lambdas(特别是如果生成器表达式可以完全避免函数调用),它比等效的生成器表达式更冗长、更慢和更丑陋。在这种情况下,无论如何您都可以使用带有内置函数的mapall(map(('role', 'company').__contains__, kwargs)),但收益可能很小;你可以只做all(k in ('role', 'company') for k in kwargs),它通常被认为更 Pythonic。
    • 同样,'user_id' in kwargs 是一种比kwargs.get('user_id') 更有效、更正确的方法来测试密钥是否存在;后者将错误地指示丢失的密钥,即使密钥存在并且具有“虚假”值。
    • 感谢您的提醒。
    猜你喜欢
    • 1970-01-01
    • 2017-02-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多