Пусть у нас есть пример класса Foo в Python:
class Foo:
bar = 'bar'
def access_bar(self):
return self.bar
Могу ли я, например, вывести предупреждение при прямом доступе к Foo().bar, но в то же время не выводить это предупреждение при вызове Foo().access_bar(), который обращается к этому атрибуту из класса?
Я попытался реализовать метод __getattribute__, но не смог отличить эти случаи.
Я знаю, что это довольно странный вопрос, но, пожалуйста, не отвечайте мне типа «Вам это не нужно».
Похоже, у вас есть мотивация задать свой вопрос. Было бы полезно, если бы вы объяснили, почему это различие необходимо (даже если это просто любопытство!)
Вам действительно нужен атрибут класс, а не атрибут экземпляра (self.bar = 'bar')? Вы уверены, что знаете разницу?






вы можете сделать 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 об их использовании). Поэтому я пытаюсь реализовать способ, при котором мне не нужно использовать символы подчеркивания внутри класса, но пользователь получает предупреждение, когда пытается получить доступ к этому атрибуту извне.
@JosefKvita имейте в виду, что в python нет переменных частного класса. Как бы вы ни старались скрыть внутреннюю переменную, содержащую значение, кто-то может найти способ получить к ней прямой доступ. Если это попытка удержать других инженеров от доступа к конфиденциальным полям, возможно, вместо этого попробуйте поговорить с ними :)
Я знаю это. Я просто хочу напечатать предупреждение, а не сделать его недоступным. :)
@JosefKvita, почему ты часто обращаешься к __bar изнутри? Разве прямой доступ не должен быть только от access_bar? Если нет, мой ответ вам не поможет, он не различает внутри и снаружи класса.
Я предлагаю сохранить значение в защищенном (один начальный знак подчеркивания) или частном (два знака подчеркивания) атрибуте и сделать 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. Вам понадобится метакласс, если вы хотите, чтобы это работало и для атрибутов класса.
Я думаю, это именно то, что мне нужно, большое спасибо! Я попробую поиграть с ним. Я знаю, что должно было быть что-то, потому что все можно сделать на Python! :-)
хороший звонок, я даже не осмелился сказать ОП использовать для этого модуль проверки. "что вам, вероятно, не следует делать" дает вам мой голос :)
Теперь он проверяет, был ли атрибут вызван из-за пределов метода access_bar(), что, если я хочу проверить вне класса Foo (бар, доступ к которому осуществляется в любом методе внутри класса, не выводит предупреждение)?
Как насчет того, чтобы вместо использования inspect найти источник вызова __getattribute__, слегка модифицируя его, чтобы он научился печатать предупреждение? Я понятия не имею, насколько это хорошая идея.
Вот еще одна попытка улучшить ответ Алекса, добавив метакласс, чтобы он также работал для атрибутов класса, и покончив с модулем 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.
Верно. Я думаю, что это решение защищает вас от использования instance.bar везде, в том числе внутри класса. Это побуждает вас заменить self.bar на self.access_bar().
Foo().access_bar()не то же самое, чтоFoo.access_bar()