Различать доступ к атрибуту класса изнутри класса (или файла) и снаружи в python

Пусть у нас есть пример класса Foo в Python:

class Foo:
    bar = 'bar'
    def access_bar(self):
        return self.bar  

Могу ли я, например, вывести предупреждение при прямом доступе к Foo().bar, но в то же время не выводить это предупреждение при вызове Foo().access_bar(), который обращается к этому атрибуту из класса?

Я попытался реализовать метод __getattribute__, но не смог отличить эти случаи.

Я знаю, что это довольно странный вопрос, но, пожалуйста, не отвечайте мне типа «Вам это не нужно».

Foo().access_bar() не то же самое, что Foo.access_bar()
Adelin 20.02.2019 10:03

Похоже, у вас есть мотивация задать свой вопрос. Было бы полезно, если бы вы объяснили, почему это различие необходимо (даже если это просто любопытство!)

holdenweb 20.02.2019 10:04

Вам действительно нужен атрибут класс, а не атрибут экземпляра (self.bar = 'bar')? Вы уверены, что знаете разницу?

Alex Hall 20.02.2019 10:09
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
3
58
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

вы можете сделать bar свойство, которое позволяет контролировать доступ, не показывая вызов метода снаружи, и сделать свой атрибут закрытым:

class Foo:
    __bar = 'bar'
    @property
    def bar(self):
        print("direct access")
        return Foo.__bar
    def access_bar(self):
        return self.__bar

f = Foo()

print("warn",f.bar)
print("OK",f.access_bar())

печатает:

direct access
warn bar
OK bar

Я знаю этот путь. Но я часто обращаюсь к `__bar` изнутри, и мне не нравятся символы подчеркивания (и предупреждения PyCharm об их использовании). Поэтому я пытаюсь реализовать способ, при котором мне не нужно использовать символы подчеркивания внутри класса, но пользователь получает предупреждение, когда пытается получить доступ к этому атрибуту извне.

Josef Kvita 20.02.2019 10:20

@JosefKvita имейте в виду, что в python нет переменных частного класса. Как бы вы ни старались скрыть внутреннюю переменную, содержащую значение, кто-то может найти способ получить к ней прямой доступ. Если это попытка удержать других инженеров от доступа к конфиденциальным полям, возможно, вместо этого попробуйте поговорить с ними :)

darksky 20.02.2019 10:24

Я знаю это. Я просто хочу напечатать предупреждение, а не сделать его недоступным. :)

Josef Kvita 20.02.2019 10:32

@JosefKvita, почему ты часто обращаешься к __bar изнутри? Разве прямой доступ не должен быть только от access_bar? Если нет, мой ответ вам не поможет, он не различает внутри и снаружи класса.

Alex Hall 20.02.2019 10:39

Я предлагаю сохранить значение в защищенном (один начальный знак подчеркивания) или частном (два знака подчеркивания) атрибуте и сделать bar свойство, к которому можно безопасно получить доступ, эквивалент access_bar в вашем вопросе. Вот как такие вещи обычно делаются в Python.

class Foo:
    _bar = 'bar'

    @property
    def bar(self):
        # do extra things here
        return self._bar

Пользователь по-прежнему может написать foo._bar или foo._Foo__bar (для закрытого атрибута), чтобы получить атрибут извне без какого-либо предупреждения, но если он знает о соглашениях, связанных с начальными символами подчеркивания, он, вероятно, почувствует себя несколько некомфортно при этом и осознает риски.

Ответ принят как подходящий

Вот «настоящий» ответ на ваш вопрос, который вам, вероятно, не следует делать:

import inspect


class Foo:
    bar = 'bar'

    def access_bar(self):
        return self.bar

    def __getattribute__(self, item):
        if item == 'bar':
            code = inspect.currentframe().f_back.f_code
            if not (start_lineno <= code.co_firstlineno <= end_lineno
                    and code.co_filename == __file__):
                print('Warning: accessing bar directly')
        return super().__getattribute__(item)


lines, start_lineno = inspect.getsourcelines(Foo)
end_lineno = start_lineno + len(lines) - 1

print(1, Foo().bar)
print(2, Foo().access_bar())

Если вы делаете это, важно, чтобы в файле был только один класс с именем Foo, иначе inspect.getsourcelines(Foo) может не дать правильного результата.

print(Foo.bar) не выводит предупреждение. Это работает только с экземплярами класса Foo. Вам понадобится метакласс, если вы хотите, чтобы это работало и для атрибутов класса.
darksky 20.02.2019 10:29

Я думаю, это именно то, что мне нужно, большое спасибо! Я попробую поиграть с ним. Я знаю, что должно было быть что-то, потому что все можно сделать на Python! :-)

Josef Kvita 20.02.2019 10:33

хороший звонок, я даже не осмелился сказать ОП использовать для этого модуль проверки. "что вам, вероятно, не следует делать" дает вам мой голос :)

Jean-François Fabre 20.02.2019 10:35

Теперь он проверяет, был ли атрибут вызван из-за пределов метода access_bar(), что, если я хочу проверить вне класса Foo (бар, доступ к которому осуществляется в любом методе внутри класса, не выводит предупреждение)?

Josef Kvita 20.02.2019 10:42

Как насчет того, чтобы вместо использования inspect найти источник вызова __getattribute__, слегка модифицируя его, чтобы он научился печатать предупреждение? Я понятия не имею, насколько это хорошая идея.

darksky 20.02.2019 11:08

Вот еще одна попытка улучшить ответ Алекса, добавив метакласс, чтобы он также работал для атрибутов класса, и покончив с модулем inspect, а вместо этого добавив флаг предупреждения к самой функции __getattribute__.

class FooType(type):
    def __getattribute__(self, item):
        if item == "bar":
            print("Warning: accessing bar directly from class")
        return item.__getattribute__(self, item)

class Foo(object, metaclass=FooType):
    bar = 'bar'

    def access_bar(self):
        return self.__getattribute__('bar', warn=False)

    def __getattribute__(self, item, warn=True):
        if item == 'bar' and warn:
            print('Warning: accessing bar directly from instance')
        return super().__getattribute__(item)


print(Foo.bar)
#Warning: accessing bar directly from class
#bar

print(Foo().bar)
#Warning: accessing bar directly from instance
#bar

print(Foo().access_bar())
#bar

Плохо то, что я должен получить доступ к атрибуту везде внутри класса (например, в 100 местах) с помощью self.__getattribute__('bar', warn=False), а не только self.bar, или я ошибаюсь? Это точно хуже, чем self._bar.

Josef Kvita 20.02.2019 11:53

Верно. Я думаю, что это решение защищает вас от использования instance.bar везде, в том числе внутри класса. Это побуждает вас заменить self.bar на self.access_bar().

darksky 20.02.2019 11:54

Другие вопросы по теме