Python面向对象全面分析

一、面向对象技术简介

Python是一门面向对象的语言,所以在Python中创建一个类和对象是很容易的,接下来简要说明一下面向对象的一些基本特征

类:用于描述具有相同属性和方法的对象的集合,它定义了该集合中每个对象所共有的属性和方法,对象是类的实例。

类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外,类变量通常不作为实例变量使用。

数据成员:类变量或者实例变量,用于处理类及其实例对象的相关数据

方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫做方法的覆盖,也称为方法的重写

局部变量:定义在方法中的变量,只作用于当前实例的类

实例变量:在类的声明中,属性是用变量来表示的,这种变量就被称为实例变量,是在类声明的内部但是在类的其他成员方法之外声明的

继承:即一个派生类,继承基类的字段和方法,继承也允许把一个派生类的对象作为一个基类的对象对待,例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟一个(is a)的关系,例如,Dog是一个Animal

实例化:创建一个类的实例,类的具体对象

方法:类中定义的函数

对象:通过类定义的数据结构实例,对象包括两个数据成员(类变量和实例变量)和方法

简要举个例子,如下所示:

#定义类和类变量
class Employee:#这个employee就是类,可以理解为设计图纸,其中的company和count就是类变量
    company="某某公司"
    count=0
    #变量count用于表示公司实际总共招的人数

#定义”实例变量”和方法(含局部变量)
#__init__叫构造方法,专门用来实例化时自动执行
    def __init__(self,name,salary):
        #下面的self.name和self.salary就是实例变量,
        # 区别就是每个员工的姓名和对应的salary都不一样
        self.name=name
        self.salary=salary
        #公司每招一个新人,总人数就会加一,这里是修改类变量
        Employee.count+=1


    #这里的work就是一个普通的方法,就是类里面定义的函数
    def work(self):
        #下面的hours就是局部变量
        #特点:只在这次work执行时有用,函数一结束,hours就没了
        hours=8
        print(f"{self.name}had worked {hours} hours")
        self._record_work(hours)
        #这里演示了调用内部方法,演示局部变量只在方法内有效

    def _record_work(self,hours):
        print(f"had recorded{self.name}worked {hours}hours")



#“继承”演示
#假设现在要招一个销售经理,他除了是个员工,还有额外的提成
class SaleManager(Employee):
#这里,括号里面写Employee,就表示这个SaleMange就是Employee的子类

    def __init__(self,name,salary,bonus):
        super(SaleManager, self).__init__(name,salary)
        #这里super的作用就是用父类的方法先处理好这两个变量,然后再下面这个是这个子类自己的实例变量
        self.bonus=bonus

    #“方法重写”演示
    def work(self):
        #这个work方法和父类里面的方法同名,这就是方法重写,也就是覆盖
        print(f"{self.name}今天陪客户吃饭,提成是{self.bonus}")


#“实例化”演示
zhangsan=Employee("张三",8000)
lisi=Employee("李四",9000)
#这两个变量都是对象,也就是具体的实例,都拥有自己的实例变量但是他们共享Employee.company这个类变量


#演示“is - a”关系,把子类当父类用
#这里假设我们要创建一个销售经理对象,因为子类是继承于父类的,所以下面的代码成立
wangwu=SaleManager("王五",12000,5000)
print(f"{wangwu.name}属于{wangwu.company}")
#这个就叫做is -a关系,也就是SaleManage is a Employee

#演示“类变量”是共享的
print(f"公司总人数:{Employee.count}")
#在此之前。每实例化一次,count就加一,到代码最后的时候,可以发现,所有对象共享共用这个count,
# 这就是类变量的特性

类就是一张设计图纸(比如Employee),对象就是照这张图纸造出来的具体实物(比如zhangsan),造实物这个过程叫实例化。图纸上所有实物共有的特征叫类变量(比如公司名company),每个实物独有的特征叫实例变量(比如名字name和工资salary),图纸上定义的功能函数叫方法(比如work()),方法里临时用一下用完就丢的变量叫局部变量(比如hours),这些变量和方法统称为数据成员。你还可以拿一张旧图纸去改造成新图纸,这叫继承(比如SalesManager继承Employee),如果新图纸对旧图纸上的某个功能不满意,可以重新写一遍覆盖掉,这叫方法重写(比如重写work()改成"陪客户")。因为新图纸造出来的东西本质上还是旧图纸那个类型,所以可以说"销售经理是一个员工",这就是is-a关系。

二、Python内置类属性

__dict__:类的属性,包含一个字典,由类的数据属性构成

__doc__类的文档字符串

__name__类名

__module__:类定义所在的模块,类的全名是__main__.className,如果类位于一个导入模块mymod中,那么className.__module__等于mynod

__bases__:类的所有父类构成元素(包含了一个由所有父类组成的元组)

三、Python对象销毁

Python中使用了引用计数这一简单技术来跟踪和回收垃圾,在Python内部记录着所有使用中的对象各有多少引用,一个内部跟踪变量,称为一个引用计数器,当对象被创建时,就创建了一个引用计数器,当这个对象不再需要时,也就是说,这个对象的引用计数变为0的时候,它被垃圾回收,但是回收不是立即回收,而是由解释器在适当的时机,将垃圾对象占用的内存空间回收。

a = 40      # 创建对象  <40>
b = a       # 增加引用, <40> 的计数
c = [b]     # 增加引用.  <40> 的计数

del a       # 减少引用 <40> 的计数
b = 100     # 减少引用 <40> 的计数
c[0] = -1   # 减少引用 <40> 的计数

垃圾回收机制不仅针对引用计数为0的对象,同样也可以处理循环引用的情况,循环引用指的是,两个对象相互引用,但是没有其他变量引用他们,在这种情况下,仅使用引用计数器是不够的,Python的垃圾收集器实际上是一个引用计数器和一个循环垃圾收集器,作为引用计数器的补充,垃圾收集器也会留心被分配的总量很大(也就是没有通过引用计数销毁的那些)的对象,在这种情况下,解释器会暂停下来,试图清理所有未引用的循环。

class Point:
    def __init__(self,x=0,y=0):
        self.x=x
        self.y=y
    def __del__(self):
        class_name=self.__class__.__name__
        print(class_name,"销毁")

pt1=Point()
pt2=pt1
pt3=pt1
print(id(pt1),id(pt2),id(pt3))
del pt1
del pt2
del pt3

程序被运行结束之后,Python会统一进行清理所有对象,但是不会保证调用__del__方法

或者更准确的说,程序引用计数器时虽然归零了,但是Python解释器关闭时不一定执行每个对象的__del__

但是在上面的例子中,如果只删除前两行,而不删除第三行的话,输出之后三个id,没有Point 销毁,那么简要举个例子来说明一下:
pt1、pt2、pt3是三个小朋友的手,都抓着同一个气球

del pt1 = 一个小朋友松手了,但还有两个抓着,气球不会飞走

del pt2 = 第二个也松手了,还剩一个抓着

del pt3 = 最后一个也松手了,没人抓了,气球飞走(销毁),飞走前喊一声"我走了"(__del__输出)

总的来说,这段代码的作用就是让人直观的看到当一个对象没人引用的时候,Python会处理它的演示过程

四、类的继承过程中的注意事项

面向对象的编程带来的好处之一就是代码的重用,实现这种重用的方法之一就是通过继承机制,通过继承创建的新类称为子类或者派生类,被继承的就叫基类、父类或者超类,在Python中继承中的一些特点是:

1.如果在子类中需要父类的构造方法,就需要显式的调用父类的构造方法,或者不重写父类的构造方法

2.在调用基类的方法时,需要加上基类的类名前缀,且需要带上self参数变量,区别在于类中调用普通函数时并不需要带上self函数

3.Python总是首先查找到对应类型的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找,

如果在继承元组中列了一个以上的类,那么它就被称作“多重继承”

## 前提:我们用这个父类做演示
class Animal:
    def __init__(self, name):
        print(f"Animal的构造方法,名字是{name}")
        self.name = name

    def eat(self):
        print(f"{self.name}在吃东西")



## 特点1:子类要不要调用父类的构造方法?子类造对象时,如果父类有,"初始化步骤",
# 子类要么主动喊一声老爸帮忙处理,要么干脆不写自己的初始化让老爸全权处理。

# 情况A:子类不写,`__init__`,老爸自动帮忙 **

class Dog(Animal):  # 子类没写 __init__
    pass

dog = Dog("旺财")  # 自动调用父类的 __init__
# 输出:Animal的构造方法,名字是旺财

#情况B:子类写了`__init__`,必须手动喊老爸 **

class Cat(Animal):
    def __init__(self, name, color):
        super().__init__(name)  # ← 这行就是"显式调用父类构造方法",必须有
        self.color = color


cat = Cat("咪咪", "白色")
# 输出:Animal的构造方法,名字是咪咪  (父类的构造方法被调用了)


#如果子类写了`__init__`,但不调父类,老爸的初始化就不执行: 
class Fish(Animal):
    def __init__(self, name):
        self.name = name  # 没调 super().__init__()
        # 老爸的__init__根本没执行,虽然看起来也能用,但破坏了继承规范

## 特点2:调用父类方法时,为什么要加类名前缀和self?

#子类想用老爸的方法时,有两种写法。如果 ** 重写了 ** 这个方法还想用老爸的版本,
# 就得"点名"说我要用`Animal`里的那个。 正常情况(没重写):直接调用,自动去父类找 
class Dog(Animal):
    pass
dog = Dog("旺财")
dog.eat()  # 子类没有eat,自动去父类找
# 输出:旺财在吃东西

#重写后还想用老爸的版本:必须点名 + 传self

class Dog(Animal):
    def eat(self):
        # 我想先吃老爸的饭,再加点自己的
        Animal.eat(self)  # ← 加类名前缀,显式传入self
        print(f"{self.name}还啃了根骨头")


dog = Dog("旺财")
dog.eat()
# 输出:
# 旺财在吃东西      (老爸的)
# 旺财还啃了根骨头   (自己的)
#对比:类里面调用普通函数不需要self,但调用父类方法必须带self **,

def 普通函数():
    print("我是普通函数")
class Test:
    def method(self):
        普通函数()  # ✅ 直接调,不需要self
        Animal.eat(self)  # ✅ 调父类方法,必须带self

## 特点3:Python找方法的顺序(查找顺序)

#人话版: ** 调用方法时,Python的优先级是 ** 自己(子类)>老爸(父类)>爷爷(父类的父类) ** 。


class Animal:
    def say(self):
        print("动物叫")

class Dog(Animal):
    def say(self):
        print("汪汪汪")  # 子类重写了

class Husky(Dog):
    pass  # 二哈啥也没写

h = Husky()
h.say()  # Python先找Husky → 没有 → 找Dog → 找到了"汪汪汪"
# 输出:汪汪汪
#如果Dog也没写,就继续往上找Animal的,"动物叫"。 

## 额外补充:多重继承(继承元组里有多个类)

# 一个儿子可以有多个老爸,写的时候用逗号隔开。

class Flyable:
    def fly(self):
        print("飞起来了")

class Swimmable:
    def swim(self):
        print("游起来了")

class Duck(Flyable, Swimmable):  # 多重继承,同时继承两个类
    pass

duck = Duck()
duck.fly()  # 输出:飞起来了
duck.swim()  # 输出:游起来了

五、基础重载方法

序号

方法, 描述 & 简单的调用

1

__init__ ( self [,args...] )构造函数
简单的调用方法: obj = className(args)

2

__del__( self )析构方法, 删除一个对象
简单的调用方法 : del obj

3

__repr__( self )转化为供解释器读取的形式
简单的调用方法 : repr(obj)

4

__str__( self )用于将值转化为适于人阅读的形式
简单的调用方法 : str(obj)

5

__cmp__ ( self, x )对象比较
简单的调用方法 : cmp(obj, x)

六、类属性与方法

(1)类的私有属性

__private_attrs:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时 self.__private_attrs。

(2)类的方法

在类的内部,使用 def 关键字可以为类定义一个方法,与一般函数定义不同,类方法必须包含参数 self,且为第一个参数

(3)类的私有方法

__private_method:两个下划线开头,声明该方法为私有方法,不能在类的外部调用。在类的内部调用 self.__private_methods

class JustCounter:
    __secertCount=0#声明这是一个私有变量类
    publicCounter=0#这是一个公共类变量

    def count(self):
        self.__secertCount+=1#每调用一次后加1
        self.publicCounter+=1#每调用一次后+1
        print(self.__secertCount)#打印当前值


counter=JustCounter()#创建一个JusuCounter类的实例对象,赋值给变量counter
#此时对象内部状态:counter.__secretCount = 0(实例私有变量,初始为0),
#counter.publicCounter = 0(实例公共变量,初始为0)
#JustCounter.__secretCount = 0(类私有变量,未被修改)
#JustCounter.publicCounter = 0(类公共变量,未被修改)
counter.count()
counter.count()
print(counter.publicCounter)
#创建一个计数器对象,然后调用两次计数方法,
# 每次让计数器+1并打印当前值,所以输出1和2。 相当于"买了个计步器,走了两步,看了两眼屏幕"
#print(counter.__secertCount)

七、注意事项

写法

名称

访问权限

举例

__foo__

双下划线开头结尾

特殊方法,Python内部定义,你别乱改

__init__, __str__

, __len__

_foo

单下划线开头

受保护(protected),建议只在类内部和子类用,但Python不强制

_name, _age

__foo

双下划线开头

私有(private),名字被改成_类名__foo

,外部不能直接访问

__secretCount

如果没有名称修饰,子类一不小心就会覆盖父类的私有变量,造成bug。有了名称修饰,父子类的私有变量各自独立。

class Parent:
    def __init__(self):
        self.__secret = "老爸的秘密"

class Child(Parent):
    def __init__(self):
        super().__init__()
        self.__secret = "儿子的秘密"   # 这个和老爸的不是同一个变量!

child = Child()
# 老爸的 __secret → _Parent__secret
# 儿子的 __secret → _Child__secret
# 两个互不干扰!

同时用代码验证“改名过程”

class Runoob:
    __site = "www.runoob.com"   # 你写的时候叫 __site

runoob = Runoob()

# 用你写的名字访问 → 报错(因为Python已经改名了)
print(runoob.__site)   # ❌ AttributeError

# 用Python改完后的名字访问 → 成功
print(runoob._Runoob__site)   # ✅ 输出:www.runoob.com

# 更直观:查看对象到底有哪些属性
print(dir(runoob))   # 输出:['_Runoob__site', ...] 看到没,
实际名字带 _Runoob

核心目的还是为了解决子类意外覆盖父类的私有变量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值