确保两个Python类具有相同名称的属性

时间:2017-07-26 03:27:43

标签: python class oop interface

我期待在Python中构建一个类接口,但发现Python缺少接口结构。

我需要的是,如果程序员试图在一个类中添加新属性而不在另一个类中添加具有相同名称的属性,则会引发异常(在编译时或运行时)

一个例子:

class MongoCompany:
    company_name = MongoField()

class ESCompany:
    company_name = ESField()

如果程序员尝试将字段添加到MongoCompany而不更改ESCompany,则会引发异常。

class MongoCompany:
    company_name = MongoField()
    company_phone = MongoField()

class ESCompany:
    company_name = ESField()

MongoCompany.init()

修改

背景 这是为了防止程序员修改使用Mongoengine的Document类声明的MongoDB模式,而不用使用elasticsearch-dsl&s在另一个文件中声明的Elasticsearch'模式中添加相应的修改。 DocType上课。

1 个答案:

答案 0 :(得分:6)

耶!元类的实际应用并不仅仅是为了使用元类而设计的!我们可以编写一个元类,如果意外的属性出现在类定义中,它将抛出。我们需要做的就是确保你的程序员真正使用它。

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'

Here's快速演示

请注意我们甚至不进行实例化:我们的检查会在定义类本身时运行。

编辑:在我的编辑中,我引用了一个函数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)

或者简单地说:

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)