Python — подробные __slots__, свойства и соглашения об именах

Python

Эта статья возникла из личного публичного аккаунта:TechFlow, оригинальность это не просто, прошу внимания


СегодняЧасть 11 тем PythonВ этой статье давайте поговорим о некоторых продвинутых способах использования объектно-ориентированного подхода.

__slots__

Если вы посмотрите на код некоторых больших коров на github, вы обнаружите, что многие большие коровы часто добавляют ключевое слово __slots__ в начало класса. Если вам достаточно любопытно, вы можете попробовать удалить это ключевое слово и повторить попытку, и вы обнаружите, что после его удаления ничего не происходит, все по-прежнему работает нормально.

Итак, что именно делает это ключевое слово __slots__?

В основном он имеет две функции, давайте сначала поговорим о первой функции, котораяОграничить использование пользователей.

Все мы знаем, что Python — очень гибкийдинамический язык, многие вещи, которые кажутся совершенно невыносимыми в других языках, осуществимы в Python, что также является философией дизайна Python, жертвуя эффективностью ради гибкости и удобства кода. Например, давайте рассмотрим очень простой пример: поскольку Python — динамический язык, члены класса могут быть созданы динамически даже после создания класса. Это абсолютно невозможно в статическом языке, мы можем вызывать только существующие свойства в классе, даневозможно или сложноДобавьте новые свойства.

Например, этот код:

class Exp:
    def __init__(self):
        self.a = None
        self.b = None


if __name__ == "__main__":
    exp = Exp()
    exp.c = 3
    print(exp.c)

Мы определяем класс с именем Exp и создаем для него два члена a и b. Но когда мы используемПрисвоение производится члену c. Вы должны знать, что в классе Exp нет члена c, но программа не сообщит об ошибке После того, как мы запустим ее таким образом, она добавит c в экземпляр.

С одной стороны, это, конечно, очень гибко, но, с другой стороны, это также оставляетскрытая опасность. Если пользователи произвольно добавляют атрибуты, это может вызвать неизвестные проблемы, особенно в сложных системах. Поэтому иногда ради строгости мы не хотим, чтобы пользователи вносили такие динамические изменения. __slots__ используется именно для этого.

Мы добавляем это ключевое слово, и результат будет другим, когда мы запустим его снова:

class Exp:

    __slots__ = ['a', 'b']
    def __init__(self):
        self.a = None
        self.b = None


if __name__ == "__main__":
    exp = Exp()
    exp.c = 3
    print(exp.c)

Если вы запустите этот код, вы получите ошибку,Подскажите, что в объекте Exp нет члена c, то есть мы можем использовать только элементы, определенные в ключевом слове __slots__, а элементы, которые не определены, не могут быть созданы по желанию, что ограничивает использование пользователей.

Хотя сейчас большинство людей используют это ключевое слово для этой цели, к сожалению, изначальное намерение создателя Python не в этом. Это говорит о второй роли ключевого слова __slots__, котораясохранить память.

Если вы понимаете основные принципы реализации Python, вы обнаружите, что в PythonСловарь создается для каждого экземпляра, — это знаменитый словарь __dict__. Именно потому, что за этим стоит словарь, мы можем создавать члены, которые не существуют изначально, и поддерживать такие динамические эффекты. Мы можем вручную вызвать этот словарь для вывода его содержимого. Прежде чем мы добавим ключевое слово __slots__, вывод будет следующим:

{'a': None, 'b': None}

Но после добавления этого ключевого слова он будетполучить ошибку, сообщит вам, что в объекте Exp нет члена __dict__. Причина очень проста, потому что использование dict для обслуживания экземпляра будет потреблять много памяти и хранить много дополнительных данных.После использования __slots__ Python больше не будет создавать словарь для обслуживания экземпляра, а будет использовать фиксированный размер .массив, который экономит много места. Эта экономия не маленькая, вообще можетСэкономьте больше половины. То есть приносится в жертву определенная гибкость и гарантируется производительность. Это также первоначальное намерение ключевого слова __slots__, но теперь многие люди используют его не в том месте.

property

Это ключевое слово было упомянуто в статье, но меня очень смущает, что из-за ограниченного понимания его, когда я писал статью ранее, в некоторых объяснениях есть некоторые ошибки, поэтому я упомяну здесь использование этого ключевого слова в качестве компенсации. .

недвижимость может нам помочьПрисвоение и получение некоторых свойств в классе привязки, то есть получить и установить. Давайте посмотрим на пример:

class Exp:
    def __init__(self, param):
        self.param = param

    @property
    def param(self):
        return self._param

    @param.setter
    def param(self, value):
        self._param = value

Аннотация свойства здесь будет выполняться, когда мы вызываем .param, а param.setter будет выполняться, когда мы присваиваем значение свойству param. Итак, вам может быть интересно, почему мы используем self.param = param вместо self._param = param, когда мы инициализируем в методе __init__, потому что, когда мы выполняем первый, PythonТо же самое вызовет @param.setterЭто аннотация, поэтому нам не нужно писать последнюю форму. Конечно, вы можете написать это, но они полностью эквивалентны.

Как бывший программист JavaДобавьте методы get и set ко всем переменным в классе.Это почти политкорректно, поэтому мне особенно нравится добавлять свойства ко всем свойствам в классе. ноэто неправильно, plus свойство очень трудоемкое, так что не делайте этого без необходимости, мы просто вызываем его напрямую для присваивания, при необходимости, мы можем вручную написать методы get и set. Итак, вопрос в том, что, поскольку это не спецификация, почему мы используем свойство?

Ответ прост, ибоПроверить тип переменной.

Поскольку Python является динамическим языком и имеет неявную типизацию, мы не знаем, какой это тип переменной, когда мы ее получаем, и какой тип ей назначает пользователь. Поэтому в некоторых случаях мы можем захотеть ограничить и сообщить пользователю, что эта переменная может быть присвоена только этому типу, иначе будет сообщено об ошибке. Используя свойство, мы можем легко это сделать.

class Exp:

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

    @property
    def param(self):
        return self._param

    @param.setter
    def param(self, value):
        if not isinstance(value, str):
            raise TypeError('Want a string')
        self._param = value

Кроме того, другое использование собственностиЗамещающая функция. Например:

class Exp:

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

    @property
    def param(self):
        return self._param

    @param.setter
    def param(self, value):
        if not isinstance(value, str):
            raise TypeError('Want a string')
        self._param = value

    @property
    def hello(self):
        return 'hello ' + self.param

Таким образом, мы можем использовать .hello вместо вызова функции, которая на самом деле является своего родаДинамический расчет. Результат hello не сохраняется и будет выполнен позже, когда мы его вызовем, что очень удобно в некоторых сценариях.

Соглашения об именах

Наконец, давайте взглянем на соглашения об именах в объектах Python.В предыдущей статье мы говорили, что в Python нет различий между открытыми и закрытыми полями.Все поля открыты, что означает, что пользователь может получить все поля и методы класса. Чтобы стандартизировать, программисты договариваются и решают всеПодчеркнутые методы и переменные считаются закрытымиДа, даже если мы можем это назвать, но в целом мы этого не делаем.

Поэтому обычно мы пишем два метода: один — открытый интерфейс, а другой — внутренняя реализация. Когда мы вызываем, мы вызываем только общедоступный интерфейс, а общедоступный интерфейс вызывает внутреннюю реализацию. Это стало соглашением в Python, потому что, когда мы вызываем внутренние методы, мы часто передаем некоторые внутренние параметры.

Давайте рассмотрим простой пример:

class ExpA:

    def __init__(self):
        pass

    def public_func(self):
       self._private_func()

    def _private_func(self):
        print('private ExpA')


if __name__ == "__main__":
    exp = ExpA()
    exp.public_func()

В дополнение к _ мы часто видим некоторыеПеременные и методы с двумя символами подчеркивания, тогда какая между ними разница?

Чтобы ответить на этот вопрос, давайте рассмотрим следующий пример:

class ExpA:

    def __init__(self):
        pass

    def public_func(self):
       self.__private_func()

    def __private_func(self):
        print('private ExpA')

class ExpB(ExpA):

    def __init__(self):
        pass

    def public_func(self):
       self.__private_func()

    def __private_func(self):
        print('private ExpB')

if __name__ == "__main__":
    exp = ExpB()
    exp.public_func()
    exp._ExpB__private_func()
    exp._ExpA__private_func()

Каким будет конечный результат?

Давайте попробуем и узнаем, что вывод первой строки — это приватный ExpB, что не проблема. Но каковы последние два?

Последние два — __private_func, ноСистема автоматически переименовывает его. Причина переименования также проста, потому что PythonМетод с двумя символами подчеркивания запрещено переопределять подклассами. Таким образом, разница между ними заключается в том, что они оба считаются закрытыми методами и свойствами, но одно подчеркивание позволяет переопределять подклассы, а два подчеркивания - нет. Так что если мы надеемся, что один из наших методов не будет переопределен подклассами во время разработки, то нам нужно добавить два символа подчеркивания.

Наконец, давайте рассмотрим небольшую проблему. В C++, когда имена наших переменных конфликтуют с системными ключевыми словами, мы часто добавляем _ перед переменной, чтобы отличить ее. Но поскольку в Python символам подчеркивания присваиваются значения, мы не можем этого сделать, так что же нам делать, когда переменные конфликтуют? Ответ также очень прост, мы можемдобавить подчеркивание после, например лямбда_.

Суммировать

Просмотрите сегодняшний контент, в основном использование __slots__, свойства и подчеркивания в классах. Все три являются объектно-ориентированными в Python.часто используетсяЗнание о них не только позволит нам писать более стандартизированный код, но и поможет нам понять исходный код других больших коров, так что это очень необходимо.

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