свойства сеттера () в метаклассе - PullRequest
0 голосов
/ 10 июня 2018

Я пытаюсь использовать property() в своем метаклассе, чтобы дать способ доступа / установки внутреннего атрибута.Я использую property() вместо @property, так как он находится внутри метакласса, и мне нужно применить свойство к классу, переданному в методе __new__.

Это базовый примермоего кода с соответствующими частями:

def __new__(mcls, name, bases, dct, **kwargs):

    def getabstract(obj):
        return getattr(obj, '_abstract', False)


    def setabstract(obj, value):
        if str(value) in ('True', 'False'):
            return setattr(obj, '_abstract', value)
        else: print(f'invalid abstract assignment {value}')
        return None        

    cls = super().__new__(mcls, name, bases, dct)

    for name, value in cls.__dict__.items():
        if callable(value):
            # only applies to callable (method)
            value._abstract = getattr(value, '_abstract', False)
            # add an _abstract attribute to the method or return its
            # current value if it already has one

    for base in bases:
        base._abstract = getattr(base, '_abstract', False)
        # give each base class an _abstract attribute if not already given

    cls.abstract = property(getabstract(cls),
                            setabstract(cls, getattr(cls, '_abstract', False)))
    # make an abstract property for class
    for name, value in cls.__dict__.items():
        if callable(value):
            # make an abstract property for functions
            value.abstract = property(getabstract(value),
                                      setabstract(value, getattr(value, '_abstract', False)))

Когда я запускаю это, никаких ошибок не возникает, но при доступе к новому классу, созданному этим метаклассом, например Foo.abstract, возвращается:

    <property object at 0x.....>

Кроме того, функция setabstract(), используемая в качестве установщика, устанавливает атрибут только, если он True или False, но когда я делаю что-то вроде Foo.abstract = 'foo', он все равно устанавливает значение 'foo'

Я что-то здесь не так делаю или что-то упустил?

1 Ответ

0 голосов
/ 11 июня 2018

Свойство не является атрибутом экземпляра, это атрибут дескриптор класса, который привязан к объекту, который получил к нему доступ.Вот упрощенный пример:

class A:
    @property
    def x(self):
        return True

print(A.x) # <property object at 0x0000021AE2944548>
print(A().x) # True

Получая A.x, вы получаете несвязанное свойство.При получении A().x свойство привязывается к экземпляру, а A.x.__get__ возвращает значение.

Назад к вашему коду

Это означает, что свойство должно бытьатрибут класса , а не атрибут экземпляра.Или, в вашем случае, свойство должно быть атрибутом metaclass .

class Meta(type):
    @property
    def abstract(cls):
        return getattr(cls, '_abstract', False)

    @abstract.setter
    def abstract(cls, value):
        if value in (True, False):
            setattr(cls, '_abstract', value)
        else:
            raise TypeError(f'invalid abstract assignment {value}')

class Foo(metaclass=Meta):
    pass

print(Foo.abstract) # False
Foo.abstract = 'Not at bool' # TypeError: invalid abstract assignment Not at bool

Хотя это только частично решает вашу проблему, так как вы хотите, чтобы у каждого метода ваших классов был abstract имущество.Для этого вам понадобится их класс , чтобы иметь это property.

Прежде чем мы углубимся, позвольте мне напомнить вам одну ключевую концепцию в Python: we 'Здесь все взрослые по согласию .Может быть, вы должны просто доверить своим пользователям, чтобы они не устанавливали abstract на что-либо другое, кроме bool, и позволяли ему быть атрибутом, не относящимся к свойствуВ частности, любой пользователь может изменить сам атрибут _abstract в любом случае.

Если это вас не устраивает и вы хотите, чтобы абстрактный был property, то один из способов сделать это - определить класс-оболочкудля методов, которые удерживают это property.

class Abstract:
    @property
    def abstract(cls):
        return getattr(cls, '_abstract', False)

    @abstract.setter
    def abstract(cls, value):
        if value in (True, False):
            setattr(cls, '_abstract', value)
        else:
            raise TypeError(f'invalid abstract assignment {value}')

class AbstractCallable(Abstract):
    def __init__(self, f):
        self.callable = f

    def __call__(self, *args, **kwargs):
        return self.callable(*args, **kwargs)

class Meta(type, Abstract):
    def __new__(mcls, name, bases, dct, **kwargs):
        for name, value in dct.items():
            if callable(value):
                dct[name] = AbstractCallable(value)

        return super().__new__(mcls, name, bases, dct)

class Foo(metaclass=Meta):
    def bar(self):
        pass

print(Foo.abstract) # False
print(Foo.bar.abstract) # False
Foo.bar.abstract = 'baz' # TypeError: invalid abstract assignment baz
...