模拟Django元类逻辑中DbMeta继承属性丢失问题及解决方法
问题:子类DbMeta为何没继承父类的属性?
我在模拟Django用元类实现数据库模型选项的逻辑时遇到了一个问题:子类的DbMeta类明明继承了父类的属性,但在实例化DatabaseOptions后却无法访问到继承来的属性。
我的测试代码
class DatabaseOptions(object): def __init__(self, opts): if opts: for key, val in opts.__dict__.items(): if not key.startswith('__') and not callable(val): setattr(self, key, val) class MetaModel(type): def __new__(cls, name, bases, classdict): result = super().__new__(cls, name, bases, dict(classdict)) opts = classdict.pop('DbMeta', None) if opts: setattr(result, '_db_meta', DatabaseOptions(opts)) return result class Model(object, metaclass=MetaModel): class DbMeta: database = 'default' migrate = True class User(Model): class DbMeta(Model.DbMeta): database = 'user' class GroupUser(User): class DbMeta(User.DbMeta): database = 'group_user'
预期输出
print(Model._db_meta.database) # default print(Model._db_meta.migrate) # True print(User._db_meta.database) # user print(User._db_meta.migrate) # True print(GroupUser._db_meta.database) # group_user print(GroupUser._db_meta.migrate) # True
实际错误
default True user Traceback (most recent call last): File "test.py", line 48, in <module> print(User._db_meta.migrate) # True AttributeError: 'DatabaseOptions' object has no attribute 'migrate'
问题原因
核心问题出在DatabaseOptions.__init__里的opts.__dict__.items()调用上:
Python中,一个类的__dict__只包含该类自己定义的属性和方法,不包括从父类继承来的内容。比如User.DbMeta只定义了database,migrate是从Model.DbMeta继承的,所以它不在User.DbMeta.__dict__中,自然不会被复制到_db_meta对象里。
解决方案
方案1:用dir()遍历所有属性(包括继承的)
你已经找到的这个方案很有效,通过dir(opts)可以拿到类的所有可访问属性(包括继承的),再用getattr获取值:
class DatabaseOptions(object): def __init__(self, opts): if opts: for key in dir(opts): if not key.startswith('__'): val = getattr(opts, key, None) if not callable(val): setattr(self, key, val)
方案2:遍历类的MRO(方法解析顺序)
如果需要处理更复杂的多层继承场景,可以遍历类的MRO(__mro__属性),逐个收集父类的属性,这样能确保所有继承链上的属性都被收集,且子类属性会覆盖父类的:
class DatabaseOptions(object): def __init__(self, opts): if opts: # 反向遍历MRO,先处理父类,再处理子类,子类属性覆盖父类 for cls in reversed(opts.__mro__): for key, val in cls.__dict__.items(): if not key.startswith('__') and not callable(val): setattr(self, key, val)
这两种方案都能解决你的问题,运行后就能得到预期的输出了。
内容的提问来源于stack exchange,提问作者hetsch
相关产品推荐
相关产品推荐

