Действительно частные переменные в Python 3

Итак, я знаю способ сделать переменную "private" в python следующим образом:

class Foo:
    def __init__(self):
        self.__private = 'bar'

Это "работает" и не работает, как показано ниже:

foo = Foo()
'__private' in vars(foo) #False
'_Foo__private' in vars(foo) #True

Теперь я понимаю, что это способ делать частные переменные в Python, и мне нравится этот способ. Это позволяет вам манипулировать именами так, чтобы никакие подклассы случайно не переопределяли это (потому что оно начинается с имени класса), и что никто не будет случайно использовать его. Это также дает вам возможность изменять частные переменные, если вы знаете, что делаете. Кроме того, это лучший способ сделать это, потому что по-настоящему частные переменные невозможны.

Или я так думал.

Недавно я читал PEP 8 и увидел следующую строку:

Мы не используем здесь термин "приватный", поскольку в Python ни один атрибут не является действительно приватным (без обычно ненужного объема работы).

Эта цитата находится в разделе " Проектирование наследования" в PEP 8.

Обратите внимание на фразу "без обычно ненужного объема работы". Теперь я уверен, что в python должен быть способ получить действительно приватные переменные. Как бы я это сделал?

Я попытался переопределить __getattribute__, но проблема в том, что невозможно определить, поступает ли вызов изнутри класса или нет (о чем я знаю).

Так же __dict__ Атрибут раздражает при попытке сделать это, потому что он содержит ссылки на все переменные экземпляра.

Я также думал о метаклассах, но у них, похоже, те же проблемы, что и __getattribute__,

Мысли?


Примечание: я понимаю, что любой способ создания действительно закрытых переменных в python никогда не должен выполняться в производительном коде. Я просто хочу знать, как это можно сделать.

5 ответов

Решение

Я попытался переопределить getattribute, но проблема в том, что нет никакого способа узнать, поступает ли вызов изнутри класса или нет (о чем я знаю).

Вы можете использовать inspect модуль для поиска имени и модуля вызывающей функции, который можно сравнить с белым списком.

Но inspect также имеет getattr_static, который может обойти любого __getattribute__,


Ничто не является по-настоящему приватным в Python. Есть способы сделать доступ трудным, но всегда есть способы обойти эти пути. Тогда единственное решение - за пределами текущего интерпретатора Python. Вы можете использовать интерфейс сторонней функции к другому языку или даже к другому интерпретатору Python, выполняющемуся в подпроцессе. Закрытая переменная и все функции, которым разрешен доступ к ней, будут находиться за пределами текущего интерпретатора. Тогда нет возможности проверить это.

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

class Foo:
    def __init__(self):
        private = 'bar'
        def print_private():
            print(private)
        self.print_private = print_private

foo = Foo()
foo.print_private()  # works
foo.private  # kaboom

Конечно, inspect можно заглянуть в замыкания тоже.

Причина, по которой у Python нет личных атрибутов, заключается в том, что мы не можем определить, находится ли он внутри или вне класса. Они разделяют один и тот же процесс доступа к атрибутам. self.private это точно obj.private, Итак, если мы будем препятствовать obj.private, self.private также предотвращается. Единственный способ отличить их - дать другое имя и сделать obj.private быть доверенным лицом self._private от @property или же data descriptor и считаю, что люди, использующие его, все взрослые.

Во всяком случае, я хотел бы поделиться концепцией data descriptor который мог бы сделать НЕПРЕРЫВНЫЕ приватные атрибуты, добавив слой прокси атрибутов (как я уже сказал, это предотвратит доступ "внутри" класса):

class Private:
    def __init__(self, attribute):
        self.attribute = attribute

    def __get__(self, obj, type=None):
        raise AttributeError("'{}' object has no attribute '{}'".format(obj, self.attribute))

    def __set__(self, obj, value):
        obj.__dict__[self.attribute] = value

class YourClass:
    private = Private('private')

    def __init__(self):
        self.private = 10
        print(self.private)  # Raise AttributeError

Используйте двойное подчеркивание или измените __getattribute__обе плохие практики, особенно последние, могут привести к бедствиям.

Что -то, что мне нравится делать, хотя это не совсем на 100% приватно, это использовать замыкания в методах для R/W обычно недоступных атрибутов в качестве объектов member_descriptor:

def privateNS():

    class MyObject(object):
        __slots__ = ['private'] # name doesn't matter

        def __new__(cls, value): # only sets inst.private on new instance creation
            inst = object.__new__(cls)

            setprivate(inst, value)

            return inst

        # __init__ is not needed, and can't be used here to set inst.private

        def showprivate(inst):
            return getprivate(inst)

    dsc = MyObject.private # get descriptor
    getprivate = dsc.__get__
    setprivate = dsc.__set__
    del MyObject.private # revoke normal access

    return MyObject

MyObject = privateNS()
del privateNS

inst = MyObject( 20 )
print( inst.showprivate() ) # 20

обратите внимание, что имя inst.private не существует и вызовет AttributeError, если на него ссылаются.
но сам дескриптор члена существует и привязан к классу.

но, как я уже сказал, это не 100% личное...
Вы можете получить доступ к методам дескриптора, предоставленным методам класса через их замыкания:

>>> inst.showprivate.__closure__[0].cell_contents
<method-wrapper '__get__' of member_descriptor object at 0x00E588A0>

это первый бэкдор, если указанный метод содержит __set__ в замыканиях.
но если нет, 2-й черный ход немного сложнее:

>>> inst.showprivate.__closure__[0].cell_contents.__self__.__set__( inst, 30 )
>>> inst.showprivate()
30

Что-то, что помогает, хотя при использовании нескольких замыканий, порядок ячеек замыкания зависит от текущего прогона (как ключи словаря).

к сожалению, я не могу понять что-то более безопасное, чем это...

проблема, как указано в предыдущем ответе:
Атрибуты не могут сказать, где к ним обращаются, и предоставление такого уровня функциональности через код Python всегда оставляет их открытыми, потому что они всегда могут быть доступны и изменены.

если я ошибаюсь, пожалуйста, прокомментируйте:)

Хорошо, посмотрев на этот ответ о inspect модуль, я (вроде) сделал это!

class Foo:
    def __init__(self, private):
        self.private = private

    def __getattribute__(self, attr):
        import inspect
        frame = inspect.currentframe()
        try:
            back_self = frame.f_back.__self__
            if not back_self == self: #is it inside the class?
                ban = ('private', '__dict__') #all private vars, ban __dict__ for no loopholes
                if attr in ban:
                    msg = 'Foo object has no attribute {!r}'
                    raise AttributeError(msg.format(attr))
        finally:
            del frame
        return super().__getattribute__(attr)

    def print_private(self):
        print(self.private) #access in the class!


foo = Foo('hi')
foo.print_private() #output: hi
foo.private #makes an error

Ну, почти. inspect также может использоваться для поиска значения. Это очень близко, хотя. Это позволяет object.attr внутри класса, но создает ошибку, если вызывается извне. Это, вероятно, как можно ближе.

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