Python从入门到进阶(十二)——面向对象编程

1 类和对象的概念

首先需要明确,面向对象编程不是python独有的;
面向对象是一种编程思想;
在面向对象的思想中

万物都是对象

1.1 面向对象的简单理解:

面向对象是把一组数据结构和处理他们的方法组成对象,把具有相同行为的对象归纳成类,通过封装隐藏类的内部细节,通过继承使类得到泛化,通过多态实现基于对象类型的动态分类。

1.2 类是什么

类表示一组(或一类)对象,每个对象都属于特定的类,并被称为该类的实例。
在面向对象编程中,你编写表示现实世界中的事物和情景的类,并基于这些类来创建对象。
编写类时,你定义一大类对象都有的通用行为。基于类创建 对象时,每个对象都自动具备这种通用行为,然后可根据需要赋予每个对象独特的个性。根据类来创建对象被称为 实例化,这让你能够使用类的实例
在面向对象编程中,术语对象大致意味着一系列数据(属性)以及一套访问和操作这些数据的方法;对象由属性和方法组成。属性不过是属于对象的变量,而方法是存储在属性中的函数。
  • python中类的创建
class Person():
    
    def __init__(self):
        pass

    def set_name(self, name):
        self.name = name

    def get_name(self):
        return self.name
    
    def greet(self):
        print("Hello, world! I'm {}.".format(self.name))
Person是类的名称,包含三个方法定义, class 语句创建独立的命名空间,用于在其中定义函数。参数 self 指向对象本身。
方法 init() 是一个特殊的方法,每当创建新实例时,Python都会自动运行它。在这个方法的名称中,开头和末尾各有两个下划线,这是一种约定,旨在避免Python默认方法与普通方法发生名称冲突。
self 很有用,甚至必不可少。如果没有它,所有的方法都无法访问对象本身——要操作的属性所属的对象;
默认情况下,可从外部访问对象的属性。若让属性不能从对象外部访问,可将属性定义为私有。私有而只能通过存取器方法来访问
Python没有为私有属性提供直接的支持,要让方法或属性成为私有的(不能从外部访问),只需让其名称以两个下划线打头即可

1.3 对象是什么

对象就是真实世界中的实体,对象与实体是一一对应的,也就是说现实世界中每一个实体都是一个对象,它是一种具体的概念。对象有以下特点:
对象具有属性和行为。
对象具有变化的状态。
对象具有唯一性。

1.4 对象都是某个类别的实例

对象就是通过类定义的数据结构实例

person=Person()

1.5 类和对象的基本使用

创建学生类, 创建一个学生列表,加入3个学生,循环打印学生信息

创建学生类, 创建一个学生列表,加入3个学生,循环打印学生信息

# -*- coding: UTF-8 -*-
class Student:
    def __init__(self, no, name, age):
        # TODO(You): 请在此初始化成员

    def dump(self):
        # TODO(You): 请在此打印学生信息

if __name__ == '__main__':
    students = []
    for i in range(0, 3):
        s = Student(i, f'somebody_{i}', 20+i)
        students.append(s)

    for s in students:
        print('')
        s.dump()

请选出下列对Student实现不正确的代码。
# A.
class Student:
    def __init__(self, no, name, age):
        self.no = no
        self.name = name
        self.age = age

    def dump(self):
        infos = [
            f"* no:{self.no}",
            f"* name:{self.name}",
            f"* age:{self.age}"
        ]
        for info in infos:
            print(info)
# B.
class Student:
    def __init__(self, no, name, age):
        self.no = no
        self.name = name
        self.age = age

    def dump(self):
        print(f"* no:{self.no}")
        print(f"* name:{self.name}")
        print(f"* age:{self.age}")
# C.
class Student:
    def __init__(self, no, name, age):
        self.no = no
        self.name = name
        self.age = age

    def dump():
        print(f"* no:{self.no}")
        print(f"* name:{self.name}")
        print(f"* age:{self.age}")
# D.
class Student:
    def __init__(self, no, name, age):
        self.fields = {
            'no': no,
            'name': name,
            'age': age
        }

    def dump(self):
        for field_name in self.fields:
            field_value = self.fields[field_name]
            print(f'* {field_name}:{field_value}')
# C ❌

2 类成员

类的成员主要包括:字段,属性,方法
字段:

字段包括:普通字段和静态字段,使用和定义都是不一样,其最本质的区别就是内存中保存的位置不同。

普通字段属于对象
静态字段属于类
class Person():
    type = 'ren' #静态字段
    def __init__(self):
        #普通字段
        self.name = 'mingzi'
        pass

    def set_name(self, name):
        self.name = name

p = Person()
p.type = 'woman'
p.name = 'man'
print(Person.type)
print(p.name)

在以上示例代码中:

type是静态字段,可通过类名之间调用
name是普通字段,需要通过实例对象调用
通常情况下我们都使用普通字段,当一个变量在类的所有对象中共同使用,而且数据共享的时候,我们就可以使用静态字段。

方法包括:普通方法、静态方法和类方法。

普通方法:由对象调用;包含一个self参数;执行普通方法时,自动将调用该方法的对象赋值给self;
类方法:由类调用; 包含一个cls参数;执行类方法时,自动将调用该方法的类复制给cls;使用@classmethod装饰器
静态方法:由类调用;没有默认参数;使用@staticmethod装饰器

代码示例:

class Human:
    count = 0
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    #普通方法
    def setName(name):
       self.name=name
       
    @classmethod
    def add_count(cls):
        cls.count += 1       # 可以通过类名修改类属性
    @classmethod
    def get(cls):
        return cls.count
    
    @staticmethod
    def static_func():      # 静态方法
        print("普通函数")

普通方法中可以直接使用对象的普通字段
类方法中可以直接使用静态字段
静态方法中不能直接使用普通字段和静态字段

属性
Python中的属性就是普通方法的变种,就是将一个方法伪装成一个属性:

  • 属性定义的两种方式:
装饰器:在方法上应用装饰器
静态字段:在类中定义值为property对象的静态字段

参考示例代码:

class A:
    @property
    def func(self):
        print("is A func")
     def get_AAA(self):
        print('get的时候运行我啊')
    def set_AAA(self,value):
        print('set的时候运行我啊')
    def delete_AAA(self):
        print('delete的时候运行我啊')
    AAA = property(get_AAA,set_AAA,delete_AAA) #内置property三个参数与get,set,delete一一对应
# A.
# 使用 @dataclass 可以简化实现
from dataclasses import dataclass

@dataclass
class Student:
    no: int
    name: str
    age: int
# B.
# 普通初始化函数添加成员
class Student:
    def __init__(no, name, age):
        self.no = no
        self.name = name
        self.age = age
# C.
# 使用 @dataclass + slot 可以简化实现并且优化内存占用
from dataclasses import dataclass

@dataclass
class Student:
    __slots__ = ['no', 'name', 'age']
    no: int
    name: str
    age: int
# D.
# 普通初始化函数添加成员
class Student:
    def __init__(self, no, name, age):
        self.no = no
        self.name = name
        self.age = age
# B ❌

3 面向对象三要素

封装:

通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。

继承:

继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。

多态:

多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。

3.1 封装

class Person():
    def __init__(self):
        pass

    def set_name(self, name):
        self.__name = name

    def get_name(self):
        return self.__name
    
    def greet(self):
        print("Hello, world! I'm {}.".format(self.name))

p = Person()
p.set_name('test')
print(p.name)

执行后报错:

print(p.name)
AttributeError: 'Person' object has no attribute 'name'

修改为:

p.get_name()
正确输出

以上代码 将数据__name封装到类中,只能通过set_name和get_name方法操作数据;

3.2 继承

继承是一种创建新类的方式,在python中,新建的类可以继承一个父类(单继承)或多个父类(多继承)

代码示例:

class Person():
    def __init__(self):
        pass

    def set_name(self, name):
        self.name = name

    def get_name(self):
        return self.name
    
    def greet(self):
        print("Hello, world! I'm {}.".format(self.name))

class Boy(Person):

    def run(self):
        print('i can run')
    
    def greet(self):
        print('i am subclass boy')

b =Boy()
b.name = '张三'
b.greet()

3.3 多态

  • 多态性的本质在于不同的类中定义有相同的方法名,这样我们就可以不用考虑而统一用一种方式去使用对象,可以通过在父类引入抽象类的概念来硬性限制子类必须有某些方法名:
  • 多态已封装和继承为前提
    在上面代码基础上新增以下代码:
class Girl(Person):

    def say(self):
        print('i can good girl')
    
    def greet(self):
        print('i am subclass Girl')

Boy,Girl都继承自Person,三个对象都有同样的方法名greet();
Boy和Girl重写了父类的方法,实现不同的输出;

4 创建类

4.1 创建一个类

# -*- coding: UTF-8 -*-
 
from random import choice

class Divergence:

    def __init__(self,name='robot'):
        self.name = name
        pass

    def getChoice(self,name=None):
        if name is not None:
            self.name = name
        self.__lists = ['剪刀','石头','布']
        result = choice(self.__lists)
        print(self.name,':选择了>',result)

定义类时,如果没有手动添加 init() 构造方法,又或者添加的 init() 中仅有一个 self 参数,则创建类对象时的参数可以省略不写。

在上面的代码中,由于构造方法除 self 参数外,还包含 name参数,且设置了默认参数,因此在实例化类对象时,按需传入相应的 name 值(self 参数是特殊参数,不需要手动传值,Python 会自动传给它值)

4.2 类对象的使用

定义的类只有进行实例化,也就是使用该类创建对象之后,才能得到利用。总的来说,实例化后的类对象可以执行以下操作:

访问或修改类对象具有的实例变量,甚至可以添加新的实例变量或者删除已有的实例变量;
调用类对象的方法,包括调用现有的方法,以及给类对象动态添加方法。

下面验收如何使用:

#实例化对象的时候传递name参数
d = Divergence('A')
d.getChoice()

#实例化时不传递参数
d = Divergence()
#调用方法时传递
d.getChoice('A')

4.3 Python类创建

创建点对象,有多种方式创建一个类的实例,我们称一个点的x、y、z都一样的点为对角点。

创建点对象,有多种方式创建一个类的实例,我们称一个点的x、y、z都一样的点为对角点。

# -*- coding: UTF-8 -*-
class Point:
    # TODO(You): 添加代码支持类的创建

if __name__ == '__main__':
    points = []

    # TODO(You): 批量创建1000个对角点,第i个点的坐标是 (i,i,i)

    for point in points:
        print(point)

以下对上述代码补全,不正确的是?
# A.
# 通过 @classmethod 装饰器,增加一个类级别的创建方法,批量创建
class Point:
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

    @classmethod
    def create_diag_points(cls, count):
        # 在@classmethod修饰的方法中,其中 cls 表示 Point
        points = []
        for i in range(count):
            points.append(cls(i,i,i))
        return points

if __name__ == '__main__':
    points = Point.create_diag_points(1000)
# B.
class Point:
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

if __name__ == '__main__':
    points = []
    for i in range(1000):
        points.append(Point(i,i,i))
# C.
class Point:
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

if __name__ == '__main__':
    points = []
    for i in range(1000):
        points.append(new Point(i,i,i))
# D.
# 添加类静态方法,批量创建对角点
class Point:
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

    @staticmethod
    def create_diag_points(count):
        points = []
        for i in range(count):
            points.append(Point(i,i,i))
        return points

if __name__ == '__main__':
    points = Point.create_diag_points(1000)
# C ❌

5 抽象类

5.1 抽象类概念

  • 抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化,需要借助python模块实现;
  • 抽象类是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
  • 抽象类与普通类的不同之处在于:抽象类中有抽象方法,该类不能被实例化,只能被继承,且子类必须实现抽象方法

Python中的abc模块

  • Python中需要利用abc模块实现抽象类
import abc #利用abc模块实现抽象类

class shuiguo(metaclass=abc.ABCMeta):
    
    all_type='sg'

    @abc.abstractmethod #定义抽象方法,无需实现功能
    def name(self):
        pass

    @abc.abstractmethod #定义抽象方法,无需实现功能
    def func(self):
        pass


class Apple(shuiguo): #子类继承抽象类,可是必须定义read和write方法
    def name(self):
        print('我是苹果')

    def func(self):
        print('好吃')

class Pear(shuiguo): #子类继承抽象类,可是必须定义read和write方法
    def name(self):
        print('我是梨子')

    def func(self):
        print('yunfeizhike')


apple =Apple()

pear=Pear()

apple.func()
pear.name()

print(pear.all_type)
print(apple.all_type)
  • 抽象类的本质仍是类,指的是一组类的类似性,包括数据属性(如all_type)和函数属性;
  • 抽象类是一个介于类和接口之间的一个概念,同时具有类和接口的部分特性

6 访问限制

6.1 为什么要做访问限制

做访问限制,是为了程序的健壮性。如果可以从外部对函数里面重要的属性进行任意修改,有可能程序崩溃只是因为一次不经意地参数修改。

  • 通过定义私有属性做访问限制
默认情况下,可从外部访问对象的属性。若让属性不能从对象外部访问,可将属性定义为私有。
私有属性只能通过存取器方法来访问
Python没有为私有属性提供直接的支持,要让方法或属性成为私有的(不能从外部访问),只需让其名称以两个下划线打头即可
class Apple2():
    def name(self,name):
        self.__name=name

    def getName(self):
        return self.__name
  • 通过定义私有函数做访问限制
与属性一样,只需要在函数名前面添加两个下划线打头既可实现私有函数;
class Apple2():
    def name(self,name):
        self.__name=name

    def getName(self):
        return self.__name

    def __setAge(self,age):
        self.__age=age

    def info(self,age):
        self.__setAge(age)

如上所示,提供了一个

getName方法

考虑到外部代码也需要获取类中的额私有变量,通过此方法提供了一个接口,外部代码可以通过此方法获取到私有变量

__name

同理,代码没有提供getAge方法,私有属性__age无法从外部代码获取,若需要从外部获取,需要提供以下方法:

def getAge(self):
	return self.__age

如上代码提供了info方法,用于在外部代码中给私有变量__age赋值,

def info(self,aget):

相比于直接给变量赋值,通过这种方式,可以对参数做检查,避免传入无效的参数,提升了代码的健壮性;

def info(self,age):
	if age < 1:
       raise ValueError('too small')
	elif age > 100:
       raise ValueError('too big')
    else:
      self.__age = age

双下划线开头的实例变量其实也不是一定不能从外部访问呢?

apple = Apple()
apple.info(10)
print(apple.getAge())
print(apple._Apple__age)

运行以上代码,打印的是同样的值 :10

不能直接访问双下划线开头的实例变量是因为Python解释器对外把__变量改成了_类名__变量名的形式;

  • 特殊情况:
在Python中,以双下划线开头,并且以双下划线结尾的变量是特殊变量如__name__,特殊变量是可以直接访问的;
按照约定俗成的规定,以一个下划线开头的实例变量名(例如_category) 外部也是可以直接访问的,但是这个形式的变量表达的意思是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”

7 获取对象信息

7.1 什么是反射机制

在Python中使用反射可以得到对象的所有属性,这个机制被称为反射(反过来让对象告诉我们他是什么),用于实现在运行时获取未知对象的信息.

  • python中有几个内置方法,可以用来检查或是访问对象的属性,这些方法可以用于任意对象:
dir([obj]):

调用这个方法将返回包含obj大多数属性名的列表(会有一些特殊的属性不包含在内)。obj的默认值是当前的模块对象。

hasattr(obj, attr):

这个方法用于检查obj是否有一个名为attr的值的属性,返回一个布尔值。

getattr(obj, attr):

调用这个方法将返回obj中名为attr值的属性的值,

setattr(obj, attr, val):

调用这个方法将给obj的名为attr的值的属性赋值为val。

type(obj)

判断对象类型,使用type()函数
	

7.2 Python 获取对象信息

过滤列表里所有含有 ‘z’ 属性的对象,打印他们的 ‘x’+‘y’+‘z’ 的值

过滤列表里所有含有 'z' 属性的对象,打印他们的 'x'+'y'+'z' 的值

# -*- coding: UTF-8 -*-
class Point2D:
    def __init__(self, x, y) -> None:
        self.x = x
        self.y = y

class Point3D:
    def __init__(self, x, y, z) -> None:
        self.x = x
        self.y = y
        self.z = z

class Vector2D:
    def __init__(self, x, y) -> None:
        self.x = x
        self.y = y

class Vector3D:
    def __init__(self, x, y, z) -> None:
        self.x = x
        self.y = y
        self.z = z

def test():
    points = [
        Point2D(0, 1),
        Point2D(0, 1),
        Point3D(0, 1, 2),
        Point3D(0, 1, 3),
        Vector2D(0, 1),
        Vector3D(0, 1, 4),
    ]

    z_objects = []
    # TODO(You): 请在此过滤出含有'z'属性的对象,在过滤中打印出对应对象的'z'属性

    for p in z_objects:
        print('x+y+z:', p.x+p.y+p.z)

if __name__ == '__main__':
    test()

请选出下列能正确实现这一功能的选项。
# A.
for p in points:
    if not hasattr(p, 'z'):
        z = getattr(p, 'z')
        print('get z attr:', z)
        z_objects.append(p)
# B.
for p in points:
    z = getattr(p, 'z')
    print('get z attr:', z)
    z_objects.append(p)
# C.
for p in points:
    if hasattr(p, 'z'):
        z = getattr(p, 'z')
        print('get z attr:', z)
        z_objects.append(p)
# D.
for p in points:
    if p.hasattr('z'):
        z = p.getattr(p, 'z')
        print('get z attr:', z)
        z_objects.append(p)

# C正确
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值