Действительно частные переменные в Python 3 - PullRequest
0 голосов
/ 14 декабря 2018

Итак, я знаю способ сделать переменную "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 ]

0 голосов
/ 08 января 2019

что мне нравится делать, хотя это не совсем на 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 всегда оставляет их открытыми, потому что они всегда могут быть доступны и изменены.

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

0 голосов
/ 14 декабря 2018

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

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 может видеть и замыкания.

0 голосов
/ 14 декабря 2018

Хорошо, посмотрев этот ответ о модуле 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 внутри класса, но создает ошибку, если вызывается извне.Это, вероятно, как можно ближе.

0 голосов
/ 14 декабря 2018

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

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

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


Ничто не является по-настоящему приватным в Python.Существуют способы сделать доступ затруднительным, но всегда есть способы обойти эти пути.

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

Этот тип разделения привилегий даже является одним из заявленных вариантов использования для библиотеки Pyro RPC.

0 голосов
/ 14 декабря 2018

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

В любом случае,Я хотел бы поделиться концепцией data descriptor, которая может сделать NEARLY закрытыми атрибутами, добавив слой прокси атрибута (как я уже сказал, это предотвратит доступ «внутри» класса):

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__ обе эти плохие практики, особенно последние, могут привести к бедствиям.

...