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.