Jak mogę utworzyć alias do atrybutu non-function member w klasie Pythona?

Jestem w trakcie pisania API biblioteki Pythona i często spotykam się ze scenariuszem, w którym moi użytkownicy chcą wiele różnych nazw dla tych samych funkcji i zmiennych.

Jeśli mam klasę Pythona z funkcją foo() i chcę utworzyć do niej alias o nazwie bar(), to jest to bardzo proste:

class Dummy(object):

   def __init__(self):
      pass

   def foo(self):
      pass

   bar = foo

Teraz mogę to zrobić bez problemu:

d = Dummy()
d.foo()
d.bar()

Zastanawiam się, jaki jest najlepszy sposób, aby to zrobić z atrybutem klasy, który jest zmienną regularną (np. string) zamiast funkcji? Gdybym miał ten kawałek kodu:

d = Dummy()
print d.x
print d.xValue

Chcę d.x i d.xValue Aby zawsze wydrukować to samo. Jeśli d.x się zmieni, to powinno się zmienić d.xValue również (i vice-versa).

Mogę wymyślić kilka sposobów, aby to zrobić, ale żaden z nich nie wydaje się tak gładki, jak bym chciał:

  • napisz niestandardową adnotację
  • użyj adnotacji @property i bałaganu z seterem
  • Override __setattr__ class functions

Które z tych sposobów jest najlepszy? A może jest inny sposób? Nie mogę pomóc, ale czuję, że jeśli tak łatwo jest tworzyć aliasy dla funkcji, powinno być tak samo łatwo dla dowolnych zmiennych...

FYI: używam Pythona 2.7.x, nie Python 3.0, więc potrzebuję Pythona 2.7.rozwiązanie kompatybilne z x (chociaż byłbym zainteresowany, gdyby Python 3.0 zrobił coś, aby bezpośrednio zaspokoić tę potrzebę).

Dzięki!

Author: Brent Writes Code, 2010-10-25

5 answers

Możesz podać __setattr__ i __getattr__, które odwołują się do mapy aliasów:

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
 14
Author: Ned Batchelder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-26 16:48:33

O ile nie źle zrozumiałem pytanie, można to rozwiązać w prawie dokładnie taki sam sposób jak w przypadku metod klasowych.

Na przykład,

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)

Obie wartości zawsze pozostaną zsynchronizowane. Piszesz rzeczywisty kod właściwości z preferowaną nazwą atrybutu, a następnie używasz pseudonimu z dowolną starszą nazwą(nazwami), której potrzebujesz.

Moim oczom ten kod jest o wiele łatwiejszy do odczytania i zrozumienia niż wszystkie __setattr__ i __getattr__ nadpisywanie.

 6
Author: smargh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-10 21:18:24

Co zrobisz, gdy połowa użytkowników zdecyduje się użyć d.x, a druga połowa d.xValue? Co się dzieje, gdy próbują udostępnić kod? Pewnie, że zadziała, jeśli znasz wszystkie aliasy, ale czy będzie to oczywiste? Czy będzie to dla ciebie oczywiste, kiedy odłożysz swój kod na rok?

W końcu myślę, że ten rodzaj uprzejmości lub luksusu jest złą pułapką, która ostatecznie spowoduje więcej zamieszania niż dobra.

Głównie dlatego, że moje Skryptowe API jest używany w wielu podsystemach & domeny, więc domyślne słownictwo zmiany. Tzw. " X " w jednym domena jest znana jako "Y" w innym domena.

Możesz tworzyć aliasy z właściwościami w ten sposób:

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

Ale z powodów wymienionych powyżej, nie sądzę, że jest to dobry projekt. Informatyka utrudnia czytanie, rozumienie i używanie manekina. Dla każdego użytkownika podwoiłeś rozmiar API, który użytkownik musi znać, aby zrozumieć Dummy.

Lepszą alternatywą jest użyj wzorca projektowego adaptera . Pozwala to zachować smoczek miły, zwarty, zwięzły:

class Dummy(object):
   def __init__(self):
      self.x=1

Podczas gdy ci użytkownicy w subdomenie, którzy chcą używać innego słownictwa, mogą to zrobić za pomocą klasy adaptera:

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    

Dla każdej metody i atrybutu w atrybucie, po prostu podłączasz podobne metody i właściwości które delegują podnoszenie ciężarów do instancji manekina.

Może to być więcej linii kodu, ale pozwoli Ci zachować czysty projekt dla atrapy, łatwiejsze w utrzymaniu, dokumentowaniu i testowaniu jednostkowym. Ludzie będą pisać kod, który ma sens, ponieważ Klasa ograniczy to, jakie API jest dostępne, i będzie tylko jedna nazwa dla każdej koncepcji, biorąc pod uwagę klasę, którą wybrali.

 5
Author: unutbu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-25 22:32:22

Możesz użyć niektórych pomysłów pokazanych w przepisie ActiveState Python zatytułowanym buforowanie i aliasowanie za pomocą deskryptorów. Oto zwięzła wersja kodu, która zapewnia funkcjonalność, której szukasz.

Edit: Klasa zawierająca atrybuty Alias może zostać wykonana w celu automatycznego usuwania wszelkich powiązanych atrybutów docelowych, gdy ty del jeden (i vice-versa). Kod do mojej odpowiedzi teraz ilustruje jeden prosty sposób można to zrobić za pomocą wygodnego dekorator klasy, który dodaje niestandardowe __delattr__() do wyspecjalizowanego zarządzania usuwaniem, gdy atrybut Alias's może być zaangażowany.

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'
 3
Author: martineau,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-30 18:53:09

Nadpisuje metodę __getattr__() i zwraca odpowiednią wartość.

 -1
Author: Ignacio Vazquez-Abrams,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-25 18:22:33