面向对象编程#

  • 面向过程编程(Procedure Oriented Programming,POP):把计算机程序视为一组函数的顺序执行,为简化程序设计,把函数继续切分为子函数来降低系统的复杂度。

  • 面向对象编程(Object Oriented Programming,OOP):把计算机程序视为一组对象的集合,一个对象包含了数据和操作数据的函数,计算机程序的执行就是一系列消息在各个对象之间传递。

1. 类和对象#

面向对象的设计思想是抽象出类(Class),然后以类为模板,创建实例(Instance,也叫对象),对象是类的具体化。

  • 类定义的变量是静态变量(储存在内存中,类被删后,仍被对象对象调用)

  • 类定义的属性是静态属性,对象的参数修改不影响类中的参数

  • 函数定义的是方法,属性名不能与函数名重复,负责将覆盖函数

1.1. 类的定义#

  • 类的三要素

  1. 名称:类名

  2. 属性:初始化的数据

  3. 方法:即类中的函数,不同于普通函数,有些特有性质

  • 创建对象:对象名 = 类名 (),创建出来的对象拥有类的数据和方法

  • 调用方法:对象名。函数名 ()

  • 添加属性:对象名。属性名

2. 构建#

2.1. self#

self 是方法的一个特殊参数,可自定义名称,但必须作为第一个参数,在调用时不必传递给实例。self 保证属性可被不同对象调用。

class Car:
    def move(self):
        print(f'A {self.color} car is running')


# 创建对象
BMW = Car()
AUDI = Car()
# 添加属性
BMW.color = 'black'
AUDI.color = 'white'
# 调用方法
BMW.move()
AUDI.move()

2.2. 初始化#

双下划线方法(dunder methods),也叫魔法方法(magic methods)。

__init__() 用于初始化。其定义默认的对象属性,没有返回值。 __init__(self) 中,在 self 后添加的参数为默认参数,相当于 C 语言中的 main() 函数。

class Car:

    # 定义默认属性
    def __init__(self):
        self.wheelNum = 4
        self.color = 'blue'


# 创建对象
BMW = Car()
print(f'car color:{BMW.color}')
print(f'wheel number:{BMW.wheelNum}')

2.3. 打印#

  • __repr__:以便于开发者理解的方式返回对象的字符串表示形式。

  • __str__ :以便于用户理解的方式返回对象的字符串表示形式。

但,通常 __str__()__repr__() 代码均是一样的

class Car:

    def __init__(self, newWheelNum, newColor):
        self.wheelNum = newWheelNum
        self.color = newColor

    def __str__(self):
        msg = f"My color is {self.color}, I have {self.wheelNum:d} wheels"
        return msg

    # 偷懒写法
    # __repr__ = __str__


BMW = Car(4, 'white')
print(BMW)

3. 类的性质#

3.1. 私有化#

Python 不能像 C++ 那样使用 private 修饰符创建私有属性,但是 Python 有个简单的机制,能避免子类意外覆盖”私有”属性。

  • __XX:私有方法或属性,无法用 import 导入,需要通过调用类中其他方法调用。

  • __XX__:魔法方法或属性,无法从外部调用,无法通过类从外部调用。

若以 __XX 的形式命名实例属性,Python 会把属性名存入实例的 __dict__ 属性中,而且会在前面加上一个下划线和类名,以__className__XX 的形式储存,可此调用。

class Msg:

    # 私有方法
    def __send_msg(self):
        print('Sending')

    # 公有方法,用于判断是否调用私有方法
    def send_msg(self, money):
        if money > 10:
            self.__send_msg()
        else:
            print('Need to charge')


msg = Msg()
msg.send_msg(100)

3.2. 对象属性#

类属性:指类中方法外的属性,可直接调用。对象属性:指类中方法内的属性,必须通过对象进行调用,对象属性会屏蔽掉同名的类属性,调用时优先调用对象属性。

class People():

    address = 'Shandong'  # 类属性
    __height = 180  # 私有类属性

    def __init__(self):
        self.name = 'xiaowang'  # 对象属性
        self.age = 20  # 对象属性


print(People.address)  #正确
print(People.__height)  #错误
print(People.name)  #错误
print(p.name)  #正确

4. 常用类装饰器#

  • 对象方法:类中的普通方法

  • 类方法:直接对类属性操作的方法

4.1. 两种方法#

classmethod 装饰器非常有用,staticmethod 不是特别有用。

class Demo:

    @classmethod
    def klassmeth(*args):
        return args

    @staticmethod
    def statmeth(*args):
        return args


print(Demo.klassmeth())  # (<class '__main__.Demo'>,)
print(Demo.klassmeth('spam'))  # (<class '__main__.Demo'>, 'spam')
print(Demo.statmeth('spam'))  # ('spam',)

4.2. 常量枚举#

当需要定义常量时,一个办法是用大写变量通过整数来定义,好处是简单,缺点是类型是 int,且仍然是变量。更好的方法是为这样的枚举类型定义一个类,然后,每个常量均是类的一个唯一实例。Python 提供了 Enum 类来实现这个功能:

from enum import Enum

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

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

若需要更精确地控制枚举类型,可从 Enum 派生出自定义类:

@unique 装饰器可帮助我们检查保证没有重复值。

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

5. 引用#

5.1. 可变参数的风险#

对可变参数,需要对其副本进行操作。

class TwilightBus:
    """让乘客销声匿迹的校车"""

    def __init__(self, passengers=None):
        if passengers is None:
            self.passengers = []
        else:
            self.passengers = passengers
            # 创建副本
            # self.passengers = list(passengers)

    def pick(self, name):
        self.passengers.append(name)

    def drop(self, name):
        self.passengers.remove(name)


basketball_team = ['Sue', 'Tina', 'Maya', 'Diana', 'Pat']
bus = TwilightBus(basketball_team)
bus.drop('Tina')
bus.drop('Pat')
print(basketball_team)
# ['Sue', 'Maya', 'Diana'],原列表也被修改了!

5.2. 垃圾回收#

del 不删除对象,而是删除对象的引用。

在 CPython 中,垃圾回收使用的主要算法是引用计数。实际上,每个对象都会统计有多少引用指向自己。当引用计数归零时,对象立即就被销毁:CPython 会在对象上调用 __del__ 方法(若定义了),然后释放分配给对象的内存。

自己编写的代码很少需要实现 __del__ 代码。

from weakref import finalize

s1 = {1, 2, 3}
s2 = s1


def bye():
    print('Gone with the wind...')


ender = finalize(s1, bye)
print(ender.alive)  # True
del s1
print(ender.alive)  # True

5.3. 弱引用#

弱引用是可调用的对象,返回的是被引用的对象;若所指对象不存在了,返回 None。

from weakref import ref

a_set = {0, 1}
wref = ref(a_set)
print(wref())  # {0, 1}

a_set = {2, 3, 4}
print(wref())  # None

不是每个 Python 对象都可以作为弱引用的目标(或称所指对象)。基本的 list 和 dict 实 例不能作为所指对象,但是它们的子类(见章 11)可以解决这个问题。

class MyList(list):
    pass


a_list = MyList(range(10))
# a_list 可以作为弱引用的目标
wref_to_a_list = ref(a_list)

int 和 tuple 实例不能作为弱引用的目标,甚至它们的子类也不行。

5.4. 内存泄露#

WeakValueDictionary 类实现的是一种可变映射,里面的值是对象的弱引用。被引用的对象 在程序中的其他地方被当作垃圾回收后,对应的键会自动从 WeakValueDictionary 中删除。 因此,WeakValueDictionary 经常用于缓存。

from weakref import WeakKeyDictionary


class Grade:

    def __init__(self):
        self._values = WeakKeyDictionary()

    def __get__(self, instance, instance_type):
        if instance is None:
            return self
        return self._values.get(instance, 0)

    def __set__(self, instance, value):
        if not (0 <= value <= 100):
            raise ValueError('Grade must be between 0 and 100')
        self._values[instance] = value


class Exam:
    math_grade = Grade()
    writing_grade = Grade()
    science_grade = Grade()


first_exam = Exam()
first_exam.writing_grade = 82
second_exam = Exam()
second_exam.writing_grade = 75
print(f'First {first_exam.writing_grade} is right')
print(f'Second {second_exam.writing_grade} is right')

6. 妙用#

6.1. 处理 JSON#

假设现在需要从两份旅游数据中获取人员名单

{ // 去过普吉岛的人员数据
    "users_visited_phuket": [
        {
            "first_name": "Sirena",
            "last_name": "Gross",
            "phone_number": "650-568-0388",
            "date_visited": "2018-03-14"
        },
        {
            "first_name": "James",
            "last_name": "Ashcraft",
            "phone_number": "412-334-4380",
            "date_visited": "2014-09-16"
        }
    ],
    // 去过新西兰的人员数据
    "users_visited_nz": [
        {
            "first_name": "Justin",
            "last_name": "Malcom",
            "phone_number": "267-282-1964",
            "date_visited": "2011-03-13"
        },
        {
            "first_name": "Albert",
            "last_name": "Potter",
            "phone_number": "702-249-3714",
            "date_visited": "2013-09-11"
        }
    ]
}

进一步,改写成

class VisitRecordDC:
    self.first_name = first_name
    self.last_name = last_name
    self.phone_number = phone_number
    self.date_visited = field(hash=False, compare=False)


def find_potential_customers_v4():
    return set(VisitRecordDC(**r) for r in users_visited_phuket) - \
        set(VisitRecordDC(**r) for r in users_visited_nz)