Проверка атрибута класса Python на __init__ - PullRequest
0 голосов
/ 05 июля 2018

Я пытаюсь проверить один атрибут моего класса, используя setter в приведенном ниже коде. Атрибут, который я хочу проверить, называется «__x» и имеет значение параметра, передаваемого в методе «init». Когда я изменяю «self__x» на «self.x», он работает так, как я ожидаю. Я хочу, чтобы он работал с «self.x», хотя я нигде не возвращал атрибут «x» в методах получения и установки, и почему он не работает с «self .__ x»?

class P:
    def __init__(self, x):
        self.__x = x  # not working
        # self.x = x  # working

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

    @x.setter
    def x(self, x):
        if x < 0:
            self.__x = 0
        else:
            self.__x = x


p = P(-5)
print(p.x)  # prints -5

Ответы [ 2 ]

0 голосов
/ 05 июля 2018

Давайте начнем с синтаксиса "@decorator". Это на самом деле только синтаксический сахар, так что

@decorate
def myfunc():
    pass

это просто сокращение для

def myfunc():
    pass

myfunc = decorate(myfunc)

Обратите внимание, что функции python также являются объектами (а также классами и модулями FWIW), поэтому вы можете передавать функции в качестве аргументов другим функциям, возвращать функции из функций, сохранять функции в качестве переменных или атрибутов и т. Д.

Теперь с классом property (да, это класс): это всего лишь общая реализация протокола descriptor , который представляет собой механизм python для поддержки вычисляемых атрибутов.

Наивная реализация Python property будет выглядеть примерно так (я игнорирую части fdel и __del__):

class propertytype(type):
    # this is what will allow you 
    # to use `property` as decorator,
    # it will return a new `property` instance
    # with `func` as setter
    def __call__(cls, func):
        return cls(func)

class property(metaclass=propertytype):

    def __init__(self, fget, fset=None):
        self.fget = fget
        self.fset = fset

   # this is the getter
   def __get__(self, instance, cls=None):
       if instance is None:
           return self
       return self.fget(instance)

   # this is the setter (if there's one)
   def __set__(self, instance, value):
       if not self.fset:
           raise AttributeError("Attribute is read-only")
       self.fset(instance, value)

   # and this allows you to use`@myprop.setter` 
   # in your class definition
   def setter(self, func):
       self.fset = func
       return self

И наконец: хотя рекомендуется создавать все атрибуты экземпляра объекта в инициализаторе (метод __init__), вы можете установить существующие или новые атрибуты просто в любом месте и в любое время. За исключением нескольких типов, которые (в основном из соображений реализации) используют совершенно другой способ хранения атрибутов (вы можете посмотреть slots, если хотите узнать больше об этом), обычные объекты Python, в основном, скрыты, поэтому myobj.foo = 'bar' обычно просто хранит 'bar' в self.__dict__['foo']. Ну, конечно, если вы не используете вычисляемые атрибуты;)

Хорошо, теперь у нас есть строительные блоки, давайте проанализируем, что происходит с вашим классом:

class P:
    # let's ignore the initializer for now

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

    @x.setter
    def x(self, x):
        if x < 0:
            self.__x = 0
        else:
            self.__x = x

Это можно переписать как

class P:
    # let's ignore the initializer for now

    def _getx(self):
        return self.__x

    def _setx(self):
        if x < 0:
            self.__x = 0
        else:
            self.__x = x

    x = property(_getx, setx)

Так что теперь с

p = P()

когда мы делаем:

p.x = 5

правила разрешения атрибутов (реализованные в object.__setattr__(self, name, value)) фактически будут искать «x» на «P», находить наше свойство «x», и, поскольку это дескриптор привязки (у него есть метод __set__), вызывать x.__set__(p, 5), который, в свою очередь, будет вызывать self.fset(p, 5) (ср. Определение property.__set__()), который будет вызывать p._setx(5).

А если мы вернули инициализатор:

class P:
    def __init__(self, x):
        self.x = x

    # getter / setter / property definition  here

затем происходит очень точное событие (за исключением того, что экземпляр P называется self вместо p) - в действительности он вызывает P._setx(self, x).

Единственное отличие от вашей первоначальной реализации заключается в том, что при использовании property имеется декоратор, функции получения и установки не становятся методами класса, они только живут как атрибуты fget и fset x объект недвижимости.

0 голосов
/ 05 июля 2018

Это так. Представьте, что есть школьный хулиган, давайте назовем его Дэн, который нацеливается на вас. Также есть Бет, которая тебе очень нравится. Обычно вы хотите избежать Дана и встретиться с Бет, но Дану все равно, и он будет бить вас по голове, если увидит вас.

Теперь ты тоже подружился с Джо. Он нежный великан. Очень хороший парень. Он говорит прийти к нему, и он позаботится о том, чтобы не впустить Дэна. Все это прекрасно работает: когда Дэн подходит к двери Джо, он отворачивается; когда приходит Бет, Джо впускает ее.

Ключевой момент таков: он работает только до тех пор, пока Дэн открывает дверь. Если вы слышите звонок в дверь и выходите сами , он больше не работает.


Итак, если вы сделаете self.x = -5, Джо проверит число, увидит, что это Дан, и отправит ему упаковку с нулем. Но если вы сделаете self.__x = -5, Джо никогда не увидит Дана. Вы получаете боб на голову.

self.__x - это просто переменная, она не может выполнять какую-либо проверку самостоятельно. self.x хотя и является функцией (две из них действительно, одна для чтения и одна для записи), и она может делать все, что захочет - установить self.__x или отказаться от нее.

...