! , ! , , . , , , .
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)
class MongoCompany(metaclass=RequiredFieldsMeta):
company_name = 'Mongo Inc.'
num_employees = 100
class ESyCompany(metaclass=RequiredFieldsMeta):
extra_prop = 'foobar'
, : , .
EDIT: is_dunder. , name.startswith('__') , , python, , .
EDIT 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)