Использование __setattr__ и дескрипторов для класса Python - PullRequest
6 голосов
/ 06 февраля 2012

Я пишу класс Python, который использует __setattr__ и __getattr__ для обеспечения доступа к пользовательским атрибутам.

Однако некоторые атрибуты не могут быть обработаны общим способом, поэтому я надеялсяиспользуйте для них дескрипторы.

Проблема заключается в том, что для дескриптора дескриптор __get__ будет вызываться в пользу экземпляров __getattr__, но при присвоении атрибуту будет вызываться __setattr__в пользу дескрипторов __set__.

Пример:

class MyDesc(object):

    def __init__(self):
        self.val = None

    def __get__(self, instance, owner):
        print "MyDesc.__get__"
        return self.val

    def __set__(self, instance, value):
        print "MyDesc.__set__"
        self.val = value

class MyObj(object):

    foo = MyDesc()

    def __init__(self, bar):
        object.__setattr__(self, 'names', dict(
            bar=bar,
        ))
        object.__setattr__(self, 'new_names', dict())

    def __setattr__(self, name, value):
        print "MyObj.__setattr__ for %s" % name
        self.new_names[name] = value

    def __getattr__(self, name):
        print "MyObj.__getattr__ for %s" % name

        if name in self.new_names:
            return self.new_names[name]

        if name in self.names:
            return self.names[name]

        raise AttributeError(name)

if __name__ == "__main__":
    o = MyObj('bar-init')

    o.bar = 'baz'
    print o.bar

    o.foo = 'quux'
    print o.foo

печатает:

MyObj.__setattr__ for bar
MyObj.__getattr__ for bar
baz
MyObj.__setattr__ for foo
MyDesc.__get__
None

Дескриптор __set__ никогда не вызывается.

Поскольку определение __setattr__ не просто переопределяет поведение для ограниченного набора имен, нет четкого места, которое можно отложить до object.__setattr__

. Есть ли рекомендуемый способ назначения атрибутов?используйте дескриптор, если имеется, и __setattr__ в противном случае?

Ответы [ 2 ]

4 голосов
/ 06 февраля 2012

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

Этого легко достичь с помощью метакласса (и декоратора для __setattr__

def setattr_deco(setattr_func):
    def setattr_wrapper(self, attr, value):
        if attr in self._descriptors:
            return object.__setattr__(self, attr, value)
        return setattr_func(self, attr, value)
    return setattr_wrapper

class MiscSetattr(type):
    def __new__(metacls, name, bases, dct):
        descriptors = set()
        for key, obj in dct.items():
            if key == "__setattr__":
                dct[key] = setattr_deco(obj)
            elif hasattr(obj, "__get__"):
                descriptors.add(key)
        dct["_descriptors"] = descriptors
        return type.__new__(metacls, name, bases, dct)

# and use MiscSetattr as metaclass for your classes
4 голосов
/ 06 февраля 2012

Один из возможных способов:

def __setattr__(self, name, value):
    print "MyObj.__setattr__ for %s" % name
    for cls in self.__class__.__mro__ + (self, ):
        if name in cls.__dict__:
            return object.__setattr__(self, name, value)
    print 'New name', name, value
    self.new_names[name] = value

Он проверяет, определено ли имя уже в классе, базовых классах или экземпляре, а затем вызывает object.__setattr__, который выполнит дескриптор __set__.

* 1007.* Другой способ:
def __setattr__(self, name, value):
    print "MyObj.__setattr__ for %s" % name
    try:
        object.__getattribute__(self, name)
    except AttributeError:
        print 'New name', name, value
        self.new_names[name] = value
    else:
        object.__setattr__(self, name, value)

Но он вызовет дескриптор __get__.

PS

Я не уверен в необходимости проверять всех __mro__ членов, посколькуMyObj будет содержать унаследованные члены класса в __dict__.

Может быть, for cls in (self.__class__, self):... будет достаточно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...