Как отличить свойство, устанавливаемое с помощью ___setattr__ внутри класса и вне класса?

Есть ли способ в __setattr__() различать атрибут, установленный внутри класса или дочернего/наследующего класса, и атрибут, установленный вне текущего или дочернего класса?

Я хочу изменить то, как установка атрибутов работает «извне», в моем случае создания модуля я хочу, чтобы у пользователя была другая логика при установке атрибута, чем когда он устанавливается внутри класса.

Например:
i.x = 5 должен обычно присваивать 5 при вызове из класса, а i является его экземпляром, но при вызове из другого класса он должен, скажем, вычитать 5 вместо установки в 5.


person laundmo    schedule 11.06.2019    source источник
comment
Этот ответ может помочь: stackoverflow.com/a/34065724   -  person quamrana    schedule 11.06.2019


Ответы (3)


Немного низкоуровневый, но вы можете использовать модуль inspect:

import inspect

class A:

    def __init__(self):
        self.__x = 0

    @property
    def x(self):
        return self.__x

    @x.setter
    def x(self, value):
        f = inspect.currentframe()
        if 'self' in f.f_back.f_locals and issubclass(type(f.f_back.f_locals['self']), A):
            print('Called from class!')
            self.__x = -value
        else:
            print('Called from outside!')
            self.__x = value

    def fn(self):
        print('Calling A.x from inside:')
        self.x = 10

class B(A):
    def __init__(self):
        super().__init__()

    def fn2(self):
        print('Calling B.x from inside:')
        self.x = 15

a = A()
print("A.x after init:", a.x)
print('Calling A.x from outside')
a.x = 10
print("A.x called from the outside:", a.x)
a.fn()
print("A.x called from the inside:", a.x)

b = B()
print("B.x after init:", b.x)
print('Calling B.x from outside')
b.x = 20
print("B.x called from the outside:", b.x)
b.fn2()
print("B.x called from the inside:", b.x)

Отпечатки:

A.x after init: 0
Calling A.x from outside
Called from outside!
A.x called from the outside: 10
Calling A.x from inside:
Called from class!
A.x called from the inside: -10
B.x after init: 0
Calling B.x from outside
Called from outside!
B.x called from the outside: 20
Calling B.x from inside:
Called from class!
B.x called from the inside: -15
person Andrej Kesely    schedule 11.06.2019
comment
делает именно то, что я хотел, и даже безупречно работает с подклассами. - person laundmo; 11.06.2019

Используйте свойство. Внутри класса вы можете назначить непосредственно базовому атрибуту. Вместо этого присваивания x уменьшают его.

class Foo:
    def __init__(self):
         self._x = 0

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        self._x -= value
person chepner    schedule 11.06.2019
comment
хотя технически это рабочее решение, другое решение делает то же самое без использования _x внутри класса. - person laundmo; 11.06.2019
comment
Я считаю отделение внешне видимого имени от основного атрибута особенностью, а не недостатком. - person chepner; 11.06.2019
comment
это именно то, что я хотел сделать БЕЗ необходимости изменять функции в подклассах, где используется этот атрибут. Принятый ответ делает именно то, о чем я просил, имея базовое разделение без фактического использования разных имен атрибутов. - person laundmo; 11.06.2019
comment
@laundmo: свойства наследуются, поэтому подклассы могут использовать простой x для неявного присвоения _x, как в базовом классе. Единственное место, где нужно использовать _x, — это функция @x.setter в базовом классе. - person martineau; 12.06.2019
comment
@martineau дело в том, что я хочу присвоить x напрямую, без вычитания, как в примере, когда это делается внутри класса, например, из метода, но когда класс, устанавливающий свойство, не связан с текущим, он делает вычитание. В предоставленном решении он будет выполнять вычитание, даже если он назначен из метода того же класса, если я не использую _x, который по причинам уже имеющегося тысячи строк кода мне нужно было бы изменить, я не хочу делать - person laundmo; 12.06.2019

Решение может состоять в том, чтобы всегда использовать self.__dict__ внутри класса без вызова метода __setattr__.

Пример:

class myClass:

    def __init__(self, value):
        self.__dict__['a'] = value

    def __setattr__(self, name, value):
        print("called from outside")
        if name == 'a':
            self.__dict__[name] = value - 5
        else:
            self.__dict__[name] = value


f = myClass(10)

print(f.a)
# 10

f.a = 20
print(f.a)
# called from outside
# 15
person abc    schedule 11.06.2019
comment
я пробовал ваше решение, но при установке self.a = 5 внутри класса он все еще думает, что он вызывается извне - person laundmo; 11.06.2019
comment
Вы должны использовать self.__dict__['a'] = 5 внутри класса, чтобы различать, если вы используете self.a = 5, вы вызовете метод __setattr__ - person abc; 11.06.2019
comment
ну, другое решение делает именно то, что я хотел, спасибо за попытку - person laundmo; 11.06.2019