Могу ли я получить ссылку на свойство Python? - PullRequest
44 голосов
/ 10 сентября 2010

Если у меня есть это:

class foo(object):
    @property
    def bar(self):
        return 0

f = foo()

Как получить ссылку на f.bar без фактического вызова метода, если это вообще возможно?

Отредактировано, чтобы добавить: ЧтоЯ хочу сделать, это написать функцию, которая перебирает членов f и делает что-то с ними (что не важно).Свойства сбивают меня с толку, потому что простое присвоение им имени в getattr () вызывает их метод __get __ ().

Ответы [ 6 ]

35 голосов
/ 10 сентября 2010

get_dict_attr (ниже) ищет attr в данном объекте __dict__ и возвращает соответствующее значение, если оно есть. Если attr не является ключом в __dict__, выполняется поиск MRO __dict__ объекта. Если ключ не найден, AttributeError поднимается.

def get_dict_attr(obj, attr):
    for obj in [obj] + obj.__class__.mro():
        if attr in obj.__dict__:
            return obj.__dict__[attr]
    raise AttributeError

Например,

class Foo(object):
    x=1
    def bar(self):
        pass
    @property
    def baz(self):
        return 0

foo=Foo()
print(get_dict_attr(foo,'x'))
# 1
print(get_dict_attr(foo,'bar'))
# <unbound method Foo.bar>
print(get_dict_attr(foo,'baz'))
# <property object at 0xb77c0dc4>
print(get_dict_attr(foo,'y'))
# AttributeError

Обратите внимание, что это сильно отличается от обычных правил поиска атрибутов. С одной стороны, дескрипторы данных в obj.__class__.__dict__ (дескрипторы с методами __get__ и __set__) обычно имеют приоритет над значениями в obj.__dict__. В get_dict_attr, obj.__dict__ имеет приоритет.

get_dict_attr не пытается позвонить __getattr__.

Наконец, get_dict_attr будет работать только с объектами obj, которые являются экземплярами классов нового стиля.

Тем не менее, я надеюсь, что это поможет.


class Foo(object):
    @property
    def bar(self):
        return 0

f = Foo()

Ссылка на свойство bar:

print(Foo.bar)
# <property object at 0xb76d1d9c>

Вы видите, bar - это ключ Foo.__dict__:

print(Foo.__dict__['bar'])
# <property object at 0xb775dbbc>

Все свойства являются дескрипторами, что предполагает использование метода __get__:

print(Foo.bar.__get__)
# <method-wrapper '__get__' of property object at 0xb76d7d74>

Вы можете вызвать метод, передав объект f и класс f в качестве аргументов:

print(Foo.bar.__get__(f,Foo))
# 0

Мне нравится следующая диаграмма. Вертикальные линии показывают связь между объектом и классом объекта.

Если у вас есть такая ситуация:

   Foo                                B
   | Foo.__dict__={'bar':b}           | B.__dict__={'__get__':...}
   |                      \           |      
   f                       `--------> b

f.bar вызывает b.__get__(f,Foo) для вызова.

Это подробно объясняется здесь .

1 голос
/ 21 сентября 2015

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

Моя ситуация была немного другой, так как вместо просто @property я смешивал в специальном декораторе, который добавляет атрибуты к обернутому методу. Так, например, обычно у меня было бы что-то вроде этого:

class Foo:
    @my_decorator
    def bar(self):
        return do_stuff()  # Pretend this is non-trivial

Мой @my_decorator тогда даст мне доступ к self.bar.my_attribute, в котором есть некоторые данные, к которым мне иногда нужно обращаться. Это прекрасно работает для функций и методов, но я столкнулся с проблемой при попытке смешать его с @property, потому что свойство скрывает ссылку на метод (self.bar.my_attribute не удается, потому что self.bar возвращает возвращаемое значение метода, которое не у меня нет my_attribute, который я хочу).

Я бы использовал @property в качестве внешнего декоратора, вот так:

class Foo:
    @property
    @my_decorator
    def bar(self):
        return do_stuff()  # Pretend this is non-trivial

И тогда решение состоит в том, чтобы получить доступ к my_attribute как Foo.bar.fget.my_attribute (важная деталь здесь заключается в том, что доступ к свойству из класса возвращает объект свойства, тогда как доступ к свойству из экземпляра просто вызывает метод и возвращает значение , без доступа к ссылке на оригинальный метод).

TL; DR: Если вам нужна ссылка на метод, который предоставляет ваш @property, вам нужно использовать YourClassName.your_property.fget.

1 голос
/ 22 апреля 2015

В вашем случае,

class foo(object):
    @property
    def bar(self):
        return 0
f = foo()

Вы можете получить доступ к свойству через словарь класса:

f.__class__.__dict__['bar']
=> <property at 0x4920c28>

Как видите, это экземпляр property.

isinstance(f.__class__.__dict__['bar'], property)
=> True

Вы можете получить доступ к его методу-получателю (который return 0) через fget следующим образом:

f.__class__.__dict__['bar'].fget(f)
=> 0

Обратите внимание, что вы должны предоставить экземпляр f дляfget() потому что это метод экземпляра (не статический), и вы получаете к нему доступ через __class__.

0 голосов
/ 13 апреля 2017
class A:
    prop = property(lambda self: 'get', lambda self, x: print('set', x))

setter = A.prop.fset
getter = A.prop.fget


my_a = A()

setter(my_a, 5)
print(getter(my_a))
0 голосов
/ 16 декабря 2012

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

Вы можете получить ссылку на имущество просто по foo.bar

Что касается того, что вы пытаетесь выполнить, перебирая членов f, подробности смотрите ниже.

Длинный ответ: вы должны понять, что методы в Python не принадлежат экземплярам, ​​а являются атрибутами объекта класса. Например,

class Foo:
    def bar(self):
        pass
foo = Foo()

если вы вызываете foo.bar(), Python сначала проверяет, есть ли у foo значение bar (нет), затем проверяет, имеет ли Foo значение bar, что он и делает. Затем Python «связывает» foo.bar с Foo.bar(foo) и вызывает это.

То же самое верно для свойств. Я не знаю точный процесс (и я думаю, что он отличается в разных реализациях), но, по сути, Python ищет foo.bar, а затем Foo.bar, видит, что это свойство, поэтому оценивает его getter и возвращает его.

0 голосов
/ 11 сентября 2010

Одна вещь, которую вы должны знать, это то, что дескрипторы данных (то есть свойства) работают только тогда, когда они применяются к классам (в новом стиле) (см. http://docs.python.org/reference/datamodel.html#implementing-descriptors).. Копирование их в объект не создаст свойство для этогообъект. Вам нужно скопировать их в (новый стиль) класс, чтобы вступить в силу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...