Сеттеры для переменных класса - PullRequest
3 голосов
/ 12 апреля 2019

У меня есть простой класс в модуле с именем foo.py:

class Foo
    foo = 1

, который я импортирую в другие модули (bar.py и baz.py) и изменяю переменную класса foo внутри этих других модулей, например ::

# bar.py

from foo import Foo
print(Foo.foo) # should print 1.
Foo.foo = 2

и

# baz.py

from foo import Foo

print(Foo.foo) # should print 2.
Foo.foo = 3

Изменения в Foo.foo однако должны быть проверены перед их установкой. Поэтому в настоящее время я использую метод установки в Foo:

# foo.py

@classmethod
def set_foo(cls, new_foo):
    # do some checks on the supplied new_foo, then set foo.
    cls.foo = new_foo

Является ли это питонским способом установки переменной класса? Или есть лучшие методы (аналогичные объявлениям @property a и @a.setter для переменных экземпляра)? Я хочу, чтобы переменная класса foo сохранялась при импорте Foo в эти другие модули, и на самом деле не хочу создавать экземпляр Foo, так как это, скорее всего, вещь класса.

Спасибо ТАК; -)

Ответы [ 3 ]

1 голос
/ 12 апреля 2019

Если вы не против небольшой магии, это можно сделать относительно разумно, используя протокол дескриптора.

class FooDescriptor:

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return obj._foo

    def __set__(self, obj, value):
        if not isinstance(obj, type):
            # disable instance name shadowing for sanity's sake
            raise AttributeError("this attribute should be set on the class object")
        obj._foo = value + "!!"


class FooMeta(type):

    foo = FooDescriptor()

    def __new__(cls, clsname, bases, namespace):
        # pluck the "foo" attr out of the class namespace,
        # and swap in our descriptor in its place
        namespace["_foo"] = namespace.pop("foo", "(default foo val)")
        namespace["foo"] = FooMeta.foo
        return type.__new__(cls, clsname, bases, namespace)

При создании класса Foo это заменит класс fooатрибут, определенный обычным декларативным способом с дескриптором данных (для предоставления пользовательских методов получения и установки).Вместо этого мы будем хранить необработанное «неуправляемое» значение в Foo._foo.

Демо:

>>> class Foo(metaclass=FooMeta): 
...     foo = "foo0" 
... 
>>> obj = Foo() 
>>> obj.foo  # accessible from instance, like a class attr
'foo0'
>>> Foo.foo  # accessible from class
'foo0'
>>> Foo.foo = "foo1"  # setattr has magic, this will add exclams
>>> obj.foo
'foo1!!'
>>> Foo.foo
'foo1!!'
>>> vars(obj)  # still no instance attributes
{}
>>> type(Foo).foo  # who does the trick?
<__main__.FooDescriptor at 0xcafef00d>
>>> obj.foo = "boom"  # prevent name shadowing (optional!)
AttributeError: this attribute should be set on the class object
0 голосов
/ 12 апреля 2019

Я думаю, что «питонический способ», если он есть, использовать a.setter, используя формат _:

@property
def bar(self):
    return self._bar

@bar.setter
def environment(self, bar):
    # do some checks on the supplied bar, then set
    self._bar = bar

Таким образом, _bar является «приватным», и вы можете использовать «bar» в своем коде, когда хотите его установить.

0 голосов
/ 12 апреля 2019

Если вы планируете наследовать от Foo, имейте в виду, что classmethod изменит атрибут класса вызывающего экземпляра .

class Base:

    class_variable = 'base'

    @classmethod
    def set_cvar(cls, value):
        cls.class_variable = value

class Derived(Base):

    pass

Derived.set_cvar('derived')
print(Base.class_variable)
print(Derived.class_variable)

Выход:

base
derived

Это может (возможно, будет) или не может быть тем, что вы хотите. Альтернатива - вместо этого использовать staticmethod и явно указывать свой класс.

В общем, я бы посчитал это хорошим методом.

...