Как я могу сделать псевдоним для атрибута нефункционального члена в классе Python?
Я нахожусь в середине написания API библиотеки Python, и я часто сталкиваюсь со сценарием, где мои пользователи хотят несколько разных имен для одних и тех же функций и переменных.
если у меня есть класс Python с функцией foo()
и я хочу сделать псевдоним для него под названием bar()
это супер просто:
class Dummy(object):
def __init__(self):
pass
def foo(self):
pass
bar = foo
теперь я могу сделать это без проблем:
d = Dummy()
d.foo()
d.bar()
мне интересно, каков наилучший способ сделать это с атрибутом класса, который регулярная переменная (например, строка), а не функция? Если бы у меня был этот кусок кода:
d = Dummy()
print d.x
print d.xValue
Я хочу d.x
и d.xValue
to всегда печатать то же самое. Если d.x
изменения, он должен изменить d.xValue
также (и наоборот).
Я могу придумать множество способов сделать это, но ни один из них не кажется так гладко, как хотелось бы:
- написать пользовательскую аннотацию
- использовать
@property
аннотация и беспорядок с сеттер - переопределить
__setattr__
класс функций
какой из этих способов лучше? Или есть другой способ? Я не могу помочь, но чувствую, что если так легко сделать псевдонимы для функций, это должно быть так же легко для произвольных переменных...
FYI: я использую Python 2.7.x, а не Python 3.0, поэтому мне нужен Python 2.7.X совместимое решение (хотя мне было бы интересно, если Python 3.0 сделает что-то, чтобы напрямую решить эту проблему потребность.)
спасибо!
5 ответов
вы можете предоставить __setattr__
и __getattr__
эта ссылка на карту псевдонимов:
class Dummy(object):
aliases = {
'xValue': 'x',
'another': 'x',
}
def __init__(self):
self.x = 17
def __setattr__(self, name, value):
name = self.aliases.get(name, name)
object.__setattr__(self, name, value)
def __getattr__(self, name):
if name == "aliases":
raise AttributeError # http://nedbatchelder.com/blog/201010/surprising_getattr_recursion.html
name = self.aliases.get(name, name)
#return getattr(self, name) #Causes infinite recursion on non-existent attribute
return object.__getattribute__(self, name)
d = Dummy()
assert d.x == 17
assert d.xValue == 17
d.x = 23
assert d.xValue == 23
d.xValue = 1492
assert d.x == 1492
Если я не неправильно понимаю вопрос, это можно решить почти точно так же, как с методами класса.
например,
class Dummy(object):
def __init__(self):
self._x = 17
@property
def x(self):
return self._x
@x.setter
def x(self, inp):
self._x = inp
# Alias
xValue = x
d = Dummy()
print(d.x, d.xValue)
#=> (17, 17)
d.x = 0
print(d.x, d.xValue)
#=> (0, 0)
d.xValue = 100
print(d.x, d.xValue)
#=> (100, 100)
эти два значения всегда будут синхронизированы. Вы пишете фактический код свойства с именем атрибута, которое вы предпочитаете, а затем псевдоним его с любым устаревшим именем(именами), которое вам нужно.
на мой взгляд, этот код намного легче читать и понимать, чем все __setattr__
и __getattr__
перезаписи.
что вы собираетесь делать, когда половина ваших пользователей решили использовать d.x
а другая половина d.xValue
? Что происходит, когда они пытаются поделиться кодом? Конечно, это сработает,если вы знаете все псевдонимы, но будет ли это очевидно? Будет ли это очевидно для вас, когда вы отложите свой код на год?
в конце концов, я думаю, что такая любезность или роскошь-это злая ловушка, которая в конечном итоге вызовет больше путаницы, чем добра.
это в основном потому что мой API сценариев используется в нескольких подсистемах & Домены, поэтому словарь по умолчанию изменения. То, что известно как" X " в одном домен известен как "Y" в другом домен.
вы можете сделать псевдонимы со свойствами таким образом:
class Dummy(object):
def __init__(self):
self.x=1
@property
def xValue(self):
return self.x
@xValue.setter
def xValue(self,value):
self.x=value
d=Dummy()
print(d.x)
# 1
d.xValue=2
print(d.x)
# 2
но по причинам, упомянутым выше, я не думаю, что это хороший дизайн. Он делает Dummy труднее читать, понимать и использовать. Для каждого пользователя вы удвоили размер API, который пользователь должен знать, чтобы понимаю манекен.
лучше использовать шаблон дизайна адаптера. Это позволяет держать манекен красиво, компактно, лаконично:
class Dummy(object):
def __init__(self):
self.x=1
в то время как те пользователи в поддомене, которые хотят использовать другой словарь может сделать это используя класс адаптера:
class DummyAdaptor(object):
def __init__(self):
self.dummy=Dummy()
@property
def xValue(self):
return self.dummy.x
@xValue.setter
def xValue(self,value):
self.dummy.x=value
для каждого метода и атрибута в Dummy вы просто подключаете аналогичные методы и свойства которые делегируют тяжелую работу экземпляру Манекен.
это может быть больше строк кода, но это позволит вам сохранить чистый дизайн для манекена, легче поддерживать, документировать и модульный тест. Люди будут писать код, который имеет смысл, потому что класс будет ограничивать доступ к API, и для каждой концепции будет только одно имя для выбранного класса.
вы можете использовать некоторые идеи, показанные в рецепте ActiveState Python под названием кэширование и сглаживание с помощью дескрипторов. Вот краткая версия кода, показанная там, которая обеспечивает функциональность, которую вы ищете.
Edit: класс, содержащий Alias
атрибуты могут быть сделаны для автоматического удаления любых связанных целевых атрибутов, когда вы del
один (и наоборот). Код для моего ответа теперь иллюстрирует один простой способ: может быть сделано с помощью удобного декоратора класса, который добавляет пользовательский __delattr__()
чтобы сделать Специализированное управление удалением, когда атрибут Alias's
может быть вовлечен.
class Alias(object):
""" Descriptor to give an attribute another name. """
def __init__(self, name):
self.name = name
def __get__(self, inst, cls):
if inst is None:
return self # a class attribute reference, return this descriptor
return getattr(inst, self.name)
def __set__(self, inst, value):
setattr(inst, self.name, value)
def __delete__(self, inst):
delattr(inst, self.name)
def AliasDelManager(cls):
""" Class decorator to auto-manage associated Aliases on deletion. """
def __delattr__(self, name):
""" Deletes any Aliases associated with a named attribute, or
if attribute is itself an Alias, deletes the associated target.
"""
super(cls, self).__delattr__(name) # use base class's method
for attrname in dir(self):
attr = getattr(Dummy, attrname)
if isinstance(attr, Alias) and attr.name == name:
delattr(Dummy, attrname)
setattr(cls, '__delattr__', __delattr__)
return cls
if __name__=='__main__':
@AliasDelManager
class Dummy(object):
def __init__(self):
self.x = 17
xValue = Alias('x') # create an Alias for attr 'x'
d = Dummy()
assert d.x == 17
assert d.xValue == 17
d.x = 23
assert d.xValue == 23
d.xValue = 1492
assert d.x == 1492
assert d.x is d.xValue
del d.x # should also remove any associated Aliases
assert 'xValue' not in dir(d)
print 'done - no exceptions were raised'