面向对象编程#
面向过程编程(Procedure Oriented Programming,POP):把计算机程序视为一组函数的顺序执行,为简化程序设计,把函数继续切分为子函数来降低系统的复杂度。
面向对象编程(Object Oriented Programming,OOP):把计算机程序视为一组对象的集合,一个对象包含了数据和操作数据的函数,计算机程序的执行就是一系列消息在各个对象之间传递。
1. 类和对象#
面向对象的设计思想是抽象出类(Class),然后以类为模板,创建实例(Instance,也叫对象),对象是类的具体化。
类定义的变量是静态变量(储存在内存中,类被删后,仍被对象对象调用)
类定义的属性是静态属性,对象的参数修改不影响类中的参数
函数定义的是方法,属性名不能与函数名重复,负责将覆盖函数
1.1. 类的定义#
类的三要素
名称:类名
属性:初始化的数据
方法:即类中的函数,不同于普通函数,有些特有性质
创建对象:对象名 = 类名 (),创建出来的对象拥有类的数据和方法
调用方法:对象名。函数名 ()
添加属性:对象名。属性名
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
中删除。
因此,WeakValueDictionar
y 经常用于缓存。
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)