Расширенное объектно-ориентированное программирование для изучения Python

искусственный интеллект Python переводчик

Учебный каталог Python

  1. Использование Python3 под Mac
  2. Типы данных для изучения Python
  3. Функция обучения Python
  4. Расширенные возможности обучения Python
  5. Функциональное программирование для изучения Python
  6. Модуль обучения Python
  7. Объектно-ориентированное программирование для изучения Python
  8. Расширенное объектно-ориентированное программирование для изучения Python
  9. Отладка ошибок и тестирование обучения Python
  10. Программирование ввода-вывода для изучения Python
  11. Процесс обучения Python и поток
  12. Регулярность изучения Python
  13. Общие модули для изучения Python
  14. Python обучение сетевому программированию

Инкапсуляция данных, наследование и полиморфизм — это всего лишь три основных понятия объектно-ориентированного программирования. В Python объектная ориентация также имеет множество дополнительных функций, таких как: множественное наследование, пользовательские классы, метаклассы и другие концепции.

_slots_

Роль: ограничить свойства экземпляра.

Python позволяет определить специальный класс при определении класса__slots__переменная, чтобы ограничить свойства, которые может добавить экземпляр класса:

class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
    
>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'

так как'score'не был размещен__slots__, поэтому его нельзя связатьscoreсобственность, пытаясь связатьscoreполучитеAttributeErrorошибка.

использовать__slots__обращать внимание,__slots__Определенные свойства работают только с текущим экземпляром класса и не влияют на унаследованные подклассы:

>>> class GraduateStudent(Student):
...     pass
...
>>> g = GraduateStudent()
>>> g.score = 9999

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

@property

Декораторы могут динамически добавлять функции к функциям. Для методов класса декораторы работают точно так же.@propertyДекоратор отвечает за превращение метода в вызов свойства:

class Student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

Чтобы превратить метод получения в свойство, просто добавьте@propertyВот и все, в это время@propertyсам создает еще один декоратор@score.setter, отвечающий за превращение метода установки в присваивание свойства, поэтому у нас есть управляемая операция свойства:

>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
  ...
ValueError: score must between 0 ~ 100!

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

Вы также можете определить свойства только для чтения, определить только метод получения и не определять, что метод установки является свойством только для чтения:

class Student(object):

    @property
    def birth(self):
        return self._birth

    @birth.setter
    def birth(self, value):
        self._birth = value

    @property
    def age(self):
        return 2015 - self._birth

вышеbirthявляется свойством чтения-записи, иageтолько одинтолько чтениесвойства, потому чтоageможет основываться наbirthи текущее время.

множественное наследование

При проектировании отношения наследования классов обычно основная линия наследуется от одиночной линии, например,Ostrichунаследовано отBird. Однако, если вам нужно «подмешать» дополнительный функционал, вы можете сделать это через множественное наследование, например, пустьOstrichПомимо наследования отBirdК тому же, а потом наследуют заодноRunnable. Этот дизайн часто называют MixIn.

Чтобы лучше увидеть отношение наследования, положимRunnableиFlyableизменить наRunnableMixInиFlyableMixIn. Точно так же вы можете определить хищниковCarnivorousMixInи травоядныеHerbivoresMixIn, пусть у животного одновременно несколько MixIn:

class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
    pass

Цель MixIn — добавить несколько функций в класс Таким образом, при проектировании класса мы отдаем приоритет объединению функций нескольких MixIn посредством множественного наследования, а не разработке сложных многоуровневых отношений наследования.

пользовательский класс

к чему-то вроде__slots__эта форма__xxx__Обратите внимание, что имена переменных или функций используются в Python для особых целей.__slots__Мы уже умеем им пользоваться,__len__()Мы также знаем, что метод заключается в том, чтобы позволить классу воздействовать наlen()функция. Кроме того, в классах Python есть много таких специальных функций, которые могут помочь нам настроить классы.

_str_

>>> class Student(object):
...     def __init__(self, name):
...         self.name = name
...     def __str__(self):
...         return 'Student object (name: %s)' % self.name
...
>>> print(Student('Michael'))
Student object (name: Michael)

Распечатанный таким образом экземпляр не только красив, но и позволяет легко увидеть важные данные внутри экземпляра. Но вам не нужно вводить переменную напрямуюprint, печатный пример по-прежнему выглядит не очень красиво:

>>> s = Student('Michael')
>>> s
<__main__.Student object at 0x109afb310>

Это связано с тем, что прямые вызовы отображаемых переменных не__str__(), но__repr__(), разница между ними__str__()возвращает строку, которую видит пользователь, а__repr__()Возвращает строку так, как ее видит разработчик программы, т. е.__repr__()предназначен для отладки.

Решение состоит в том, чтобы определить другой__repr__(). но обычно__str__()и__repr__()Код тот же, поэтому есть ленивый способ написания:

class Student(object):
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return 'Student object (name=%s)' % self.name
    __repr__ = __str__

_iter_

Если класс хочет использоваться дляfor ... inЦиклы, такие как список или кортеж, должны реализовывать__iter__()метод, который возвращает итерируемый объект, а затем цикл for Python будет продолжать вызывать итеративный объект__next__()метод для получения следующего значения в цикле, пока не встретитсяStopIterationВыход из цикла при ошибке.

Давайте возьмем в качестве примера числовую последовательность Фибоначчи и напишем класс Фибоначчи, который может действовать в цикле for:

class Fib(object):
   def __init__(self):
       self.a, self.b = 0, 1 # 初始化两个计数器a,b

   def __iter__(self):
       return self # 实例本身就是迭代对象,故返回自己

   def __next__(self):
       self.a, self.b = self.b, self.a + self.b # 计算下一个值
       if self.a > 100000: # 退出循环的条件
           raise StopIteration()
       return self.a # 返回下一个值

Теперь попробуйте применить экземпляр Fib к циклу for:

>>> for n in Fib():
...     print(n)
...
1
1
2
3
5
...
46368
75025

_getitem_

Чтобы извлечь элементы по индексу, как список, вам нужно реализовать__getitem__()метод:

class Fib(object):
    def __getitem__(self, n):
        a, b = 1, 1
        for x in range(n):
            a, b = b, a + b
        return a

Теперь вы можете получить доступ к любому элементу последовательности по индексу:

>>> f = Fib()
>>> f[0]
1
>>> f[1]
1
>>> f[2]
2
>>> f[3]
3
>>> f[10]
89
>>> f[100]
573147844013817084101

Но у списка есть волшебный метод нарезки:

>>> list(range(100))[5:10]
[5, 6, 7, 8, 9]

Для Fib он сообщает об ошибке. Причина в том, что__getitem__()Входящий параметр может быть объектом типа int или slice.slice, так что судите:

class Fib(object):
    def __getitem__(self, n):
        if isinstance(n, int): # n是索引
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        if isinstance(n, slice): # n是切片
            start = n.start
            stop = n.stop
            if start is None:
                start = 0
            a, b = 1, 1
            L = []
            for x in range(stop):
                if x >= start:
                    L.append(a)
                a, b = b, a + b
            return L 

_getattr_

Python имеет механизм для написания__getattr__()метод, который динамически возвращает свойство:

class Student(object):

    def __init__(self):
        self.name = 'Michael'

    def __getattr__(self, attr):
        if attr=='score':
            return 99

При вызове несуществующего свойства, напримерscore, интерпретатор Python попытается вызвать__getattr__(self, 'score')чтобы попытаться получить собственность, таким образом, у нас есть шанс вернутьсяscoreЗначение:

>>> s = Student()
>>> s.name
'Michael'
>>> s.score
99

Также совершенно нормально возвращать функцию:

class Student(object):

    def __getattr__(self, attr):
        if attr=='age':
            return lambda: 25

Просто позвоните, чтобы измениться на:

>>> s.age()
25

Обратите внимание, что он вызывается только в том случае, если свойство не найдено.__getattr__, существующие свойства, такие какname, не будет в__getattr__Найти в.

Также обратите внимание, что произвольные вызовы, такие какs.abcвернусьNone, это потому, что мы определяем__getattr__Возврат по умолчаниюNone. Чтобы класс реагировал только на несколько определенных свойств, мы должны следовать соглашению и бросатьAttributeErrorошибка:

class Student(object):

    def __getattr__(self, attr):
        if attr=='age':
            return lambda: 25
        raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)

_call_

Экземпляр объекта может иметь свои собственные свойства и методы, когда мы вызываем методы экземпляра, мы используемinstance.method()звонить.

Точно так же любой класс должен только определить__call__()метод, вы можете вызвать экземпляр напрямую. См. пример:

class Student(object):
    def __init__(self, name):
        self.name = name

    def __call__(self):
        print('My name is %s.' % self.name)
        
>>> s = Student('Michael')
>>> s() # self参数不要传入
My name is Michael.

__call__()Параметры также могут быть определены. Прямой вызов экземпляра подобен вызову функции, поэтому вы можете думать об объекте как о функции, а о функции как об объекте, потому что между ними нет принципиальной разницы.

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

Итак, как определить, является ли переменная объектом или функцией? На самом деле чаще нам нужно судить о том, можно ли назвать объект, а объект, который можно назвать, являетсяCallableОбъекты, такие как функции и те, которые мы определили выше с помощью__call__()экземпляр класса:

>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('str')
False

перечисляемый класс

"класс перечисления"EnumЯвляется ли определение типа класса для типа перечисления, тогда каждая константа является уникальным экземпляром класса.

from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

Итак, мы получаемMonthКласс перечисления типа, который можно использовать напрямуюMonth.Janдля ссылки на константу или для перечисления всех ее членов:

for name, member in Month.__members__.items():
    print(name, '=>', member, ',', member.value)

valueАтрибуты автоматически назначаются членамintпостоянная, по умолчанию от1Начните считать.

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

from enum import Enum, unique

@unique
class Weekday(Enum):
    Sun = 0 # Sun的value被设定为0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6

@uniqueДекораторы могут помочь нам убедиться, что нет повторяющихся значений.

Есть несколько способов получить доступ к этим типам перечислений:

>>> day1 = Weekday.Mon
>>> print(day1)
Weekday.Mon
>>> print(Weekday.Tue)
Weekday.Tue
>>> print(Weekday['Tue'])
Weekday.Tue
>>> print(Weekday.Tue.value)
2
>>> print(day1 == Weekday.Mon)
True
>>> print(day1 == Weekday.Tue)
False
>>> print(Weekday(1))
Weekday.Mon
>>> print(day1 == Weekday(1))
True
>>> Weekday(7)
Traceback (most recent call last):
  ...
ValueError: 7 is not a valid Weekday
>>> for name, member in Weekday.__members__.items():
...     print(name, '=>', member)
...
Sun => Weekday.Sun
Mon => Weekday.Mon
Tue => Weekday.Tue
Wed => Weekday.Wed
Thu => Weekday.Thu
Fri => Weekday.Fri
Sat => Weekday.Sat

Видно, что на константы перечисления можно ссылаться по именам членов, а константы перечисления можно получить напрямую по значению value.

метакласс

type()

Самая большая разница между динамическими языками и статическими языками заключается в том, что определения функций и классов не определяются во время компиляции, а динамически создаются во время выполнения.

Допустим, мы хотим определитьHelloкласс, просто напишиhello.pyМодуль:

class Hello(object):
    def hello(self, name='world'):
        print('Hello, %s.' % name)

Когда интерпретатор Python загружаетсяhelloмодуль, все операторы модуля будут выполняться по очереди, а результатом выполнения является динамическое созданиеHelloобъект класса, тест выглядит следующим образом:

>>> from hello import Hello
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class 'hello.Hello'>

type()Функция может смотреть на тип типа или переменной,Helloэто класс, тип которогоtypehявляется экземпляром, тип которого является классомHello.

Мы говорим, что определение класса создается динамически во время выполнения, и способ создания класса заключается в использованииtype()функция.

type()Функции могут как возвращать тип объекта, так и создавать новые типы, например, мы можем передатьtype()функция созданаHelloкласс без прохожденияclass Hello(object)...Определение:

>>> def fn(self, name='world'): # 先定义函数
...     print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class '__main__.Hello'>

Чтобы создать объект класса,type()Функция передает по очереди 3 параметра:

  1. название класса;
  2. Коллекция унаследованных родительских классов.Обратите внимание, что Python поддерживает множественное наследование.Если есть только один родительский класс, не забудьте написать один элемент кортежа;
  3. Имя метода класса привязано к функции, здесь мы помещаем функциюfnпривязать к имени методаhelloначальство.

пройти черезtype()Класс, созданный функцией, точно такой же, как и запись класса напрямую, потому что, когда интерпретатор Python встречает определение класса, он просто просматривает синтаксис определения класса, а затем вызываетtype()Функции создают классы.

Обычно мы используемclass Xxx...однако для определения классаtype()Функции также позволяют нам динамически создавать классы, то есть сами динамические языки поддерживают динамическое создание классов во время выполнения, что сильно отличается от статических языков.Чтобы создавать классы во время выполнения на статических языках, вы должны создавать строки исходного кода. а затем вызов компилятора или создание реализаций байт-кода с помощью некоторых инструментов, по сути, является динамической компиляцией, которая может быть очень сложной.

metaclass

Помимо использованияtype()Помимо динамического создания классов, для управления поведением при создании классов вы также можете использовать метаклассы.

метакласс, дословно переводится как метакласс, простое объяснение:

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

Но что, если мы хотим создать классы? Затем класс должен быть создан в соответствии с метаклассом, поэтому: сначала определите метакласс, а затем создайте класс.

Связь такова: сначала определите метакласс, вы можете создать класс и, наконец, создать экземпляр.

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

Далее: Отладка ошибок и тестирование обучения Python