确保两个Python类具有相同名称的属性
我希望用Python构建一个类接口,但发现Python缺少接口构造 我需要的是,如果程序员试图在一个类中添加新属性,而没有在另一个类中添加同名属性,则会引发一个异常(在编译时或运行时) 例如: 如果程序员试图在不更改确保两个Python类具有相同名称的属性,python,class,oop,interface,Python,Class,Oop,Interface,我希望用Python构建一个类接口,但发现Python缺少接口构造 我需要的是,如果程序员试图在一个类中添加新属性,而没有在另一个类中添加同名属性,则会引发一个异常(在编译时或运行时) 例如: 如果程序员试图在不更改ESCompany的情况下向MongoCompany添加字段,则会引发异常 class MongoCompany: company_name = MongoField() company_phone = MongoField() class ESCompany:
ESCompany
的情况下向MongoCompany
添加字段,则会引发异常
class MongoCompany:
company_name = MongoField()
company_phone = MongoField()
class ESCompany:
company_name = ESField()
MongoCompany.init()
编辑:
背景
这是为了防止程序员修改MongoDB使用Mongoengine的
文档
类声明的模式,而不添加对Elasticsearch dsl的DocType
类在另一个文件中声明的Elasticsearch模式的相应修改。耶!一个元类的实际应用程序,它不仅仅是为了使用元类而设计的!我们可以编写一个元类,如果类定义中出现意外属性,该元类将抛出。我们需要做的就是确保您的程序员实际使用它
class RequiredFieldsMeta(type):
_interface = {'company_name', 'num_employees'}
def __new__(cls, clsname, bases, attrs):
for field in RequiredFieldsMeta._interface:
if field not in attrs:
raise AttributeError(
'Class %s missing required property %s'
% (clsname, field))
for name in attrs:
if not isdunder(name) and name not in RequiredFieldsMeta._interface:
raise AttributeError(
'Class %s has extra property %s'
% (clsname, name))
return super(RequiredFieldsMeta, cls).__new__(cls, clsname, bases, attrs)
# Works fine:
class MongoCompany(metaclass=RequiredFieldsMeta):
company_name = 'Mongo Inc.'
num_employees = 100
# Throws AttributeError:
class ESyCompany(metaclass=RequiredFieldsMeta):
extra_prop = 'foobar'
快速演示
请注意,我们甚至没有进行实例化:我们的检查在定义类本身时运行
编辑:在我的编辑中,我引用了一个函数is\u dunder
。这可以是简单的name.startswith('''.'
)或正则表达式或任何你想要的,只要它去掉python而不是程序员在类上设置的属性
编辑2:为了好玩,这里有两个更“优雅”(虽然不太具体)的支票实现:
def __new__(cls, clsname, bases, attrs):
attr_names = {a for a in attrs if not is_dunder(a)}
if attr_names.difference(RequiredFieldsMeta._interface):
raise AttributeError('Class %s has extra properties' % clsname)
if RequiredFieldsMeta._interface.difference(attr_names):
raise AttributeError('Class %s missing required properties' % clsname)
return super(RequiredFieldsMeta, cls).__new__(cls, clsname, bases, attrs)
或者简单地说:
def __new__(cls, clsname, bases, attrs):
attr_names = {a for a in attrs if not is_dunder(a)}
if attr_names != RequiredFieldsMeta._interface:
raise AttributeError(
'Class %s does not match the required interface' % clsname)
return super(RequiredFieldsMeta, cls).__new__(cls, clsname, bases, attrs)
乍一看,您是否熟悉的概念?您可以创建一个“接口”类,该类继承自所需的各种方法上的
throw NotImplementedError
s。@MateenUlhaq它不会阻止程序员添加比“接口”类更多的属性,您可以在程序开始时进行快速运行时检查,以比较类。@MateenUlhaq您是指检查两个类的代码?没有pythonic类结构可以使用?啊,谢谢,我想这正是我需要的,但是代码没有按预期工作?MongoCompany抛出了一个错误。但是我明白了,在Python2和Python3之间,使用元类的语法发生了变化。你使用的是哪个版本?我相信是3.6.1,我正在运行你的快速演示。这是一个打字错误,还有其他的。。。。。我应该这样做。我刚才使用了w.startswith(“'uuu')
作为我的“is dunder”谓词。如果需要更严格的检查,也可以使用正则表达式或checkendswith
def __new__(cls, clsname, bases, attrs):
attr_names = {a for a in attrs if not is_dunder(a)}
if attr_names != RequiredFieldsMeta._interface:
raise AttributeError(
'Class %s does not match the required interface' % clsname)
return super(RequiredFieldsMeta, cls).__new__(cls, clsname, bases, attrs)