Używanie property () w metodach klas


173

Mam klasę z dwiema metodami klas (przy użyciu funkcji classmethod ()) do pobierania i ustawiania tego, co jest zasadniczo zmienną statyczną. Próbowałem użyć z nimi funkcji property (), ale powoduje to błąd. Udało mi się odtworzyć błąd w tłumaczu:

class Foo(object):
    _var = 5
    @classmethod
    def getvar(cls):
        return cls._var
    @classmethod
    def setvar(cls, value):
        cls._var = value
    var = property(getvar, setvar)

Mogę zademonstrować metody klas, ale nie działają one jako właściwości:

>>> f = Foo()
>>> f.getvar()
5
>>> f.setvar(4)
>>> f.getvar()
4
>>> f.var
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: 'classmethod' object is not callable
>>> f.var=5
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: 'classmethod' object is not callable

Czy jest możliwe użycie funkcji property () z funkcjami dekorowanymi metodą klas?

Odpowiedzi:


90

Właściwość jest tworzona w klasie, ale wpływa na instancję. Więc jeśli chcesz mieć właściwość classmethod, utwórz właściwość w metaklasie.

>>> class foo(object):
...     _var = 5
...     class __metaclass__(type):  # Python 2 syntax for metaclasses
...         pass
...     @classmethod
...     def getvar(cls):
...         return cls._var
...     @classmethod
...     def setvar(cls, value):
...         cls._var = value
...     
>>> foo.__metaclass__.var = property(foo.getvar.im_func, foo.setvar.im_func)
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3

Ale ponieważ i tak używasz metaklasy, będzie lepiej czytać, jeśli po prostu przeniesiesz tam metody klas.

>>> class foo(object):
...     _var = 5
...     class __metaclass__(type):  # Python 2 syntax for metaclasses
...         @property
...         def var(cls):
...             return cls._var
...         @var.setter
...         def var(cls, value):
...             cls._var = value
... 
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3

lub, używając metaclass=...składni Pythona 3 i metaklasy zdefiniowanej poza treścią fooklasy oraz metaklasy odpowiedzialnej za ustawienie początkowej wartości _var:

>>> class foo_meta(type):
...     def __init__(cls, *args, **kwargs):
...         cls._var = 5
...     @property
...     def var(cls):
...         return cls._var
...     @var.setter
...     def var(cls, value):
...         cls._var = value
...
>>> class foo(metaclass=foo_meta):
...     pass
...
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3

1
Wydaje się, że to nie działa dla mnie w Pythonie 3.2. Jeśli zmienię foo .__ metaclass __. Var = property (foo.getvar.im_func, foo.setvar.im_func) na foo .__ metaclass __. Var = property (foo.getvar .__ func__, foo.setvar .__ func__) I get "AttributeError: type obiekt „foo” nie ma atrybutu „var” ”podczas wykonywania„ foo.var ”.
— Michael Kelley,

Podwójna korekta SIGH : działa w Pythonie 2.7, ale nie w Pythonie 3.2.
— Michael Kelley

@MichaelKelley - To dlatego, że składnia metaklas zmieniła się w Pythonie 3.x
— mac

1
Nie jestem pewien, czy rozumiem, w jaki sposób można by to napisać w Pythonie 3.x?
— SylvainD

8
@Josay: Najpierw musisz zdefiniować metaklasę, a następnie zdefiniować klasę, używając nowej class Foo(metaclass=...)składni.
— Kevin,

69

Czytając informacje o wydaniu Pythona 2.2 , znajduję następujące informacje.

Metoda get [właściwości] nie zostanie wywołana, gdy do właściwości uzyskuje się dostęp jako atrybut klasy (Cx), a nie jako atrybut instancji (C (). X). Jeśli chcesz przesłonić operację __get__ dla właściwości, gdy jest używana jako atrybut klasy, możesz podklasować właściwość - jest to sam typ nowego stylu - aby rozszerzyć jej metodę __get__ lub możesz zdefiniować typ deskryptora od podstaw, tworząc nowy -style, która definiuje metody __get__, __set__ i __delete__.

UWAGA: Poniższa metoda w rzeczywistości nie działa w przypadku seterów, tylko metody pobierające.

Dlatego uważam, że zalecanym rozwiązaniem jest utworzenie klasy ClassProperty jako podklasy właściwości.

class ClassProperty(property):
    def __get__(self, cls, owner):
        return self.fget.__get__(None, owner)()

class foo(object):
    _var=5
    def getvar(cls):
        return cls._var
    getvar=classmethod(getvar)
    def setvar(cls,value):
        cls._var=value
    setvar=classmethod(setvar)
    var=ClassProperty(getvar,setvar)

assert foo.getvar() == 5
foo.setvar(4)
assert foo.getvar() == 4
assert foo.var == 4
foo.var = 3
assert foo.var == 3

Jednak setery w rzeczywistości nie działają:

foo.var = 4
assert foo.var == foo._var # raises AssertionError

foo._var jest niezmieniona, po prostu nadpisałeś właściwość nową wartością.

Możesz również użyć ClassPropertyjako dekoratora:

class foo(object):
    _var = 5

    @ClassProperty
    @classmethod
    def var(cls):
        return cls._var

    @var.setter
    @classmethod
    def var(cls, value):
        cls._var = value

assert foo.var == 5

20
Nie sądzę, aby część ustawiająca właściwości ClassProperty faktycznie działała tak, jak opisano: podczas gdy wszystkie potwierdzenia w przykładzie przechodzą, na końcu foo._var == 4 (nie 3, jak sugerowano). Ustawienie właściwości powoduje jej ubijanie. Kiedy omawiano właściwości klas na python-dev , zwrócono uwagę, że chociaż metody pobierające są trywialne, to setery są trudne (niemożliwe?) Bez metaklasy
— Gabriel Grant

4
@Gabriel Całkowicie poprawne. Nie mogę uwierzyć, że nikt nie zwracał na to uwagi przez dwa lata.
— agf

Nie jestem też pewien, dlaczego nie używasz tu po prostu self.fget(owner)i nie usuwasz potrzeby używania go @classmethodw ogóle? (to co classmethod robi , tłumaczyć .__get__(instance, owner)(*args, **kwargs)do function(owner, *args, **kwargs)połączeń za pośrednictwem pośrednika; właściwości nie potrzebują pośrednika).
— Martijn Pieters

W Twojej demonstracji brakuje jakiejkolwiek rzeczywistej transformacji zarówno w funkcji pobierającej, jak i ustawiającej, która zgrabnie wykazałaby, że twoje foo.var = 3przypisanie w rzeczywistości nie przechodzi przez właściwość , a zamiast tego po prostu zamieniło obiekt właściwości na fooliczbę całkowitą. Jeśli dodałeś assert isinstance(foo.__dict__['var'], ClassProperty)wywołania między swoimi asercjami, zobaczysz, że po foo.var = 3wykonaniu zakończy się niepowodzeniem .
— Martijn Pieters

1
Klasy Python nie obsługują deskryptor wiążące ustawienie na samej klasy , tylko na uzyskanie (tak instance.attr, instance.attr = valuei del instance.attrbędzie wszystko wiążą deskryptor znaleziono type(instance), ale jednocześnie classobj.attrwiąże, classobj.attr = valuei del classobj.attrczy nie i zamiast wymienić lub usunąć sam obiekt deskryptora). Potrzebujesz metaklasy do obsługi ustawiania i usuwania (czyniąc obiekt klasy instancją, a metaklasę typem).
— Martijn Pieters

56

Mam nadzieję, że ten nieskomplikowany @classpropertydekorator tylko do odczytu pomógłby komuś, kto szuka właściwości klas.

class classproperty(object):

    def __init__(self, fget):
        self.fget = fget

    def __get__(self, owner_self, owner_cls):
        return self.fget(owner_cls)

class C(object):

    @classproperty
    def x(cls):
        return 1

assert C.x == 1
assert C().x == 1

2
Czy to działa z podklasami? (czy podklasa może zastąpić classproperties?)
— zakdances

1
Umm tak? class D(C): x = 2; assert D.x == 2
— dtheodor

Chciałbym to działało kiedy używam go w .formatjak "{x}".format(**dict(self.__class__.__dict__, **self.__dict__)):(
— 2rs2ts

@Nathan Nie tylko ... kiedy to ustawisz, nadpisujesz cały xdostęp 10. Podoba mi się to podejście, ponieważ jest schludne i proste, ale brzmi jak anty
— Michele d'Amico

Łatwo naprawione: dodaj, __set__co podnosi an, ValueErroraby zapobiec zastąpieniu.
— Kiran Jonnalagadda

30

Czy jest możliwe użycie funkcji property () z funkcjami dekorowanymi metodą klas?

Nie.

Jednak metoda klasowa jest po prostu metodą związaną (funkcją częściową) klasy dostępnej z instancji tej klasy.

Ponieważ instancja jest funkcją klasy i możesz wyprowadzić klasę z instancji, możesz uzyskać dowolne pożądane zachowanie z właściwości klasy za pomocą property:

class Example(object):
    _class_property = None
    @property
    def class_property(self):
        return self._class_property
    @class_property.setter
    def class_property(self, value):
        type(self)._class_property = value
    @class_property.deleter
    def class_property(self):
        del type(self)._class_property

Ten kod może służyć do testowania - powinien przejść bez generowania błędów:

ex1 = Example()
ex2 = Example()
ex1.class_property = None
ex2.class_property = 'Example'
assert ex1.class_property is ex2.class_property
del ex2.class_property
assert not hasattr(ex1, 'class_property')

Zwróć uwagę, że w ogóle nie potrzebowaliśmy metaklas - i tak nie masz bezpośredniego dostępu do metaklasy za pośrednictwem instancji jej klas.

pisanie @classpropertydekoratora

Rzeczywiście można stworzyć classpropertydekorator w ciągu zaledwie kilku linii kodu przez instacji property(nie jest to realizowane w C, ale można zobaczyć równoważne Python tutaj ):

class classproperty(property):
    def __get__(self, obj, objtype=None):
        return super(classproperty, self).__get__(objtype)
    def __set__(self, obj, value):
        super(classproperty, self).__set__(type(obj), value)
    def __delete__(self, obj):
        super(classproperty, self).__delete__(type(obj))

Następnie potraktuj dekorator tak, jakby był metodą klasową połączoną z właściwością:

class Foo(object):
    _bar = 5
    @classproperty
    def bar(cls):
        """this is the bar attribute - each subclass of Foo gets its own.
        Lookups should follow the method resolution order.
        """
        return cls._bar
    @bar.setter
    def bar(cls, value):
        cls._bar = value
    @bar.deleter
    def bar(cls):
        del cls._bar

A ten kod powinien działać bez błędów:

def main():
    f = Foo()
    print(f.bar)
    f.bar = 4
    print(f.bar)
    del f.bar
    try:
        f.bar
    except AttributeError:
        pass
    else:
        raise RuntimeError('f.bar must have worked - inconceivable!')
    help(f)  # includes the Foo.bar help.
    f.bar = 5

    class Bar(Foo):
        "a subclass of Foo, nothing more"
    help(Bar) # includes the Foo.bar help!
    b = Bar()
    b.bar = 'baz'
    print(b.bar) # prints baz
    del b.bar
    print(b.bar) # prints 5 - looked up from Foo!

    
if __name__ == '__main__':
    main()

Ale nie jestem pewien, jak dobrze byłoby to zalecane. Stary artykuł z listy mailingowej sugeruje, że to nie powinno działać.

Doprowadzenie nieruchomości do pracy na zajęciach:

Wadą powyższego jest to, że „właściwość klasy” nie jest dostępna z poziomu klasy, ponieważ po prostu nadpisałaby deskryptor danych z klasy __dict__.

Możemy jednak nadpisać to właściwością zdefiniowaną w metaklasie __dict__. Na przykład:

class MetaWithFooClassProperty(type):
    @property
    def foo(cls):
        """The foo property is a function of the class -
        in this case, the trivial case of the identity function.
        """
        return cls

Następnie instancja klasy metaklasy może mieć właściwość, która uzyskuje dostęp do właściwości klasy przy użyciu zasady przedstawionej już w poprzednich sekcjach:

class FooClassProperty(metaclass=MetaWithFooClassProperty):
    @property
    def foo(self):
        """access the class's property"""
        return type(self).foo

Teraz widzimy obie instancję

>>> FooClassProperty().foo
<class '__main__.FooClassProperty'>

i klasą

>>> FooClassProperty.foo
<class '__main__.FooClassProperty'>

mieć dostęp do właściwości klasy.


3
Jasna, zwięzła, dokładna: taka powinna być akceptowana odpowiedź.
— pfabri

25

Python 3!

Stare pytanie, wiele widoków, bardzo potrzebuję jednego, prawdziwego Pythona 3 sposobu.

Na szczęście z metaclasskwargiem jest to łatwe :

class FooProperties(type):

    @property
    def var(cls):
        return cls._var

class Foo(object, metaclass=FooProperties):
    _var = 'FOO!'

Następnie, >>> Foo.var

'BLA!'


1
to znaczy, że nie ma prostego wyjścia z pudełka
— mehmet

@mehmet Czy to nie jest proste? Foojest instancją swojej metaklasy i @propertymoże być używany w jej metodach tak samo, jak w przypadku instancji Foo.
— OJFord,

2
trzeba było zdefiniować inną klasę dla klasy, co oznacza podwójną złożoność przy założeniu, że metaklasa nie nadaje się do ponownego użycia.
— mehmet

Metoda klasowa działa zarówno z klasy, jak i instancji. Ta właściwość działa tylko z klasy. Nie sądzę, żeby o to się prosiło.
— Aaron Hall

1
@AaronHall Jeśli to ważne, łatwo je dodać Foo.__new__. Chociaż w tym momencie warto albo zamiast tego użyć getattribute, albo zapytać, czy udawanie, że istnieje funkcja języka, jest naprawdę podejściem, które w ogóle chcesz przyjąć.
— OJFord

16

Nie ma rozsądnego sposobu, aby ten system „właściwości klasy” działał w Pythonie.

Oto jeden nierozsądny sposób, aby to zadziałało. Z pewnością możesz uczynić to bardziej płynnym, zwiększając ilość magii metaklas.

class ClassProperty(object):
    def __init__(self, getter, setter):
        self.getter = getter
        self.setter = setter
    def __get__(self, cls, owner):
        return getattr(cls, self.getter)()
    def __set__(self, cls, value):
        getattr(cls, self.setter)(value)

class MetaFoo(type):
    var = ClassProperty('getvar', 'setvar')

class Foo(object):
    __metaclass__ = MetaFoo
    _var = 5
    @classmethod
    def getvar(cls):
        print "Getting var =", cls._var
        return cls._var
    @classmethod
    def setvar(cls, value):
        print "Setting var =", value
        cls._var = value

x = Foo.var
print "Foo.var = ", x
Foo.var = 42
x = Foo.var
print "Foo.var = ", x

Problem polega na tym, że Python nazywa właściwościami „deskryptorami”. Nie ma krótkiego i łatwego sposobu na wyjaśnienie, jak działa tego rodzaju metaprogramowanie, więc muszę wskazać wam deskryptor Howto .

Musisz zrozumieć tego rodzaju rzeczy tylko wtedy, gdy implementujesz dość zaawansowany framework. Na przykład utrwalanie obiektów przezroczystych lub system RPC lub rodzaj języka specyficznego dla domeny.

Jednak w komentarzu do poprzedniej odpowiedzi mówisz, że ty

trzeba zmodyfikować atrybut, który w sposób widoczny dla wszystkich instancji klasy oraz w zakresie, z którego wywoływane są te metody klasy, nie zawiera odniesień do wszystkich instancji klasy.

Wydaje mi się, że to, czego naprawdę chcesz, to wzorzec projektowy Observer .


Podoba mi się pomysł z przykładowym kodem, ale wydaje się, że w praktyce byłby trochę niezgrabny.
— Mark Roddy,

To, co próbuję osiągnąć, to dość proste ustawienie i uzyskanie pojedynczego atrybutu, który jest używany jako flaga do modyfikowania zachowania wszystkich instancji, więc myślę, że Observer byłby przesadzony z powodu tego, co próbuję zrobić. Gdyby chodziło o wiele atrybutów, byłbym bardziej skłonny.
— Mark Roddy,

Wydaje się, że zwykłe upublicznienie funkcji i bezpośrednie wywołanie ich było prostym rozwiązaniem. Byłem ciekawy, czy robię coś złego, czy też próbuję zrobić to niemożliwe. Przy okazji przepraszam za wiele komentarzy. Limit 300 znaków jest do niczego.
— Mark Roddy

Fajną rzeczą w tym przykładzie kodu jest to, że możesz zaimplementować wszystkie niezgrabne bity raz, a następnie odziedziczyć je. Po prostu przenieś _var do klasy pochodnej. class D1 (Foo): _var = 21 class D2 (Foo) _var = "Hello" D1.var 21 D2.var Witam
— Thomas L Holaday

6

Ustawienie go tylko w klasie meta nie pomaga, jeśli chcesz uzyskać dostęp do właściwości klasy za pośrednictwem obiektu, dla którego utworzono instancję, w tym przypadku musisz również zainstalować normalną właściwość na obiekcie (która jest wysyłana do właściwości klasy). Myślę, że to jest nieco bardziej jasne:

#!/usr/bin/python

class classproperty(property):
    def __get__(self, obj, type_):
        return self.fget.__get__(None, type_)()

    def __set__(self, obj, value):
        cls = type(obj)
        return self.fset.__get__(None, cls)(value)

class A (object):

    _foo = 1

    @classproperty
    @classmethod
    def foo(cls):
        return cls._foo

    @foo.setter
    @classmethod
    def foo(cls, value):
        cls.foo = value

a = A()

print a.foo

b = A()

print b.foo

b.foo = 5

print a.foo

A.foo = 10

print b.foo

print A.foo

3

Połowa rozwiązania, __set__ w klasie nadal nie działa. Rozwiązaniem jest niestandardowa klasa właściwości implementująca zarówno właściwość, jak i metodę statyczną

class ClassProperty(object):
    def __init__(self, fget, fset):
        self.fget = fget
        self.fset = fset

    def __get__(self, instance, owner):
        return self.fget()

    def __set__(self, instance, value):
        self.fset(value)

class Foo(object):
    _bar = 1
    def get_bar():
        print 'getting'
        return Foo._bar

    def set_bar(value):
        print 'setting'
        Foo._bar = value

    bar = ClassProperty(get_bar, set_bar)

f = Foo()
#__get__ works
f.bar
Foo.bar

f.bar = 2
Foo.bar = 3 #__set__ does not

3

Ponieważ muszę zmodyfikować atrybut, który w taki sposób, który jest widoczny dla wszystkich wystąpień klasy, oraz w zakresie, z którego te metody są wywoływane, nie ma odwołań do wszystkich wystąpień klasy.

Czy masz dostęp do co najmniej jednej instancji tej klasy? Mogę wtedy wymyślić sposób, aby to zrobić:

class MyClass (object):
    __var = None

    def _set_var (self, value):
        type (self).__var = value

    def _get_var (self):
        return self.__var

    var = property (_get_var, _set_var)

a = MyClass ()
b = MyClass ()
a.var = "foo"
print b.var

2

Spróbuj, a zadanie zostanie wykonane bez konieczności zmiany / dodawania dużej ilości istniejącego kodu.

>>> class foo(object):
...     _var = 5
...     def getvar(cls):
...         return cls._var
...     getvar = classmethod(getvar)
...     def setvar(cls, value):
...         cls._var = value
...     setvar = classmethod(setvar)
...     var = property(lambda self: self.getvar(), lambda self, val: self.setvar(val))
...
>>> f = foo()
>>> f.var
5
>>> f.var = 3
>>> f.var
3

propertyFunkcja wymaga dwóch callableargumentów. daj im opakowania lambda (które przekazuje instancję jako pierwszy argument) i wszystko jest w porządku.


Jak wskazuje Florian Bösch, składnia wymagana (przez biblioteki innych firm lub starszy kod) to foo.var.
— Thomas L Holaday

2

Oto rozwiązanie, które powinno działać zarówno w przypadku dostępu za pośrednictwem klasy, jak i dostępu za pośrednictwem instancji używającej metaklasy.

In [1]: class ClassPropertyMeta(type):
   ...:     @property
   ...:     def prop(cls):
   ...:         return cls._prop
   ...:     def __new__(cls, name, parents, dct):
   ...:         # This makes overriding __getattr__ and __setattr__ in the class impossible, but should be fixable
   ...:         dct['__getattr__'] = classmethod(lambda cls, attr: getattr(cls, attr))
   ...:         dct['__setattr__'] = classmethod(lambda cls, attr, val: setattr(cls, attr, val))
   ...:         return super(ClassPropertyMeta, cls).__new__(cls, name, parents, dct)
   ...:

In [2]: class ClassProperty(object):
   ...:     __metaclass__ = ClassPropertyMeta
   ...:     _prop = 42
   ...:     def __getattr__(self, attr):
   ...:         raise Exception('Never gets called')
   ...:

In [3]: ClassProperty.prop
Out[3]: 42

In [4]: ClassProperty.prop = 1
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-4-e2e8b423818a> in <module>()
----> 1 ClassProperty.prop = 1

AttributeError: can't set attribute

In [5]: cp = ClassProperty()

In [6]: cp.prop
Out[6]: 42

In [7]: cp.prop = 1
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-7-e8284a3ee950> in <module>()
----> 1 cp.prop = 1

<ipython-input-1-16b7c320d521> in <lambda>(cls, attr, val)
      6         # This makes overriding __getattr__ and __setattr__ in the class impossible, but should be fixable
      7         dct['__getattr__'] = classmethod(lambda cls, attr: getattr(cls, attr))
----> 8         dct['__setattr__'] = classmethod(lambda cls, attr, val: setattr(cls, attr, val))
      9         return super(ClassPropertyMeta, cls).__new__(cls, name, parents, dct)

AttributeError: can't set attribute

Działa to również z seterem zdefiniowanym w metaklasie.


1

Po przeszukaniu różnych miejsc znalazłem metodę definiowania właściwości klasy poprawnej w Pythonie 2 i 3.

from future.utils import with_metaclass

class BuilderMetaClass(type):
    @property
    def load_namespaces(self):
        return (self.__sourcepath__)

class BuilderMixin(with_metaclass(BuilderMetaClass, object)):
    __sourcepath__ = 'sp'        

print(BuilderMixin.load_namespaces)

Mam nadzieję, że to może komuś pomóc :)


1
Jeśli ta metoda jest czymś, co gdzieś znalazłeś, dobrze byłoby podać link (patrz Jak odwoływać się do materiałów napisanych przez innych )
— Andrew Myers

-27

Oto moja sugestia. Nie używaj metod klasowych.

Poważnie.

Jaki jest powód używania w tym przypadku metod klasowych? Dlaczego nie mieć zwykłego przedmiotu zwykłej klasy?


Jeśli chcesz po prostu zmienić wartość, właściwość nie jest zbyt pomocna, prawda? Po prostu ustaw wartość atrybutu i skończ z tym.

Właściwość powinna być używana tylko wtedy, gdy jest coś do ukrycia - coś, co może się zmienić w przyszłej implementacji.

Może twój przykład jest znacznie okrojony i jest jakieś piekielne obliczenia, które przerwałeś. Ale wygląda na to, że nieruchomość nie wnosi znaczącej wartości.

Techniki "prywatności" oparte na Javie (w Pythonie nazwy atrybutów zaczynające się od _) nie są zbyt pomocne. Od kogo prywatne? Kwestia prywatności jest trochę mglista, gdy masz źródło (tak jak w Pythonie).

Metody pobierające i ustawiające w stylu EJB oparte na Javie (często wykonywane jako właściwości w Pythonie) służą do ułatwienia prymitywnej introspekcji w Javie, a także do przekazywania testów za pomocą kompilatora języka statycznego. Wszystkie te metody pobierające i ustawiające nie są tak pomocne w Pythonie.


14
Ponieważ muszę zmodyfikować atrybut, który w taki sposób, który jest widoczny dla wszystkich wystąpień klasy, oraz w zakresie, z którego te metody są wywoływane, nie ma odwołań do wszystkich wystąpień klasy.
— Mark Roddy,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.