Как динамически добавить свойство в класс?
Цель состоит в том, чтобы создать фиктивный класс, который ведет себя как набор результатов db.
Так, например, если запрос к базе данных возвращается, используя выражение dict, {'ab':100, 'cd':200}
тогда я бы хотел увидеть:
>>> dummy.ab
100
Сначала я подумал, может быть, я мог бы сделать это так:
ks = ['ab', 'cd']
vs = [12, 34]
class C(dict):
def __init__(self, ks, vs):
for i, k in enumerate(ks):
self[k] = vs[i]
setattr(self, k, property(lambda x: vs[i], self.fn_readyonly))
def fn_readonly(self, v)
raise "It is ready only"
if __name__ == "__main__":
c = C(ks, vs)
print c.ab
но c.ab
вместо этого возвращает объект свойства.
Замена setattr
линия с k = property(lambda x: vs[i])
бесполезен вообще.
Итак, как правильно создать свойство экземпляра во время выполнения?
PS Я знаю об альтернативе, представленной в __getattribute__
метод используется?
25 ответов
Полагаю, мне следует расширить этот ответ, теперь, когда я стал старше и мудрее и знаю, что происходит. Лучше поздно, чем никогда.
Вы можете добавить свойство к классу динамически. Но в этом-то и суть: вы должны добавить это в класс.
>>> class Foo(object):
... pass
...
>>> foo = Foo()
>>> foo.a = 3
>>> Foo.b = property(lambda self: self.a + 1)
>>> foo.b
4
property
на самом деле простая реализация вещи, называемой дескриптором. Это объект, который обеспечивает пользовательскую обработку для данного атрибута в данном классе. Вроде как способ факторинга огромного if
дерево из __getattribute__
,
Когда я прошу foo.b
в приведенном выше примере Python видит, что b
определенный в классе реализует протокол дескриптора - который просто означает, что это объект с __get__
, __set__
, или же __delete__
метод. Дескриптор отвечает за обработку этого атрибута, поэтому Python вызывает Foo.b.__get__(foo, Foo)
и возвращаемое значение передается обратно вам как значение атрибута. В случае property
каждый из этих методов просто вызывает fget
, fset
, или же fdel
Вы перешли к property
конструктор.
Дескрипторы - это на самом деле способ Python, демонстрирующий всю реализацию OO. На самом деле, есть еще один тип дескриптора, даже более распространенного, чем property
,
>>> class Foo(object):
... def bar(self):
... pass
...
>>> Foo().bar
<bound method Foo.bar of <__main__.Foo object at 0x7f2a439d5dd0>>
>>> Foo().bar.__get__
<method-wrapper '__get__' of instancemethod object at 0x7f2a43a8a5a0>
Скромный метод - просто еще один вид дескриптора. это __get__
привязывает вызывающий экземпляр в качестве первого аргумента; по сути, он делает это:
def __get__(self, instance, owner):
return functools.partial(self.function, instance)
Во всяком случае, я подозреваю, что именно поэтому дескрипторы работают только с классами: они являются формализацией того, что обеспечивает классы в первую очередь. Они даже являются исключением из правила: очевидно, вы можете назначать дескрипторы классу, а сами классы являются экземплярами type
! На самом деле, пытаясь прочитать Foo.b
все еще звонит property.__get__
; для дескрипторов идиоматично возвращать себя, когда к ним обращаются как к атрибутам класса.
Я думаю, что это здорово, что практически вся ОО-система Python может быть выражена на Python.:)
О, и я написал долгую статью в блоге о дескрипторах некоторое время назад, если вам интересно.
Цель состоит в том, чтобы создать фиктивный класс, который ведет себя как набор результатов db.
Итак, что вы хотите - это словарь, в котором вы можете написать a['b'] как ab?
Это легко:
class atdict(dict):
__getattr__= dict.__getitem__
__setattr__= dict.__setitem__
__delattr__= dict.__delitem__
Вам не нужно использовать свойство для этого. Просто переопределить __setattr__
сделать их только для чтения.
class C(object):
def __init__(self, keys, values):
for (key, value) in zip(keys, values):
self.__dict__[key] = value
def __setattr__(self, name, value):
raise Exception("It is read only!")
Тад.
>>> c = C('abc', [1,2,3])
>>> c.a
1
>>> c.b
2
>>> c.c
3
>>> c.d
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'C' object has no attribute 'd'
>>> c.d = 42
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in __setattr__
Exception: It is read only!
>>> c.a = 'blah'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in __setattr__
Exception: It is read only!
Кажется, вы могли бы решить эту проблему гораздо проще с namedtuple
, так как вы знаете весь список полей заранее.
from collections import namedtuple
Foo = namedtuple('Foo', ['bar', 'quux'])
foo = Foo(bar=13, quux=74)
print foo.bar, foo.quux
foo2 = Foo() # error
Если вам абсолютно необходимо написать собственный установщик, вам придется выполнять метапрограммирование на уровне класса; property()
не работает на экземплярах.
Как динамически добавить свойство в класс Python?
Допустим, у вас есть объект, к которому вы хотите добавить свойство. Как правило, я хочу использовать свойства, когда мне нужно начать управлять доступом к атрибуту в коде, который используется в нисходящем направлении, чтобы я мог поддерживать согласованный API. Теперь я обычно добавляю их в исходный код, в котором определен объект, но давайте предположим, что у вас нет такого доступа, или вам нужно действительно динамически выбирать свои функции программно.
Создать класс
Используя пример на основе документации дляproperty
Давайте создадим класс объекта со скрытым атрибутом и создадим его экземпляр:
class C(object):
'''basic class'''
_x = None
o = C()
В Python мы ожидаем, что будет один очевидный способ сделать что-то. Однако в этом случае я собираюсь показать два способа: с обозначением декоратора и без. Во-первых, без обозначения декоратора. Это может быть более полезно для динамического назначения методов получения, установки или удаления.
Динамический (он же Monkey Patching)
Давайте создадим некоторые для нашего класса:
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
И теперь мы назначаем их на собственность. Обратите внимание, что мы могли бы выбрать наши функции программно здесь, отвечая на динамический вопрос:
C.x = property(getx, setx, delx, "I'm the 'x' property.")
И использование:
>>> o.x = 'foo'
>>> o.x
'foo'
>>> del o.x
>>> print(o.x)
None
>>> help(C.x)
Help on property:
I'm the 'x' property.
Декораторы
Мы могли бы сделать то же самое, что мы делали выше с нотацией декоратора, но в этом случае мы должны присвоить методам одно и то же имя (и я бы рекомендовал оставить его таким же, как у атрибута), так что программное назначение не так тривиально, как он использует вышеуказанный метод:
@property
def x(self):
'''I'm the 'x' property.'''
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
И присвойте объекту свойства с предоставленными ему установщиками и удалителями класс:
C.x = x
И использование:
>>> help(C.x)
Help on property:
I'm the 'x' property.
>>> o.x
>>> o.x = 'foo'
>>> o.x
'foo'
>>> del o.x
>>> print(o.x)
None
Вот решение, которое:
- Позволяет указывать имена свойств в виде строк, чтобы они могли поступать из какого-либо внешнего источника данных, а не из всех перечисленных в вашей программе.
- Добавляет свойства при определении класса, а не каждый раз при создании объекта.
После того, как класс был определен, вы просто делаете это, чтобы динамически добавить к нему свойство:
setattr(SomeClass, 'propertyName', property(getter, setter))
Вот полный пример, протестированный на Python 3:
#!/usr/bin/env python3
class Foo():
pass
def get_x(self):
return 3
def set_x(self, value):
print("set x on %s to %d" % (self, value))
setattr(Foo, 'x', property(get_x, set_x))
foo1 = Foo()
foo1.x = 12
print(foo1.x)
Для тех, кто прибывает из поисковых систем, вот две вещи, которые я искал, говоря о динамических свойствах:
class Foo:
def __init__(self):
# we can dynamically have access to the properties dict using __dict__
self.__dict__['foo'] = 'bar'
assert Foo().foo == 'bar'
# or we can use __getattr__ and __setattr__ to execute code on set/get
class Bar:
def __init__(self):
self._data = {}
def __getattr__(self, key):
return self._data[key]
def __setattr__(self, key, value):
self._data[key] = value
bar = Bar()
bar.foo = 'bar'
assert bar.foo == 'bar'
__dict__
хорошо, если вы хотите поставить динамически созданные свойства. __getattr__
хорошо делать что-то только тогда, когда нужно это значение, например, запрашивать базу данных. Комбинация set/get удобна для упрощения доступа к данным, хранящимся в классе (как в примере выше).
Если вам нужно только одно динамическое свойство, взгляните на встроенную функцию property().
Вы можете использовать следующий код для обновления атрибутов класса, используя объект словаря:
class ExampleClass():
def __init__(self, argv):
for key, val in argv.items():
self.__dict__[key] = val
if __name__ == '__main__':
argv = {'intro': 'Hello World!'}
instance = ExampleClass(argv)
print instance.intro
Я задал похожий вопрос в этом сообщении о переполнении стека, чтобы создать фабрику классов, которая создавала простые типы. Результатом был этот ответ, который имел рабочую версию фабрики классов. Вот фрагмент ответа:
def Struct(*args, **kwargs):
def init(self, *iargs, **ikwargs):
for k,v in kwargs.items():
setattr(self, k, v)
for i in range(len(iargs)):
setattr(self, args[i], iargs[i])
for k,v in ikwargs.items():
setattr(self, k, v)
name = kwargs.pop("name", "MyStruct")
kwargs.update(dict((k, None) for k in args))
return type(name, (object,), {'__init__': init, '__slots__': kwargs.keys()})
>>> Person = Struct('fname', 'age')
>>> person1 = Person('Kevin', 25)
>>> person2 = Person(age=42, fname='Terry')
>>> person1.age += 10
>>> person2.age -= 10
>>> person1.fname, person1.age, person2.fname, person2.age
('Kevin', 35, 'Terry', 32)
>>>
Вы можете использовать некоторые варианты этого для создания значений по умолчанию, которые являются вашей целью (в этом вопросе также есть ответ, который имеет дело с этим).
Вы не можете добавить новый property()
к экземпляру во время выполнения, потому что свойства являются дескрипторами данных. Вместо этого вы должны динамически создать новый класс или перегрузить __getattribute__
для обработки дескрипторов данных на экземплярах.
Не уверен, что я полностью понимаю вопрос, но вы можете изменить свойства экземпляра во время выполнения с помощью встроенного __dict__
вашего класса:
class C(object):
def __init__(self, ks, vs):
self.__dict__ = dict(zip(ks, vs))
if __name__ == "__main__":
ks = ['ab', 'cd']
vs = [12, 34]
c = C(ks, vs)
print(c.ab) # 12
Это немного отличается от того, что хотел OP, но я ломал себе голову, пока не получил рабочее решение, поэтому я помещаю здесь для следующего парня / девушки
Мне нужен был способ указать динамические сеттеры и геттеры.
class X:
def __init__(self, a=0, b=0, c=0):
self.a = a
self.b = b
self.c = c
@classmethod
def _make_properties(cls, field_name, inc):
_inc = inc
def _get_properties(self):
if not hasattr(self, '_%s_inc' % field_name):
setattr(self, '_%s_inc' % field_name, _inc)
inc = _inc
else:
inc = getattr(self, '_%s_inc' % field_name)
return getattr(self, field_name) + inc
def _set_properties(self, value):
setattr(self, '_%s_inc' % field_name, value)
return property(_get_properties, _set_properties)
Я знаю свои поля заранее, поэтому я собираюсь создать свои свойства. ПРИМЕЧАНИЕ: вы не можете сделать этот экземпляр PER, эти свойства будут существовать в классе!!!
for inc, field in enumerate(['a', 'b', 'c']):
setattr(X, '%s_summed' % field, X._make_properties(field, inc))
Давайте сейчас все это проверим..
x = X()
assert x.a == 0
assert x.b == 0
assert x.c == 0
assert x.a_summed == 0 # enumerate() set inc to 0 + 0 = 0
assert x.b_summed == 1 # enumerate() set inc to 1 + 0 = 1
assert x.c_summed == 2 # enumerate() set inc to 2 + 0 = 2
# we set the variables to something
x.a = 1
x.b = 2
x.c = 3
assert x.a_summed == 1 # enumerate() set inc to 0 + 1 = 1
assert x.b_summed == 3 # enumerate() set inc to 1 + 2 = 3
assert x.c_summed == 5 # enumerate() set inc to 2 + 3 = 5
# we're changing the inc now
x.a_summed = 1
x.b_summed = 3
x.c_summed = 5
assert x.a_summed == 2 # we set inc to 1 + the property was 1 = 2
assert x.b_summed == 5 # we set inc to 3 + the property was 2 = 5
assert x.c_summed == 8 # we set inc to 5 + the property was 3 = 8
Это сбивает с толку? Да, извините, я не смог найти никаких значимых примеров из реального мира. Кроме того, это не для слабонервных.
Лучший способ добиться этого - определить __slots__
, Таким образом, ваши экземпляры не могут иметь новые атрибуты.
ks = ['ab', 'cd']
vs = [12, 34]
class C(dict):
__slots__ = []
def __init__(self, ks, vs): self.update(zip(ks, vs))
def __getattr__(self, key): return self[key]
if __name__ == "__main__":
c = C(ks, vs)
print c.ab
Это печатает 12
c.ab = 33
Это дает: AttributeError: 'C' object has no attribute 'ab'
Еще один пример того, как добиться желаемого эффекта
class Foo(object):
_bar = None
@property
def bar(self):
return self._bar
@bar.setter
def bar(self, value):
self._bar = value
def __init__(self, dyn_property_name):
setattr(Foo, dyn_property_name, Foo.bar)
Так что теперь мы можем делать такие вещи, как:
>>> foo = Foo('baz')
>>> foo.baz = 5
>>> foo.bar
5
>>> foo.baz
5
Если требуется динамически генерировать свойства на основе некоторого атрибута экземпляра, то может быть полезен приведенный ниже код:
import random
class Foo:
def __init__(self, prop_names: List[str], should_property_be_zero: bool = False) -> None:
self.prop_names = prop_names
self.should_property_be_zero = should_property_be_zero
def create_properties(self):
for name in self.prop_names:
setattr(self.__class__, name, property(fget=lambda self: 0 if self.should_property_be_zero else random.randint(1, 100)))
Важным моментом, на который следует обратить внимание, является использование
setattr(self.__class__, name, ...)
и не
setattr(self, name, ...)
Пример использования:
In [261]: prop_names = ['a', 'b']
In [262]: ff = Foo(prop_names=prop_names, should_property_be_zero=False)
In [263]: ff.create_properties()
In [264]: ff.a
Out[264]: 10
In [265]: ff.b
Out[265]: 37
In [266]: ft = Foo(prop_names=prop_names, should_property_be_zero=True)
In [267]: ft.create_properties()
In [268]: ft.a
Out[268]: 0
In [269]: ft.b
Out[269]: 0
Установка свойства повысит
AttributeError: can't set attribute
как и ожидалось:
In [270]: ff.a = 5
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-270-5f9cad5b617d> in <module>
----> 1 ff.a = 5
AttributeError: can't set attribute
In [271]: ft.a = 5
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-271-65e7b8e25b67> in <module>
----> 1 ft.a = 5
AttributeError: can't set attribute
Хотя дано много ответов, я не смог найти тот, которым я доволен. Я выяснил свое собственное решение, которое делает property
работа для динамического случая. Источник для ответа на оригинальный вопрос:
#!/usr/local/bin/python3
INITS = { 'ab': 100, 'cd': 200 }
class DP(dict):
def __init__(self):
super().__init__()
for k,v in INITS.items():
self[k] = v
def _dict_set(dp, key, value):
dp[key] = value
for item in INITS.keys():
setattr(
DP,
item,
lambda key: property(
lambda self: self[key], lambda self, value: _dict_set(self, key, value)
)(item)
)
a = DP()
print(a) # {'ab': 100, 'cd': 200}
a.ab = 'ab100'
a.cd = False
print(a.ab, a.cd) # ab100 False
Что-то, что работает для меня, это:
class C:
def __init__(self):
self._x=None
def g(self):
return self._x
def s(self, x):
self._x = x
def d(self):
del self._x
def s2(self,x):
self._x=x+x
x=property(g,s,d)
c = C()
c.x="a"
print(c.x)
C.x=property(C.g, C.s2)
C.x=C.x.deleter(C.d)
c2 = C()
c2.x="a"
print(c2.x)
Выход
a
aa
class atdict(dict):
def __init__(self, value, **kwargs):
super().__init__(**kwargs)
self.__dict = value
def __getattr__(self, name):
for key in self.__dict:
if type(self.__dict[key]) is list:
for idx, item in enumerate(self.__dict[key]):
if type(item) is dict:
self.__dict[key][idx] = atdict(item)
if type(self.__dict[key]) is dict:
self.__dict[key] = atdict(self.__dict[key])
return self.__dict[name]
d1 = atdict({'a' : {'b': [{'c': 1}, 2]}})
print(d1.a.b[0].c)
И вывод:
>> 1
Это похоже на работу (но см. Ниже):
class data(dict,object):
def __init__(self,*args,**argd):
dict.__init__(self,*args,**argd)
self.__dict__.update(self)
def __setattr__(self,name,value):
raise AttributeError,"Attribute '%s' of '%s' object cannot be set"%(name,self.__class__.__name__)
def __delattr__(self,name):
raise AttributeError,"Attribute '%s' of '%s' object cannot be deleted"%(name,self.__class__.__name__)
Если вам нужно более сложное поведение, не стесняйтесь редактировать свой ответ.
редактировать
Следующее, вероятно, будет более эффективным для памяти для больших наборов данных:
class data(dict,object):
def __init__(self,*args,**argd):
dict.__init__(self,*args,**argd)
def __getattr__(self,name):
return self[name]
def __setattr__(self,name,value):
raise AttributeError,"Attribute '%s' of '%s' object cannot be set"%(name,self.__class__.__name__)
def __delattr__(self,name):
raise AttributeError,"Attribute '%s' of '%s' object cannot be deleted"%(name,self.__class__.__name__)
Чтобы ответить на основную суть вашего вопроса, вам нужен атрибут "только для чтения" из dict в качестве неизменного источника данных:
Цель состоит в том, чтобы создать фиктивный класс, который ведет себя как набор результатов db.
Так, например, если запрос к базе данных возвращается, используя выражение dict,
{'ab':100, 'cd':200}
тогда я бы увидела>>> dummy.ab 100
Я покажу, как использовать namedtuple
от collections
Модуль для достижения только этого:
import collections
data = {'ab':100, 'cd':200}
def maketuple(d):
'''given a dict, return a namedtuple'''
Tup = collections.namedtuple('TupName', d.keys()) # iterkeys in Python2
return Tup(**d)
dummy = maketuple(data)
dummy.ab
возвращается 100
Расширяя идею от kjfletch
# This is my humble contribution, extending the idea to serialize
# data from and to tuples, comparison operations and allowing functions
# as default values.
def Struct(*args, **kwargs):
FUNCTIONS = (types.BuiltinFunctionType, types.BuiltinMethodType, \
types.FunctionType, types.MethodType)
def init(self, *iargs, **ikwargs):
"""Asume that unamed args are placed in the same order than
astuple() yields (currently alphabetic order)
"""
kw = list(self.__slots__)
# set the unnamed args
for i in range(len(iargs)):
k = kw.pop(0)
setattr(self, k, iargs[i])
# set the named args
for k, v in ikwargs.items():
setattr(self, k, v)
kw.remove(k)
# set default values
for k in kw:
v = kwargs[k]
if isinstance(v, FUNCTIONS):
v = v()
setattr(self, k, v)
def astuple(self):
return tuple([getattr(self, k) for k in self.__slots__])
def __str__(self):
data = ['{}={}'.format(k, getattr(self, k)) for k in self.__slots__]
return '<{}: {}>'.format(self.__class__.__name__, ', '.join(data))
def __repr__(self):
return str(self)
def __eq__(self, other):
return self.astuple() == other.astuple()
name = kwargs.pop("__name__", "MyStruct")
slots = list(args)
slots.extend(kwargs.keys())
# set non-specific default values to None
kwargs.update(dict((k, None) for k in args))
return type(name, (object,), {
'__init__': init,
'__slots__': tuple(slots),
'astuple': astuple,
'__str__': __str__,
'__repr__': __repr__,
'__eq__': __eq__,
})
Event = Struct('user', 'cmd', \
'arg1', 'arg2', \
date=time.time, \
__name__='Event')
aa = Event('pepe', 77)
print(aa)
raw = aa.astuple()
bb = Event(*raw)
print(bb)
if aa == bb:
print('Are equals')
cc = Event(cmd='foo')
print(cc)
Выход:
<Event: user=pepe, cmd=77, arg1=None, arg2=None, date=1550051398.3651814>
<Event: user=pepe, cmd=77, arg1=None, arg2=None, date=1550051398.3651814>
Are equals
<Event: user=None, cmd=foo, arg1=None, arg2=None, date=1550051403.7938335>
Недавно я столкнулся с подобной проблемой, решение, которое я нашел, использует __getattr__
а также __setattr__
для свойств, которые я хочу обработать, все остальное передается оригиналам.
class C(object):
def __init__(self, properties):
self.existing = "Still Here"
self.properties = properties
def __getattr__(self, name):
if "properties" in self.__dict__ and name in self.properties:
return self.properties[name] # Or call a function, etc
return self.__dict__[name]
def __setattr__(self, name, value):
if "properties" in self.__dict__ and name in self.properties:
self.properties[name] = value
else:
self.__dict__[name] = value
if __name__ == "__main__":
my_properties = {'a':1, 'b':2, 'c':3}
c = C(my_properties)
assert c.a == 1
assert c.existing == "Still Here"
c.b = 10
assert c.properties['b'] == 10
Вот простой пример программного создания объекта свойства.
#!/usr/bin/python3
class Counter:
def __init__(self):
cls = self.__class__
self._count = 0
cls.count = self.count_ref()
def count_get(self):
print(f'count_get: {self._count}')
return self._count
def count_set(self, value):
self._count = value
print(f'count_set: {self._count}')
def count_del(self):
print(f'count_del: {self._count}')
def count_ref(self):
cls = self.__class__
return property(fget=cls.count_get, fset=cls.count_set, fdel=cls.count_del)
counter = Counter()
counter.count
for i in range(5):
counter.count = i
del counter.count
'''
output
======
count_get: 0
count_set: 0
count_set: 1
count_set: 2
count_set: 3
count_set: 4
count_del: 4
'''
Единственный способ динамически присоединить свойство - это создать новый класс и его экземпляр с вашим новым свойством.
class Holder: p = property(lambda x: vs[i], self.fn_readonly)
setattr(self, k, Holder().p)
Многие из предоставленных ответов требуют так много строк для каждого свойства, то есть / и / или - что я бы назвал уродливой или утомительной реализацией из-за повторяемости, требуемой для нескольких свойств и т. Д. Я предпочитаю сводить детали вниз / упрощать их, пока они больше не может быть упрощено или пока это не принесет особой цели.
Вкратце: в завершенных работах, если я повторяю 2 строки кода, я обычно преобразую его в однострочную вспомогательную функцию и т. Д. Я упрощаю математические или нечетные аргументы, такие как ( start_x, start_y, end_x, end_y), до ( x, y, w, h) то есть x, y, x + w, y + h (иногда требуется min / max или если w / h отрицательны и реализации не нравится, я вычту из x / у и пресс с ч / ч и т. д.).
Переопределение внутренних методов получения / установки является нормальным способом, но проблема в том, что вы должны сделать это для каждого класса или создать родительский класс для этой базы... Это не работает для меня, поскольку я предпочел бы свободно выбирать детей / родителей для наследования, дочерних узлов и т. д.
Я создал решение, которое отвечает на вопрос, не используя тип данных Dict для предоставления данных, так как считаю, что ввод данных является утомительным и т. Д.
Мое решение требует, чтобы вы добавили 2 дополнительные строки над вашим классом, чтобы создать базовый класс для класса, в который вы хотите добавить свойства, затем 1 строку на каждый, и у вас есть возможность добавлять обратные вызовы для управления данными, сообщать вам об изменениях данных ограничить данные, которые могут быть установлены на основе значения и / или типа данных, и многое другое.
У вас также есть возможность использовать _object.x, _object.x = значение, _object.GetX(), _object.SetX(значение), и они обрабатываются эквивалентно.
Кроме того, значения являются единственными нестатическими данными, которые назначаются экземпляру класса, но фактическое свойство присваивается классу, то есть вещи, которые вы не хотите повторять, повторять не нужно... Вы может назначить значение по умолчанию, чтобы метод получения не нуждался в нем каждый раз, хотя есть опция для переопределения значения по умолчанию по умолчанию, и есть другая опция, чтобы метод получения возвращал необработанное сохраненное значение путем переопределения возвратов по умолчанию (примечание: этот метод означает, что необработанное значение присваивается только при назначении значения, в противном случае оно равно None - когда значение равно Reset, оно присваивает None и т. д.)
Также есть много вспомогательных функций - первое добавленное свойство добавляет в класс примерно 2 помощника для ссылки на значения экземпляра... Это повторяемые переменные ResetAccessors( _key, ..) (все можно повторить, используя первые именованные аргументы) и SetAccessors( _key, _value) с возможностью добавления большего числа в основной класс для повышения эффективности - планируется: способ группировать средства доступа вместе, поэтому, если вы склонны сбрасывать несколько одновременно, каждый раз Вы можете назначить их группе и сбросить группу вместо повторения именованных клавиш каждый раз, и многое другое.
Сохраненное значение экземпляра / необработанного хранится в классе., класс. ссылается на класс Accessor, который содержит статические переменные / значения / функции для свойства. _учебный класс. это свойство, которое вызывается при доступе через класс экземпляра во время установки / получения и т. д.
Accessor _class.__ указывает на класс, но, поскольку он является внутренним, его необходимо назначить в классе, поэтому я решил использовать __Name = AccessorFunc( ...) для его присвоения, по одной строке на свойство со многими необязательными аргументы для использования (используя ключевые переменные, потому что их проще и эффективнее идентифицировать и поддерживать)...
Как я уже упоминал, я также создаю много функций, некоторые из которых используют информацию о функциях доступа, поэтому их не нужно вызывать (поскольку в данный момент это немного неудобно - сейчас вам нужно использовать _class..FunctionName (_class_instance), args) - я нашел способ использовать стек / трассировку, чтобы получить ссылку на экземпляр, чтобы получить значение, добавив функции, которые запускают этот битовый марафон, или добавив методы доступа к объекту и используя self (названный this, чтобы указать, что они для экземпляра и для сохранения доступа к себе, ссылки на класс AccessorFunc и другую информацию в определениях функций).
Это не совсем сделано, но это фантастическая нога. Примечание. Если вы не используете __Name = AccessorFunc( ...) для создания свойств, у вас не будет доступа к клавише __, даже если я определю ее в функции init. Если вы делаете, то нет никаких проблем.
Также: Обратите внимание, что Имя и Ключ разные... Имя является "формальным", используется при создании имени функции, а ключ предназначен для хранения и доступа к данным. то есть _class.x, где строчная буква x является ключом, имя будет прописной буквой X, так что GetX () - это функция вместо Getx(), которая выглядит немного странно. это позволяет self.x работать и выглядеть соответствующим образом, но также позволяет GetX () и выглядеть соответствующим образом.
У меня есть пример класса с ключом / именем, идентичным, и другим, чтобы показать. множество вспомогательных функций, созданных для вывода данных (Примечание: не все это завершено), чтобы вы могли видеть, что происходит.
Текущий список функций, использующих key: x, name: X, выводится как:
Это ни в коем случае не исчерпывающий список - есть некоторые, которые не вошли в этот список на момент публикации...
_instance.SetAccessors( _key, _value [ , _key, _value ] .. ) Instance Class Helper Function: Allows assigning many keys / values on a single line - useful for initial setup, or to minimize lines. In short: Calls this.Set<Name>( _value ) for each _key / _value pairing.
_instance.ResetAccessors( _key [ , _key ] .. ) Instance Class Helper Function: Allows resetting many key stored values to None on a single line. In short: Calls this.Reset<Name>() for each name provided.
Note: Functions below may list self.Get / Set / Name( _args ) - self is meant as the class instance reference in the cases below - coded as this in AccessorFuncBase Class.
this.GetX( _default_override = None, _ignore_defaults = False ) GET: Returns IF ISSET: STORED_VALUE .. IF IGNORE_DEFAULTS: None .. IF PROVIDED: DEFAULT_OVERRIDE ELSE: DEFAULT_VALUE 100
this.GetXRaw( ) RAW: Returns STORED_VALUE 100
this.IsXSet( ) ISSET: Returns ( STORED_VALUE != None ) True
this.GetXToString( ) GETSTR: Returns str( GET ) 100
this.GetXLen( _default_override = None, _ignore_defaults = False ) LEN: Returns len( GET ) 3
this.GetXLenToString( _default_override = None, _ignore_defaults = False ) LENSTR: Returns str( len( GET ) ) 3
this.GetXDefaultValue( ) DEFAULT: Returns DEFAULT_VALUE 1111
this.GetXAccessor( ) ACCESSOR: Returns ACCESSOR_REF ( self.__<key> ) [ AccessorFuncBase ] Key: x : Class ID: 2231452344344 : self ID: 2231448283848 Default: 1111 Allowed Types: {"<class 'int'>": "<class 'type'>", "<class 'float'>": "<class 'type'>"} Allowed Values: None
this.GetXAllowedTypes( ) ALLOWED_TYPES: Returns Allowed Data-Types {"<class 'int'>": "<class 'type'>", "<class 'float'>": "<class 'type'>"}
this.GetXAllowedValues( ) ALLOWED_VALUES: Returns Allowed Values None
this.GetXHelpers( ) HELPERS: Returns Helper Functions String List - ie what you're reading now... THESE ROWS OF TEXT
this.GetXKeyOutput( ) Returns information about this Name / Key ROWS OF TEXT
this.GetXGetterOutput( ) Returns information about this Name / Key ROWS OF TEXT
this.SetX( _value ) SET: STORED_VALUE Setter - ie Redirect to __<Key>.Set N / A
this.ResetX( ) RESET: Resets STORED_VALUE to None N / A
this.HasXGetterPrefix( ) Returns Whether or Not this key has a Getter Prefix... True
this.GetXGetterPrefix( ) Returns Getter Prefix... Get
this.GetXName( ) Returns Accessor Name - Typically Formal / Title-Case X
this.GetXKey( ) Returns Accessor Property Key - Typically Lower-Case x
this.GetXAccessorKey( ) Returns Accessor Key - This is to access internal functions, and static data... __x
this.GetXDataKey( ) Returns Accessor Data-Storage Key - This is the location where the class instance value is stored.. _x
Некоторые из выводимых данных:
Это для совершенно нового класса, созданного с использованием демо-класса, без каких-либо данных, назначенных, кроме имени (чтобы его можно было выводить), то есть _foo, имя переменной, которую я использовал...
_foo --- MyClass: ---- id( this.__class__ ): 2231452349064 :::: id( this ): 2231448475016
Key Getter Value | Raw Key Raw / Stored Value | Get Default Value Default Value | Get Allowed Types Allowed Types | Get Allowed Values Allowed Values |
Name: _foo | _Name: _foo | __Name.DefaultValue( ): AccessorFuncDemoClass | __Name.GetAllowedTypes( ) <class 'str'> | __Name.GetAllowedValues( ) Saved Value Restrictions Levied by Data-Type |
x: 1111 | _x: None | __x.DefaultValue( ): 1111 | __x.GetAllowedTypes( ) (<class 'int'>, <class 'float'>) | __x.GetAllowedValues( ) Saved Value Restrictions Levied by Data-Type |
y: 2222 | _y: None | __y.DefaultValue( ): 2222 | __y.GetAllowedTypes( ) (<class 'int'>, <class 'float'>) | __y.GetAllowedValues( ) Saved Value Restrictions Levied by Data-Type |
z: 3333 | _z: None | __z.DefaultValue( ): 3333 | __z.GetAllowedTypes( ) (<class 'int'>, <class 'float'>) | __z.GetAllowedValues( ) Saved Value Restrictions Levied by Data-Type |
Blah: <class 'int'> | _Blah: None | __Blah.DefaultValue( ): <class 'int'> | __Blah.GetAllowedTypes( ) <class 'str'> | __Blah.GetAllowedValues( ) Saved Value Restrictions Levied by Data-Type |
Width: 1 | _Width: None | __Width.DefaultValue( ): 1 | __Width.GetAllowedTypes( ) (<class 'int'>, <class 'bool'>) | __Width.GetAllowedValues( ) Saved Value Restrictions Levied by Data-Type |
Height: 0 | _Height: None | __Height.DefaultValue( ): 0 | __Height.GetAllowedTypes( ) <class 'int'> | __Height.GetAllowedValues( ) (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) |
Depth: 2 | _Depth: None | __Depth.DefaultValue( ): 2 | __Depth.GetAllowedTypes( ) Saved Value Restricted to Authorized Values ONLY | __Depth.GetAllowedValues( ) (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) |
this.IsNameSet( ): True this.GetName( ): _foo this.GetNameRaw( ): _foo this.GetNameDefaultValue( ): AccessorFuncDemoClass this.GetNameLen( ): 4 this.HasNameGetterPrefix( ): <class 'str'> this.GetNameGetterPrefix( ): None
this.IsXSet( ): False this.GetX( ): 1111 this.GetXRaw( ): None this.GetXDefaultValue( ): 1111 this.GetXLen( ): 4 this.HasXGetterPrefix( ): (<class 'int'>, <class 'float'>) this.GetXGetterPrefix( ): None
this.IsYSet( ): False this.GetY( ): 2222 this.GetYRaw( ): None this.GetYDefaultValue( ): 2222 this.GetYLen( ): 4 this.HasYGetterPrefix( ): (<class 'int'>, <class 'float'>) this.GetYGetterPrefix( ): None
this.IsZSet( ): False this.GetZ( ): 3333 this.GetZRaw( ): None this.GetZDefaultValue( ): 3333 this.GetZLen( ): 4 this.HasZGetterPrefix( ): (<class 'int'>, <class 'float'>) this.GetZGetterPrefix( ): None
this.IsBlahSet( ): False this.GetBlah( ): <class 'int'> this.GetBlahRaw( ): None this.GetBlahDefaultValue( ): <class 'int'> this.GetBlahLen( ): 13 this.HasBlahGetterPrefix( ): <class 'str'> this.GetBlahGetterPrefix( ): None
this.IsWidthSet( ): False this.GetWidth( ): 1 this.GetWidthRaw( ): None this.GetWidthDefaultValue( ): 1 this.GetWidthLen( ): 1 this.HasWidthGetterPrefix( ): (<class 'int'>, <class 'bool'>) this.GetWidthGetterPrefix( ): None
this.IsDepthSet( ): False this.GetDepth( ): 2 this.GetDepthRaw( ): None this.GetDepthDefaultValue( ): 2 this.GetDepthLen( ): 1 this.HasDepthGetterPrefix( ): None this.GetDepthGetterPrefix( ): (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
this.IsHeightSet( ): False this.GetHeight( ): 0 this.GetHeightRaw( ): None this.GetHeightDefaultValue( ): 0 this.GetHeightLen( ): 1 this.HasHeightGetterPrefix( ): <class 'int'> this.GetHeightGetterPrefix( ): (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
И это после присвоения всем свойствам _foo (кроме имени) следующих значений в том же порядке: 'string ', 1.0, True, 9, 10, False
this.IsNameSet( ): True this.GetName( ): _foo this.GetNameRaw( ): _foo this.GetNameDefaultValue( ): AccessorFuncDemoClass this.GetNameLen( ): 4 this.HasNameGetterPrefix( ): <class 'str'> this.GetNameGetterPrefix( ): None
this.IsXSet( ): True this.GetX( ): 10 this.GetXRaw( ): 10 this.GetXDefaultValue( ): 1111 this.GetXLen( ): 2 this.HasXGetterPrefix( ): (<class 'int'>, <class 'float'>) this.GetXGetterPrefix( ): None
this.IsYSet( ): True this.GetY( ): 10 this.GetYRaw( ): 10 this.GetYDefaultValue( ): 2222 this.GetYLen( ): 2 this.HasYGetterPrefix( ): (<class 'int'>, <class 'float'>) this.GetYGetterPrefix( ): None
this.IsZSet( ): True this.GetZ( ): 10 this.GetZRaw( ): 10 this.GetZDefaultValue( ): 3333 this.GetZLen( ): 2 this.HasZGetterPrefix( ): (<class 'int'>, <class 'float'>) this.GetZGetterPrefix( ): None
this.IsBlahSet( ): True this.GetBlah( ): string Blah this.GetBlahRaw( ): string Blah this.GetBlahDefaultValue( ): <class 'int'> this.GetBlahLen( ): 11 this.HasBlahGetterPrefix( ): <class 'str'> this.GetBlahGetterPrefix( ): None
this.IsWidthSet( ): True this.GetWidth( ): False this.GetWidthRaw( ): False this.GetWidthDefaultValue( ): 1 this.GetWidthLen( ): 5 this.HasWidthGetterPrefix( ): (<class 'int'>, <class 'bool'>) this.GetWidthGetterPrefix( ): None
this.IsDepthSet( ): True this.GetDepth( ): 9 this.GetDepthRaw( ): 9 this.GetDepthDefaultValue( ): 2 this.GetDepthLen( ): 1 this.HasDepthGetterPrefix( ): None this.GetDepthGetterPrefix( ): (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
this.IsHeightSet( ): True this.GetHeight( ): 9 this.GetHeightRaw( ): 9 this.GetHeightDefaultValue( ): 0 this.GetHeightLen( ): 1 this.HasHeightGetterPrefix( ): <class 'int'> this.GetHeightGetterPrefix( ): (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
_foo --- MyClass: ---- id( this.__class__ ): 2231452349064 :::: id( this ): 2231448475016
Key Getter Value | Raw Key Raw / Stored Value | Get Default Value Default Value | Get Allowed Types Allowed Types | Get Allowed Values Allowed Values |
Name: _foo | _Name: _foo | __Name.DefaultValue( ): AccessorFuncDemoClass | __Name.GetAllowedTypes( ) <class 'str'> | __Name.GetAllowedValues( ) Saved Value Restrictions Levied by Data-Type |
x: 10 | _x: 10 | __x.DefaultValue( ): 1111 | __x.GetAllowedTypes( ) (<class 'int'>, <class 'float'>) | __x.GetAllowedValues( ) Saved Value Restrictions Levied by Data-Type |
y: 10 | _y: 10 | __y.DefaultValue( ): 2222 | __y.GetAllowedTypes( ) (<class 'int'>, <class 'float'>) | __y.GetAllowedValues( ) Saved Value Restrictions Levied by Data-Type |
z: 10 | _z: 10 | __z.DefaultValue( ): 3333 | __z.GetAllowedTypes( ) (<class 'int'>, <class 'float'>) | __z.GetAllowedValues( ) Saved Value Restrictions Levied by Data-Type |
Blah: string Blah | _Blah: string Blah | __Blah.DefaultValue( ): <class 'int'> | __Blah.GetAllowedTypes( ) <class 'str'> | __Blah.GetAllowedValues( ) Saved Value Restrictions Levied by Data-Type |
Width: False | _Width: False | __Width.DefaultValue( ): 1 | __Width.GetAllowedTypes( ) (<class 'int'>, <class 'bool'>) | __Width.GetAllowedValues( ) Saved Value Restrictions Levied by Data-Type |
Height: 9 | _Height: 9 | __Height.DefaultValue( ): 0 | __Height.GetAllowedTypes( ) <class 'int'> | __Height.GetAllowedValues( ) (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) |
Depth: 9 | _Depth: 9 | __Depth.DefaultValue( ): 2 | __Depth.GetAllowedTypes( ) Saved Value Restricted to Authorized Values ONLY | __Depth.GetAllowedValues( ) (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) |
Обратите внимание, что из-за ограниченных типов данных или ограничений по значению некоторые данные не были назначены - это сделано специально. Сеттер запрещает присваивать неверные типы данных или значения, даже если они назначаются в качестве значения по умолчанию (если вы не переопределите поведение защиты значений по умолчанию)
Код не был размещен здесь, потому что у меня не было места после примеров и объяснений... Кроме того, потому что он изменится.
Пожалуйста, обратите внимание: во время этой публикации, файл грязный - это изменится. Но если вы запустите его в Sublime Text и скомпилируете его или запустите из Python, он скомпилирует и выплеснет тонну информации - часть AccessorDB не сделана (которая будет использоваться для обновления Print Getters и GetKeyOutput helper функции вместе с изменением на функцию Instance, возможно, помещенную в одну функцию и переименованную - ищите ее..)
Далее: не все требуется для его запуска - большая часть комментариев внизу предназначена для дополнительной информации, используемой для отладки - ее может не быть при загрузке. Если это так, вы сможете раскомментировать и перекомпилировать, чтобы получить больше информации.
Я ищу обходной путь к необходимости MyClassBase: pass, MyClass( MyClassBase): ... - если вы знаете о решении - опубликуйте его.
Единственное, что необходимо в классе - это __-строки - str для отладки, как и init - их можно удалить из демонстрационного класса, но вам нужно будет закомментировать или удалить некоторые из строк ниже ( _foo / 2 / 3)..
Классы String, Dict и Util вверху являются частью моей библиотеки Python - они не завершены. Я скопировал из библиотеки несколько вещей, которые мне были нужны, и создал несколько новых. Полный код будет связан с полной библиотекой и будет включать его вместе с предоставлением обновленных вызовов и удалением кода (фактически, единственным оставшимся кодом будет демонстрационный класс и операторы печати - система AccessorFunc будет перемещена в библиотеку)...
Часть файла:
##
## MyClass Test AccessorFunc Implementation for Dynamic 1-line Parameters
##
class AccessorFuncDemoClassBase( ):
pass
class AccessorFuncDemoClass( AccessorFuncDemoClassBase ):
__Name = AccessorFuncBase( parent = AccessorFuncDemoClassBase, name = 'Name', default = 'AccessorFuncDemoClass', allowed_types = ( TYPE_STRING ), allowed_values = VALUE_ANY, documentation = 'Name Docs', getter_prefix = 'Get', key = 'Name', allow_erroneous_default = False, options = { } )
__x = AccessorFuncBase( parent = AccessorFuncDemoClassBase, name = 'X', default = 1111, allowed_types = ( TYPE_INTEGER, TYPE_FLOAT ), allowed_values = VALUE_ANY, documentation = 'X Docs', getter_prefix = 'Get', key = 'x', allow_erroneous_default = False, options = { } )
__Height = AccessorFuncBase( parent = AccessorFuncDemoClassBase, name = 'Height', default = 0, allowed_types = TYPE_INTEGER, allowed_values = VALUE_SINGLE_DIGITS, documentation = 'Height Docs', getter_prefix = 'Get', key = 'Height', allow_erroneous_default = False, options = { } )
Эта красота невероятно упрощает создание новых классов с динамически добавляемыми свойствами с помощью AccessorFuncs / callbacks / data-type / value принудительного применения и т. Д.
На данный момент ссылка на (эта ссылка должна отражать изменения в документе.): https://www.dropbox.com/s/6gzi44i7dh58v61/dynamic_properties_accessorfuncs_and_more.py?dl=0
Также: если вы не используете Sublime Text, я рекомендую его для Notepad++, Atom, Visual Code и других из-за правильной реализации многопоточности, которая делает его намного, намного быстрее в использовании... Я также работаю над IDE-подобным кодом Система сопоставления для него - взгляните на: https://bitbucket.org/Acecool/acecoolcodemappingsystem/src/master/ (сначала добавьте репо в диспетчер пакетов, затем установите плагин - когда версия 1.0.0 будет готова, я добавлю это к основному списку плагинов...)
Я надеюсь, что это решение поможет... и, как всегда:
Просто потому, что это работает, не делает это правильно - Джош 'Acecool' Мозер