跳到主要内容

面向对象编程

信息
2024年8月10日 · ·

1. 面向对象编程

Object Oriented Programming

简称 OOP,一种程序设计思想,以对象为程序基本单元,一个对象包含数据和数据的操作方法;

面向对象的设计思想来自自然界的类(Class)和实例(Instance),抽象出 Class,根据 Class 创建 Instance,抽象程度比函数高(既包含数据,又包含操作数据的函数);

数据封装、继承、多态是面向对象的三大特点;

类和实例

Class 是抽象的模板,是创建 Instance 的模板;

>>> class Student(object): # 定义类
... pass
...
>>> s1 = Student() # 创建实例
>>> s1.name = 'Aurelius' # 实例属性绑定
>>> s1.name
'Aurelius'

Python 允许对实例绑定任何数据,即使一个类的两个实例,他们拥有的变量名称都可能不同;

创建实例时强制绑定属性;

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

和普通函数相比,类中定义的函数第一个参数永远是实例变量 self,且调用时不用传值,self 指向创建的实例本身

数据封装

通过实例函数访问实例的数据,不直接从外部访问这些数据;

访问限制

限制外部代码对实例内部一些属性和方法的访问,可以加强代码的健壮性;

Python 没有任何强制机制限制访问权限;

在 Class 定义的内部属性前加 __ 可以把属性变成私有,通过 get_p(), set_p() 方法来访问这些属性;

__property 被 Python 解释器自动改为 _ClassName__property

__xxx__ 在 Python 中属于特殊属性,不是私有的,可以被访问的;

继承和多态

  • 继承,可以把父类的所有功能直接拿来,子类只需要新增自己特有的方法,重写覆盖父类中不适合的方法;

  • 多态,是把一个子类对象赋给一个父类变量,调用方法是子类实现;

开闭原则

对扩展开放,对修改封闭;

鸭子模式

def twice_run(animal):
animal.run()
animal.run()

class Timer(object):
def run(self):
print('Start ...')

传入 twice_run() 的对象不必是 Animal 的子类,只要有 run() 即可;

因此相对静态语言,动态语言(Python)的继承不是那么必要;

type()

获取对象类型;

类型类型常量对象
intint123
strstr'123'
函数types.FunctionTypedef fn(): pass
内建函数types.BuiltinFunctionTypeabs
匿名函数types.LambdaTypelambda x: x
生成器types.GeneratorType(x for x in range(10))

isinstance()

判断是否指定类型或指定类型元组中的一个;

>>> isinstance(x, t) # x 是对象,t 是类型,t 可以是多个类型组成的一个 tuple 对象

dir()

获取对象的所有属性和方法;

len()

实际是调用了对象的 __len__() 方法,因此按照鸭子类型,自己的类只要实现了 __len__(),也可以使用 len(myObj);

getattr(obj, pname)

获取对象的指定属性或方法,也可以传第 3 个参数作为默认值;

setattr(obj, pname, pvalue)

设置对象指定属性的值;

hasattr(obj, pname)

判断对象是否存在指定名称的属性或方法;

可以确定存在而直接访问的属性或方法,就不要使用getattr,因此可以通过hasattr判断属性或方法是否存在;

实例属性和类属性

通过实例变量或self变量绑定实例属性

通过类本身绑定的是类属性,类属性归类所有,但类的所有实例都可以访问到;

>>> class Student(object):
... name = 'Student'
...
>>> s = Student()
>>> print(s.name)
Student
>>> print(Student.name)
Student
>>> s.name = 'Aurelius'
>>> print(s.name)
Aurelius
>>> print(Student.name)
Student
>>> del s.name
>>> print(s.name)
Student

相同名称的实例属性将屏蔽类属性(当查找到对应名称的实例属性时,即使存在同名类属性,也不会被查找到),当删除实例属性后,可以再次访问到类属性;

2. 面向对象高级编程

__slots__

动态绑定方法

Instance绑定方法,只对当前Instance有效;

class Student(object):
pass

s = Student()

def set_age(self, age):
self.age = age

from types import MethodType
s.set_age = MethodType(set_age, s)
s.set_age(25)

Class绑定方法,对所有Instance有效;

def set_score(self, score):
self.score = score

Student.set_score = set_score
s.set_score(100)

使用 __slots__

用来限制 class 实例可以添加的属性,包括Class定义中绑定的属性;

class Student(object):
# tuple定义允许绑定的属性名称
__slots__ = ('name', 'age')

__slots__只对当前类有效,对子类无效;若子类也定义了__slots__,有效范围是自身加父类的范围;

不定义__slots__的类相当于其有效范围是任意属性;

@property

把一个getter方法变成属性,同时创建另一个装饰器@pname.setter,负责把另一个setter方法变成属性赋值;

class Student(object):
@property
def birth(self):
return self._birth

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

# 只读属性
@property
def age(self):
return 2020 - self._birth

多重继承

Python 允许使用多重继承,一个子类可以同时继承多个父类的所有功能;

MixIn

除了继承自主线,还额外混入其他类的功能,这种设计叫MixIn

定制类

通过一些__xxx__属性定制类;

__str__

返回给用户看到的字符串,实例的打印结果;

__repr__

返回实例调式值显示结果;

__iter__

Class实例变成一个迭代器,需要实现__next__()方法,for 循环会不断调用迭代对象的__next__()

class Fib(object):
def __init__(self):
self.a, self.b = 0, 1

def __iter__(self):
return self

def __next__(self):
self.a, self.b = self.b, self.a + self.b
if self.a > 1000:
raise StopIteration()
return self.a

__getitem__

通过索引器或者切片读取实例的值时被调用;

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

slicestep参数和负数值可以进一步处理

__getattr__

当调用实例不存在的属性时被调用,已有的属性不会在__getattr__中查找;

__getattr__可以实现完全动态的调用

class Chain(object):
def __init__(self, path=''):
self._path = path

def __getattr__(self, path):
return Chain(f'{self._path}/{path}')

def __call__(self, param):
return Chain(f'{self._path}/{param}')

def __str__(self):
return self._path

__repr__ = __str__


print(Chain().status.user('Aurelius').timeline.list)

__call__

定义了__call__,就可以调用实例本身,__call__可以有参数;

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

def __call__(self, text):
print(f'{self._name}: {text}')

print(Student('Aurelius')('A'))

类本身的调用会执行type__call__方法

枚举类

将一组相关常量定义在一个Class中,并且不可变,成员可以直接比较;

通过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(name, '=>', member, ',', member.value)
...
Jan => Month.Jan , 1
Feb => Month.Feb , 2
Mar => Month.Mar , 3
Apr => Month.Apr , 4
May => Month.May , 5
Jun => Month.Jun , 6
Jul => Month.Jul , 7
Aug => Month.Aug , 8
Sep => Month.Sep , 9
Oct => Month.Oct , 10
Nov => Month.Nov , 11
Dec => Month.Dec , 12

通过继承Enum

from enum import Enum, unique

@unique
class Weekday(Enum):
Sum = 0 # 默认从 1 开始,这里设置 0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6

获取方式

  • Enum['Name']
  • Enum.Name
  • Enum(value)

元类

type

type函数既可以返回一个对象的类型,又可以创建出新的类型;

type创建class

def fn(self, name='world'):
print(f'Hello, {name}')

Hello = type('Hello', (object,), dict(hello=fn))
h = Hello()
h.hello()

type() 的 3 个参数:

  • class 的名称
  • 继承的父类元组
  • class 的方法和其绑定函数的字典

metaclass

metaclass允许创建类或修改类,可以把类看作metaclass创建的实例;

定义metaclass

class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)

__new__()的 4 个参数依次是:

  • 当前准备创建的类对象
  • 类的名称
  • 类继承的父类元组
  • 类的方法字典

定制类

# 在 Python 解释器创建 MyList 时,要通过 ListMetaclass.__new__() 来创建
class MyList(list, metaclass=ListMetaclass):
pass

Python 解释器首先在当前类的定义中查找metaclass,如果没有,就继续在父类查找,知道找到,用来创建当前类,metaclass隐式继承到子类;


PS:感谢每一位志同道合者的阅读,欢迎关注、评论、赞!