kak*_*eys 4 python oop interface class
我期待在Python中构建一个类接口,但发现Python缺少接口结构.
我需要的是,如果程序员试图在一个类中添加新属性而不在另一个类中添加具有相同名称的属性,则会引发异常(在编译时或运行时)
一个例子:
class MongoCompany:
company_name = MongoField()
class ESCompany:
company_name = ESField()
Run Code Online (Sandbox Code Playgroud)
如果程序员尝试在MongoCompany不更改的情况下添加字段,则会引发异常ESCompany.
class MongoCompany:
company_name = MongoField()
company_phone = MongoField()
class ESCompany:
company_name = ESField()
MongoCompany.init()
Run Code Online (Sandbox Code Playgroud)
编辑:
背景
这是为了防止程序员修改使用Mongoengine Document类声明的MongoDB模式,而不使用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'
Run Code Online (Sandbox Code Playgroud)
这是一个快速演示
注意我们如何甚至不进行实例化:我们的检查在定义类本身时运行.
编辑:在我的编辑中,我引用了一个函数is_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)
Run Code Online (Sandbox Code Playgroud)
或者干脆:
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)
Run Code Online (Sandbox Code Playgroud)