简体   繁体   中英

Detect re-declaration of Python class variables

Let's say I have a Python class as below:

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.

Is there any way to detect this programmatically at runtime, or even via a lint rule?


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. 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:

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!

I'm not entirely sure this will work; I don't recall if db.Model uses a metaclass that could interfere with this. But instead of using a class statement, you can call type directly with a dict of attributes which has been prescreened for duplicates.

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 :

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.)

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:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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