2015-02-09 1 views
3

У меня есть дескрипторы данных, работающих для объектов с __set__ и __get__.Способ иметь дескриптор типа уровня класса с __set__

Кажется, что дескрипторы классов не поддерживают __set__. Выполнение этого вместо этого заменяет сам объект дескриптора назначенным значением.

Следующий код демонстрирует это

from __future__ import print_function 

class Descriptor(object): 
    def __get__(self, obj, cls): 
     print('__get__') 
    def __set__(self, obj, value): 
     print('__set__') 

class Class(object): 
    descriptor = Descriptor() 

print('Object') 
a = Class() 
a.descriptor 
a.descriptor = 1 

print('Class') 
Class.descriptor 
Class.descriptor = 2 

Какие выходы

Object 
__get__ 
__set__ 
Class 
__get__ 

Как вы можете видеть, уровень класса __set__ не дозвонились.

Есть ли способ обхода или взлома (независимо от того, насколько ужасно), что позволит дескриптор данных __set__ на классе?

Просто, чтобы быть ясным, я не хочу, чтобы код вызывал, чтобы реализовать любой «взлом». Я хочу, чтобы вызывающий код работал так, как ожидалось выше, но любой взлом «за кулисами».

Использование Python 2.7

ответ

5

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

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

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

class Descriptor(object): 
    def __get__(self, obj, cls): 
     print('__get__') 
    def __set__(self, obj, value): 
     print('__set__') 

class MetaClass(type): 
    descriptor = Descriptor() 

class Class(object): 
    __metaclass__ = MetaClass 

# This will work fine when you do Class.descriptor, as you asked 
# but it will raise an AttributeError if you do 
# a = Class() 
# a.descriptor 
# Read on for the full explanation... 

descriptor переменных, определенные в MetaClass виден только Class класса. Любые экземпляры Class, которые попытаются позвонить ему, предоставят вам AttributeError. Это связано с тем, что экземпляры классов при поиске атрибута ищут свой собственный __dict__ перед поиском класса __dict__, но он не будет искать до __metaclass__ класса. Теперь, если вы хотите иметь оба варианта и использовать то же имя переменной как для класса и его экземпляры (хотя я бы не рекомендовал его, как было бы пригласить к путанице), вы можете сделать это:

class Descriptor(object): 
    def __get__(self, obj, cls): 
     print('__get__') 
    def __set__(self, obj, value): 
     print('__set__') 

class MetaClass(type): 
    descriptor = Descriptor() 

class Class(object): 
    __metaclass__ = MetaClass 
    descriptor = Descriptor() 

На данный момент вам может быть интересно: , если экземпляр ищет свой собственный __dict__ перед поиском его класса, как получается, что вызов «Class.descriptor» не будет выбирать тот же дескриптор, который использует «a.descriptor» (который, как вы видели, не будет работать должным образом), если Class.descriptor по существу является его собственной переменной экземпляра (из метакласса POV)?

Ответ заключается в том дескрипторы данных (дескрипторы, которые имеют как __get__ и __set__ определены), в отличие от дескрипторов не-данных (которые только определенных __get__), имеют приоритет над переменными экземпляра. Другими словами, переменная descriptor в MetaClass будет равна Class, потому что она имеет приоритет над Class собственной переменной descriptor. То же самое относится к экземпляру Class, который автоматически подбирает переменную descriptor, определенную в Class.

Надеюсь, я вас не смутил. Этот материал легко забыть, я думаю, вдвойне, потому что это не очень распространено и не нужно понимать этот уровень магии большую часть времени. Я должен был освежить эту память! Хороший вопрос :)

+0

Фантастический, только то, что мне нужно =) – Rebs

+0

Чтобы уточнить: при разрешении 'a.descriptor' язык не выполняет эквивалент' getattr (type (a), 'descriptor') ', которые могут быть перехвачены метаклассом. Вместо этого он «type (a) .__ dict __ ['descriptor']'. (Источник: https://docs.python.org/2/reference/datamodel.html#invoking-descriptors и 'PyObject_GenericSetAttr' в https://docs.python.org/2/c-api/object.html подтверждает что это также относится к '__set__'). – mhsmith

Смежные вопросы