简体   繁体   English

检测 Python 类变量的重新声明

[英]Detect re-declaration of Python class variables

Let's say I have a Python class as below:假设我有一个 Python 类,如下所示:

class Foo:
    one = 'some value'
    two = 'some other value'
    # ... and so on ...
    twenty = 'blah blah'
    one = 'updated value'  # OH NOEEESSS :o

I want to detect (and possibly prevent) the re-declaration of Foo.one in the class body.我想检测(并可能阻止)在类主体中重新声明Foo.one

Is there any way to detect this programmatically at runtime, or even via a lint rule?有没有办法在运行时以编程方式甚至通过 lint 规则检测到这一点?


If it helps, my use case is a bit more specific.如果有帮助,我的用例会更具体一些。 I want to avoid re-using column names of Flask SQLAlchemy models, where I have a long list of columns and I risk re-using old column names while trying to add new columns.我想避免重复使用Flask SQLAlchemy模型的列名,我有很长的列列表,并且在尝试添加新列时我冒着重新使用旧列名的风险。 For example:例如:

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class UserModel(db.Model):
    """ A sample user model """
    __tablename__ = 'user'

    name = db.Column(db.String)
    is_dead = db.Column(db.Boolean)
    # ... and so on ...
    name = db.Column(db.String)  # Detect/prevent re-declaration of 'name'

I managed to solve for my specific usecase at runtime using a metaclass, a special dictionary and by overriding the __prepare__ method of the metaclass:我设法在运行时使用元类、特殊字典并通过覆盖元类的__prepare__方法解决了我的特定用例:

from sqlalchemy.sql.schema import Column
from flask_sqlalchemy import SQLAlchemy


db = SQLAlchemy()


class NoColumnReassignmentDict(dict):
    def __setitem__(self, key, value):
        if key in self and isinstance(self[key], Column):
            raise Exception(f'Cannot re-assign column <{key}>')
        return super().__setitem__(key, value)


class MetaBaseModel(type(db.Model)):
    @classmethod
    def __prepare__(cls, *args, **kwargs):
        return NoColumnReassignmentDict()


class UserModel(db.Model, metaclass=MetaBaseModel):
    """ A sample user model """
    __tablename__ = 'user'

    user_id = db.Column(db.String, nullable=False, primary_key=True)
    name = db.Column(db.String)
    is_dead = db.Column(db.Boolean)
    # ... and so on ...
    name = db.Column(db.String)  # Raises: 'Exception: Cannot re-assign column <name>'

I suppose this could be extended for a generic usecase too.我想这也可以扩展到通用用例。


PS: This was a good reference! PS: 是一个很好的参考!

I'm not entirely sure this will work;我不完全确定这会起作用; I don't recall if db.Model uses a metaclass that could interfere with this.我不记得db.Model使用了可能会干扰这个的元类。 But instead of using a class statement, you can call type directly with a dict of attributes which has been prescreened for duplicates.但是,您可以使用已预先筛选重复项的属性字典直接调用type ,而不是使用class语句。

def checkForDuplicateFields(fields):
    seen = set()
    for k in fields:
        if k in seen:
            raise ValueError(f"Duplicate key {k} found")
        seen.add(k)


fields = [
    ('name', db.Column(db.String)),
    ...
]

checkForDuplicateFields(fields)
UserModel = type('UserModel', (db.Model,), dict(fields))

(If db.Model does use some other metaclass, for example DBMeta , then you would call that instead of type : (如果db.Model确实使用了其他一些元类,例如DBMeta ,那么您将调用它而不是type

UserModel = DBMeta('UserModel', (db.Model,) dict(fields))

) )

(I'm not sure this is something even a custom metaclass could handle. The way a class statement works, the names assigned in the body are gathered into a dict and passed as an argument to the metaclass. You would need the class statement to use a special kind of dict that rejects duplicate assignments (or at least a multi dict, which doesn't exist in standard Python) before the metaclass is even invoked, and I don't know if there is any hook to allow that.) (我不确定这是什至自定义元类可以处理的事情。 class语句的工作方式,主体中分配的名称被收集到一个dict并作为参数传递给元类。您需要class语句来在元类被调用之前,使用一种特殊的dict拒绝重复赋值(或者至少是一个 multi dict,它在标准 Python 中不存在),我不知道是否有任何钩子允许这样做。)

How about checking if the attribute is set first?如何检查属性是否首先设置?

class Foo:
    def __init__(self):
        self.one = 'some value'
        self.two = 'some other value'
        # ... and so on ...
        self.twenty = 'blah blah'
        if not hasattr(self, 'one'):
            self.one = 'updated value'  # Aww yea

In case the variables are in the form of config or if their string names are known:如果变量是 config 的形式或者它们的字符串名称是已知的:

class Foo:
    def __init__(self):
        self.one = 'some value'
        self.two = 'some other value'
        # ... and so on ...
        self.twenty = 'blah blah'
        if not hasattr(self, 'one'):
            setattr(self, 'one', 'updated value') # Aww yea 2

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM