python零基础10:面向对象编程

面向过程编程

  • 以过程或函数为核心,程序由一系列函数组成,每个函数完成一个特定的任务.
  • 数据以变量的形式存在,而函数则对这些变量进行操作。数据和操作数据的函数相互分离。

面向对象编程

  • 以对象为核心,程序由一系列对象组成,每个对象包含数据和操作这些数据的方法。
  • 将数据和操作数据的方法封装在对象中,对象对外部隐藏其内部实现细节,只通过公共接口与外界交互。
  • 难点:难于编写,但代码复用性高;封装性好,效率低

两种范式比较

面向过程适合简单任务,面向对象适合复杂系统,前者关注步骤,后者关注对象和交互。

函数和类的区别 例子

  • 函数(Function) - 做事的工具
# 函数:完成特定任务的工具
# 函数 = 独立的工具
def 锤子(钉子):
    return f"敲好了{钉子}"

def 螺丝刀(螺丝):
    return f"拧紧了{螺丝}"

# 直接使用工具
锤子("长钉子")      # 完成任务
螺丝刀("十字螺丝")   # 完成任务
  • 类 事物的蓝图
# 类:描述一种事物的模板
class 木匠:
    def __init__(self, 名字, 经验):
        self.名字 = 名字
        self.经验 = 经验
        self.工具 = ["锤子", "锯子"]
    
    def 做椅子(self):
        return f"{self.名字}{self.工具[1]}做了把椅子"
    
    def 修桌子(self):
        return f"{self.名字}{self.工具[0]}修好了桌子"

# 需要先"雇佣"木匠,再让他工作
王木匠 = 木匠("老王", "10年")
结果 = 王木匠.做椅子()  # 老王用锯子做了把椅子
# 王木匠这里就是一个对象(Object),也叫实例(Instance)。

类和对象

  • 类(class):对象的模板。类是对多个客观存在物体的抽象。
  • 类封装了数据和操作数据的方法。
    • 数据:类内变量,类的属性
    • 操作:类内函数,类的方法
  • •对象(object):类的实例(instance)。具体的对象则是类的实例。对象是类的具体化,拥有类所定义的属性和方法。
  • 例子:Cat 类抽象了各种猫的属性和方法,用于生成各个具体猫的对象(Kitty, Garfield, Doraemon)

内置类和对象的使用

字符串、列表、元组等都是Python内置数据类型(class),它们的对象(object),即可用来保存数据(data),又具有处理数据的方法(method)。例子:

fruits = ["apple", "banana","cherry"]
# 这里fruits列表保存了三个字符串数据。列表作为一个"容器",存储了具体的数据。
fruits.append("orange")
# 这里append()是列表的方法,它处理(修改)了列表中的数据。
print(fruits)  

模块中类和对象的使用

使用import 指令导入已定义好的模块,进而通过类名后跟圆括号的方式,来实例化一个类的对象。

# 导入turtle 模块
import turtle
# 生成turtle 对象变量
bob = turtle.Turtle()
# 调用对象的方法画图
# 前进100像素
bob.fd(100)
# 归还鼠标控制权
turtle.mainloop()

Python 名字空间(Namespace) 管理

  • 内置名字空间:内置名字空间在Python解释器启动时就已经存在,存储了内置的变量、函数和类,作用域是整个程序范围
  • 函数名字空间: 函数名字空间存储了函数内部的变量、参数等,作用域仅限于函数内部
  • 类名字空间:类名字空间中存储了类中的变量和方法,作用域仅限于类内部,外部访问需通过类或对象名加点号的形式。(比如:类变量、类方法、静态方法和实例方法)
    • 类名字空间的生命周期从类定义(从class开始)时开始,直到类对象被销毁。
  • 模块名字空间:每个模块都有自己的名字空间,存储了模块中的变量、函数和类,作用域仅限于模块内部,外部访问需使用模块名加点号的形式。

类方法使用

类和对象的定义

定义对象来保存数据: 使用关键字class来定义类,然后使用类名()的形式生成类对象。一个最简单的类,可以不定义任何属性和方法,然后在使用中通过点号操作给对象添加新属性

import math
def print_point(p):
	print('(%g,%g)'%(p.x,p.y))

class Point:
    """Represents a point in 2-D space."""

blank = Point()
blank.x = 3
blank.y = 4
print_point(blank)
distance = math.sqrt(blank.x**2+blank.y**2)
print(distance) 

定义对象来保存数据

类对象的属性在被赋值后是可变的,且和内建的各种数据类型一样,用户定义的类型可以作为函数的参数和返回值。

# 类的定义
class Rectangle:
    """Representsarectangle.attributes:width,
    height,corner."""
# 对象作为函数参数
def grow_Rectangle(rect,dwidth,dheight):
    rect.width += dwidth
    rect.height += dheight
# 类的实例化
box = Rectangle()
# 给对象box 添加新属性
box.width = 100
box.height = 200
box.corner = Point()
box.corner.x = 0
box.corner.y = 0

# 对象作为函数返回值
def find_center(rect):
    p = Point() # 之前定义的类
    p.x = box.corner.x + rect.width/2
    p.y = box.corner.y + rect.height/2
    return p

p = find_center(box)
print_point(p)

复制对象

可变对象(如列表、字典,类对象等)的赋值会引发新对象和原始对象共享内容。需要时,可使用copy标准库来复制对象,避免这种情况。

  • 浅复制:浅复制只复制对象的顶层内容。
  • 深复制:深复制递归地复制对象内部的所有嵌套对象。
import copy
box2 = copy.copy(box)
print(box2 == box)
print(box2 is box)
print(box2.corner is box.corner)

box3 = copy.deepcopy(box)
print(box3 == box)
print(box3 is box)
print(box3.corner is box.corner)

浅复制 (copy.copy):
box (原始) box2 (浅复制副本)
├── width: 100 ├── width: 100 (副本)
├── height: 200 ├── height: 200 (副本)
└── corner ──────┐ └── corner ───┘
├── x: 0 │ ├── x: 0 (同一个对象!)
└── y: 0 │ └── y: 0 (同一个对象!)

└────── 指向同一个Point对象!

深复制 (copy.deepcopy):
box (原始) box3 (深复制副本)
├── width: 100 ├── width: 100 (副本)
├── height: 200 ├── height: 200 (副本)
└── corner └── corner (全新的Point对象!)
├── x: 0 ├── x: 0 (副本)
└── y: 0 └── y: 0 (副本)

  1. 浅复制 (copy.copy)
    创建新的Rectangle对象
    但内部的corner属性仍然指向同一个Point对象
    修改box2.width不会影响box.width(因为数字是基本类型)
    但修改box2.corner.x会影响box.corner.x(因为指向同一个对象)
# 验证浅复制的问题
box2.corner.x = 999
print(box.corner.x)  # 输出: 999!原始对象也被修改了!
  1. 深复制 (copy.deepcopy)
    创建全新的Rectangle对象
    同时也创建全新的Point对象
    两个对象完全独立,互不影响
# 验证深复制的独立性
box3.corner.x = 888
print(box.corner.x)  # 输出: 999(原始对象不受影响)

定义处理对象的函数

为对象定义方法

那些定义用来处理特定类对象的函数,可以放入类内作为对象方法:在类内部使用def关键字定义函数,第一个参数通常是self,表示类的实例。
对象方法可以通过对象.方法名(参数)来调用,对象将会隐式作为第一个实参传入,不再需要显式传入。

#%% 
class Time:
    # 第一个参数通常是self,表示类的实例,self代表对象本身
    def print_time(self):
        print('%.2d:%.2d:%.2d'% (self.hour,self.minute,self.second))

# 当作普通函数
Time.print_time(start)
# 通过对象调用
start = Time()
start.hour = 9
start.minute = 45
start.second = 0
start.print_time()

为对象添加初始化方法

# %%
def int_to_time(seconds):
    time = Time()
    minutes,time.second = divmod(seconds,60)
    # divmod同时执行除法和取模运算,返回一个包含商和余数的元组
    time.hour,time.minute = divmod(minutes,60)
    return time

def add_time(t1,t2):
    seconds = time_to_int(t1)+time_to_int(t2)
    return int_to_time(seconds)

class Time:
    """Represents the time of day. """
    # 定义类的初始化方法
    def __init__(self,hour=0,minute=0,second=0):
        self.hour = hour
        self.minute = minute
        self.second = second
    
    def time_to_int(self):
        minutes = self.hour * 60 + self.minute
        seconds = minutes * 60 + self.second
        return seconds
    
    # def print_time(self):
        # print('%.2d:%.2d:%.2d'% (self.hour,self.minute,self.second))
        # 为对象添加打印方法
    def __str__(self):
        return '%.2d:%.2d:%.2d'% (self.hour,self.minute,self.second)

    def increment(self,seconds):
        seconds += self.time_to_int()
        return int_to_time(seconds)
    
    # def increment(time,seconds):
    #     time.second += seconds
    #     if time.second >= 60:
    #         time.second -= 60
    #         time.minute += 1
    #     if time.minute >= 60:
    #         time.minute -= 60
    #         time.hour += 1
    
    def is_after(self,other):
        return self.time_to_int() > other.time_to_int()

start = Time(9,45,0)
# start.print_time()
print(start)
end = start.increment(1337)
# end.print_time()
print(end) 
end.is_after(start)

为对象添加运算符方法

我们可以为自己定义的类定义各种运算符,包括算术运算符和关系运算符等,称为重载。每个运算符对应一个预先定义好的函数名,比如加号+运算符,对应的方法是__add__方法。

class Time:
    """Represents the time of day. """
    # 定义类的初始化方法
    def __init__(self,hour=0,minute=0,second=0):
        self.hour = hour
        self.minute = minute
        self.second = second
    
    def time_to_int(self):
        minutes = self.hour * 60 + self.minute
        seconds = minutes * 60 + self.second
        return seconds
    
    def __str__(self):
        return '%.2d:%.2d:%.2d'% (self.hour,self.minute,self.second)

    def increment(self,seconds):
        seconds += self.time_to_int()
        return int_to_time(seconds)
    
    def add_time(self,other):
        seconds = self.time_to_int()+other.time_to_int()
        return int_to_time(seconds)

    def __add__(self,other):
        if isinstance(other,Time):
            return self.add_time(other)
        else:
            return self.increment(other)

start = Time(9,45)
duration = Time(1,35)
'''
这里有两个知识点:
1. __add__ 为类定义运算符,包括算术和关系运算符,
称为	运算符重载,这里调用的时候是自动调用;如果是add_time
就需要显式调用,也就是把add_time写出来
优点: 接近现实,很自然
2. 前者针对对象,调用内置print;后者是Python内置的print函数
'''
print(start+duration)
print("aaaaa")

Python内部维护了一个运算符到方法名的映射表:

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __add__(self, other):        # + 加法
        return Vector(self.x + other.x, self.y + other.y)
    
    def __sub__(self, other):        # - 减法
        return Vector(self.x - other.x, self.y - other.y)
    
    def __mul__(self, scalar):        # * 乘法
        return Vector(self.x * scalar, self.y * scalar)
    
    def __eq__(self, other):         # == 相等比较
        return self.x == other.x and self.y == other.y
    
    def __str__(self):               # str() 字符串表示
        return f"Vector({self.x}, {self.y})"
    
    def __len__(self):               # len() 长度
        return int((self.x**2 + self.y**2)**0.5)

# 使用
v1 = Vector(3, 4)
v2 = Vector(1, 2)

print(v1 + v2)   # Vector(4, 6) - 调用__add__
print(v1 * 2)    # Vector(6, 8) - 调用__mul__
print(v1 == v2)  # False - 调用__eq__
print(len(v1))   # 5 - 调用__len__

类变量vs.实例变量

  • 类变量: 与类相关联的变量,所有类实例对象共享,可以通过类名或实例对象访问和修改
  • 实例变量: 与类实例相关联的变量,每个实例都有自己的独立副本,通过类实例对象访问
class Student:
	count = 0
stu1 = Student()
# 基于对象访问类变量,是独立的,不影响除了该实例以外的变量
stu1.count = 1
print(stu1.count) # 1
print(Student.count) # 0
#基于类名访问类变量 会改变
Student.count = 3
print(Student.count) # 3
stu2 = Student()
print(stu2.count) # 3

类方法 vs.实例方法 vs. 静态方法

  • 实例方法: 直接定义,接收self参数,需要实例化对象后调用,可以访问实例变量和类变量。
  • 类方法: 通过@classmethod 定义,接收cls参数,直接改变类变量,不能直接访问实例变量。
  • 静态方法: 通过@staticmethod 定义,不接收self 或cls参数,与类和实例无关,只是存在于类中的普通函数。
# %%
class Car:
    manufacturer = "Generic" # 类变量
    
    def __init__(self,model,year):
        self.model = model
        self.year = year
    def display_info(self):
        print(f"{self.year} {self.model} by {Car.manufacturer}")
    @classmethod
    def set_manufacturer(cls,manufacturer):
        cls.manufacturer = manufacturer
    # 不是类方法 可以指修改实例
    # def set_manufacturer(self,manufacturer):
      #  self.manufacturer  = manufacturer
    @staticmethod
    def is_electric(car_model):
        return car_model.endswith('Electric')

my_car = Car("Model S Electric",2020)
print(my_car.manufacturer) # Generic

Car.set_manufacturer("Tesla") 
print(Car.manufacturer) # "Tesla"
# 类方法对实例居然也会修改
print(my_car.manufacturer) # "Tesla"
# my_car.set_manufacturer("Tesla") 
# 可以只修改实例


my_car.display_info() #2020 Model S Electric by Tesla

another_car = Car("Cybertruck Electric",2021)

print(Car.is_electric(another_car.model)) # True
print(Car.is_electric(my_car.model)) # True

类方法的修改对实例也会修改吗?
答案:取决于实例是否已经创建了自己的属性!
实例创建后,只要没有自己的属性,就会自动跟随类变量的更新
- 如果实例没有自己的属性:会受影响(使用类属性)
- 如果实例有自己的属性:不受影响(使用自己的属性)
解释: 当实例没有自己的manufacturer属性时,Python会动态查找类变量。所以即使实例创建在先,类变量修改在后,实例访问时也会得到最新值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值