Python: Как передать более одного аргумента в средство получения свойств?

#python #properties #language-features

#python #свойства #язык-особенности

Вопрос:

Рассмотрим следующий пример:

 class A:
    @property
    def x(self): return 5
  

Итак, конечно, вызов a = A(); a.x вернет 5

Но представьте, что вы хотите иметь возможность изменять свойство x.
Таким образом, например:

 class A:
    @property
    def x(self, neg = False): return 5 if not neg else -5
  

И вызвать его с a = A(); a.x(neg=True)

Это вызовет ошибку типа: 'int' object is not callable , что вполне нормально, поскольку our x оценивается как 5 .

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

Ответ №1:

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

 class A:
    def get_x(self, neg=False):
        return -5 if neg else 5
    x = property(get_x)

>>> a = A()
>>> a.x
5
>>> a.get_x()
5
>>> a.get_x(True)
-5
  

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

Комментарии:

1. Чувак … это на самом деле блестящая идея. Хотелось бы, чтобы другие API использовали это вместо только установщиков-getters / only properties.

2. Больше декораторов? Все они поддерживают использование таким образом, поскольку синтаксис декоратора — это просто сахар для вызова функции и повторной привязки имени.

3. О, я понимаю, что вы имеете в виду — больше API, предоставляющих как методы установки / получения, так и связанные с ними свойства.

4. Идея инкапсуляции здесь отсутствует. Нет смысла использовать геттеры таким образом. Там нарушен интерфейс.

5. Я бы использовал вместо x = property(get_x) , @property def x(self): return self.get_x() . Более читаемый.

Ответ №2:

Я думаю, вы не до конца поняли назначение свойств.

Если вы создадите свойство x , вы получите к нему доступ с помощью obj.x вместо obj.x() . После создания свойства нелегко вызвать базовую функцию напрямую.

Если вы хотите передать аргументы, назовите свой метод get_x и не делайте его свойством:

 def get_x(self, neg=False):
    return 5 if not neg else -5
  

Если вы хотите создать средство установки, сделайте это следующим образом:

 class A:
    @property
    def x(self): return 5

    @x.setter
    def x(self, value): self._x = value
  

Комментарии:

1. Я знаю! И вы можете видеть a.x вызов в моем первом примере.

2. @Rizo: свойства просто не предназначены для этого.

Ответ №3:

свойство должно зависеть только от связанного объекта. Если вы хотите использовать некоторые внешние параметры, вам следует использовать методы.

Комментарии:

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

2. @pentanol Я бы сказал, что метод свойств — это не место для кода аутентификации API.

Ответ №4:

В вашем втором примере вы используете a.x() так, как если бы это была функция: a.x(neg=True) . Имея это в виду, почему бы просто не определить это как функцию?

Комментарии:

1. Это прямое решение, но я использую очень сложные средства получения и установки и хотел бы продолжать использовать их без необходимости писать отдельные функции для атрибутов.

2. Это не отвечает на мой вопрос. Теперь Я могу использовать функции на python! 🙂 Я просто хочу знать, есть ли какой-нибудь способ заставить свойство действовать как функция.

3. @Rizo почему? Если вы хотите, чтобы это действовало как функция, тогда используйте функцию (метод). Но в ответ на ваш вопрос, единственным способом было бы вернуть вызываемый объект из свойства, которое принимает параметр. Тогда у вас было бы что-то, что выглядит точно так же, как функция, но менее эффективно.

4. Если средство получения делает что-либо, кроме возврата простого значения (или значения по умолчанию), это больше не «средство получения», а обычная функция.

5. @Rizo: в примере, который вы приводите, вероятно, разумнее просто позволить вызывающему пользователю решать, что делать с возвращаемым значением. в конце концов, это вызывающий объект, передающий условие.

Ответ №5:

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

 a = A()
assert a.x == 5
assert A.x.fget(a, True) == -5
  

Как упоминали другие, это не рекомендуется.

Ответ №6:

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

 class A:
    @property
    def x(self):
        return self._x(neg = False)

    @property
    def x_neg(self):
        return self._x(neg = True)

    def _x(self, neg):
        return 5 if not neg else -5
  

Ответ №7:

Я только что столкнулся с этой проблемой. У меня есть класс Polynomial(), и я определяю градиент, который я хотел бы возвращать функцией, если нет аргументов, или оценивать, если есть аргументы. Я хочу сохранить градиент как атрибут, чтобы мне не нужно было вычислять его каждый раз, когда мне нужно его использовать, и я хочу использовать @property, чтобы атрибут gradient вычислялся лениво. Мое решение состояло в том, чтобы определить класс Gradient с определенным методом вызова для возвращаемого свойства grad полинома.

 @property
def grad(self):
    """
    returns gradient vector
    """
    class Gradient(list):
        def __call__(self, *args, **kwargs):
            res = []
            for partial_derivative in g:
                res.append(partial_derivative(*args, **kwargs))
            return res
    g = Gradient()
    for i in range(1, len(self.term_matrix[0])):
        g.append(self.derivative(self.term_matrix[0][i]))
    return g
  

И тогда у меня следующие тесты успешно проходят:

 def test_gradient(self):
    f = Polynomial('x^2y   y^3   xy^3')
    self.assertEqual(f.grad, [Polynomial('2xy   y^3'), Polynomial('x^2   3xy^2   3y^2')])
    self.assertEqual(f.grad(x=1, y=2), [12, 25])
    f = Polynomial('x^2')
    self.assertEqual(f.grad(1), [2])
  

Итак, для решения этой проблемы мы могли бы попробовать:

 class A:
    @property
    def x(self):
        class ReturnClass(int):
            def __call__(self, neg=False):
                if not neg:
                    return 5
                return -5
        return ReturnClass()
  

Комментарии:

1. Не уверен, что решение работает: если я скажу, a = A(); print(a.x) я получу 0 в результате — значение по умолчанию для неинициализированного int. Решение — насколько я понимаю — должно быть 5 для a.x .