Как использовать дескрипторы свойств по умолчанию и успешно назначить из __init__()?
Какова правильная идиома для этого, пожалуйста?
Я хочу определить объект, содержащий свойства, которые могут (необязательно) быть инициализированы из dict (dict происходит из JSON; он может быть неполным). Позже я могу изменить свойства через сеттеры. На самом деле существует более 13 свойств, и я хочу иметь возможность использовать методы получения и установки по умолчанию, но в данном случае это не работает:
Но я не хочу писать явные дескрипторы для всех prop1... propn
Кроме того, я хотел бы переместить назначения по умолчанию из __init__()
и в аксессоры... но тогда мне нужны точные дескрипторы.
Какое самое элегантное решение? (кроме перемещения всех вызовов сеттера из __init__()
и в метод / класс метод _make()
?)
[УДАЛЕННЫЙ КОММЕНТАРИЙ Код для badprop, использующий дескриптор по умолчанию, был из-за комментария предыдущего SO-пользователя, который произвел впечатление, что он дает вам установщик по умолчанию. Но это не так - сеттер не определен, и он обязательно выбрасывает AttributeError
.]
class DubiousPropertyExample(object):
def __init__(self,dct=None):
self.prop1 = 'some default'
self.prop2 = 'other default'
#self.badprop = 'This throws AttributeError: can\'t set attribute'
if dct is None: dct = dict() # or use defaultdict
for prop,val in dct.items():
self.__setattr__(prop,val)
# How do I do default property descriptors? this is wrong
#@property
#def badprop(self): pass
# Explicit descriptors for all properties - yukk
@property
def prop1(self): return self._prop1
@prop1.setter
def prop1(self,value): self._prop1 = value
@property
def prop2(self): return self._prop2
@prop2.setter
def prop2(self,value): self._prop2 = value
dub = DubiousPropertyExample({'prop2':'crashandburn'})
print dub.__dict__
# {'_prop2': 'crashandburn', '_prop1': 'some default'}
Если вы запустите это со строкой 5 self.badprop = ...
без комментариев, он терпит неудачу:
self.badprop = 'This throws AttributeError: can\'t set attribute'
AttributeError: невозможно установить атрибут
[Как всегда, я читаю SO сообщения на дескрипторы, неявные дескрипторы, вызывая их из init]
3 ответа
Я думаю, вы немного не понимаете, как работают свойства. Здесь нет "установщика по умолчанию". Это бросает AttributeError
на настройке badprop
не потому, что он еще не знает, что badprop
является свойством, а не обычным атрибутом (если бы это было так, он просто установил бы атрибут без ошибок, потому что это теперь нормальные атрибуты ведут себя), но потому что вы не предоставили установщик для badprop
только добытчик.
Посмотри на это:
>>> class Foo(object):
@property
def foo(self):
return self._foo
def __init__(self):
self._foo = 1
>>> f = Foo()
>>> f.foo = 2
Traceback (most recent call last):
File "<pyshell#12>", line 1, in <module>
f.foo = 2
AttributeError: can't set attribute
Вы не можете установить такой атрибут даже снаружи __init__
после того, как экземпляр построен. Если вы просто используете @property
, то, что у вас есть, это свойство только для чтения (фактически, вызов метода, который выглядит как атрибут read).
Если все, что вы делаете в своих методах получения и установки, - это перенаправление доступа на чтение / запись к атрибуту с тем же именем, но с добавлением подчеркивания, то, безусловно, самое простое, что нужно сделать - это полностью избавиться от свойств и просто использовать обычные атрибуты., Python - это не Java (и даже в Java я не уверен в достоинствах приватных полей с очевидным общедоступным getter /setter). Атрибут, который напрямую доступен для внешнего мира, является вполне разумной частью вашего "публичного" интерфейса. Если позже вы обнаружите, что вам нужно запускать некоторый код всякий раз, когда атрибут читается / пишется, вы можете сделать его свойством, не изменяя ваш интерфейс (это фактически то, для чего изначально предназначались дескрипторы, а не так, чтобы мы могли начать писать геттеры в стиле Java). /setters для каждого отдельного атрибута).
Если вы на самом деле делаете что-то в свойствах, отличных от изменения имени атрибута, и хотите, чтобы ваши атрибуты были доступны только для чтения, тогда, вероятно, лучше всего обрабатывать инициализацию в __init__
как непосредственная установка базовых атрибутов данных с добавлением подчеркивания. Тогда ваш класс может быть прямо инициализирован безAttributeErrors
и после этого свойства будут выполнять свои функции при чтении атрибутов.
Если вы на самом деле делаете что-то в свойствах, отличных от изменения имени атрибута, и вы хотите, чтобы ваши атрибуты были доступны для чтения и записи, вам нужно будет на самом деле указать, что происходит, когда вы получаете / устанавливаете их. Если у каждого атрибута есть независимое пользовательское поведение, то нет более эффективного способа сделать это, чем явное предоставление метода получения и установки для каждого атрибута.
Если вы используете точно такой же (или очень похожий) код в каждом отдельном методе получения / установки (и это не просто добавление подчеркивания к реальному имени атрибута), и именно поэтому вы возражаете против того, чтобы записать их все (правильно!) тогда вам лучше обслужить__getattr__
,__getattribute__
, а также __setattr__
, Они позволяют перенаправлять чтение / запись атрибута каждый раз в один и тот же код (с именем атрибута в качестве параметра), а не на две функции для каждого атрибута (получение / настройка).
Кажется, что самый простой способ сделать это - просто реализовать __getattr__
а также __setattr__
так что они получат доступ к любому ключу в вашем разобранном файле JSON, который вы должны установить в качестве члена экземпляра. Или вы можете позвонить update()
на self.__dict__
с вашим разобранным JSON, но это не самый лучший способ действовать, так как это означает, что ваш вводный дикт может потенциально растоптать членов вашего экземпляра.
Что касается ваших сеттеров и геттеров, вы должны создавать их только в том случае, если они действительно делают что-то особенное, кроме прямой установки или извлечения рассматриваемого значения. Python - это не Java (или C++, или что-то еще), вы не должны пытаться имитировать частную /set/get парадигму, которая распространена в этих языках.
Я просто помещаю dict в локальную область видимости и получаю / устанавливаю там свои свойства.
class test(object):
def __init__(self,**kwargs):
self.kwargs = kwargs
#self.value = 20 asign from init is possible
@property
def value(self):
if self.kwargs.get('value') == None:
self.kwargs.update(value=0)#default
return self.kwargs.get('value')
@value.setter
def value(self,v):
print(v) #do something with v
self.kwargs.update(value=v)
x = test()
print(x.value)
x.value = 10
x.value = 5
Выход
0
10
5