Python之面向对象和类
一.类
1.类的定义:
class 类名:
“”“注释 ”“”
pass
2.实例的创建:
实例 = 类名(parameterlist)
parameterlist:定义类时__init__()方法的参数,如果该方法只有一个self参数,parameterlist可以省略
class Goose():"""定义了鹅类"""passgoose = Goose()
print(goose)#<__main__.Goose object at 0x00000229213C6900>
3.创建类的构造函数方法:__init__()方法
定义类时,通常会包含一个__init__()方法。该方法是构造函数方法,每当创建一个类的新实例时,Python都会自动执行它。
1.__init__()方法用来初始化新创建对象,在一个对象被创建以后会立即调用。
2. __init__()方法必须包含一个self参数,并且必须是第一个参数。self参数是一个指向实例本身的引用,用于访问类中的属性和方法。(相当于JAVA中的this)
3.当__init__()方法只有一个参数时,在创建类的实例时,不需要指定实际参数。
4.__init__()方法的名称是以两个连续下划线开头和结尾,这是一种约定,用于区分Python默认方法和普通方法。
5._init_()方法可以分为无参构造方法和有参构造方法。当使用无参构造方法创建对象时,所有对象的属性都有相同的初始值;当使用有参构造为法创建对象时,对象的属性可以有不同的初始值;
6.如果你写多个构造方法,但是你在调用的时候,后写的构造方法会取代前面的构造方法(无论这两者参数上是否有区别),这点与JAVA不一样,所以一般一个类只有一个构造方法。
7.一般来说,构造方法里面写初始化内容
class Goose():def __init__(self):print ("self:",self) #self: <__main__.Goose object at 0x0000022CD8766900>print("type of self:",type(self))#type of self: <class '__main__.Goose'>print("我是鹅类,我会自动执行该语句")
g=Goose()
在__init__()方法中,除了self参数外,还可以自定义一些参数,参数间使用逗号“,”进行分隔。
class Goose:'''鹅类'''def __init__(self,beak,wing,claw):print("我是鹅类!我有以下特征:")print(beak)print(wing)print(claw)
beak_1 = "喙的基部较高,长度和头部的长度几乎相等"
wing_1 = "翅膀长而尖"
claw_1 = "爪子是蹼壮的"
wildGoose = Goose(beak_1,wing_1,claw_1)
当然,也可以不用构造函数(一般不推荐这样)
class Rect1():def getPeri(self,a,b):print("周长为:",(a+b)*2)def getArea(self,a,b):print("面积为:",a*b)
r1=Rect1()
r1.getPeri(3,4)
r1.getArea(3,4)
print(r1.__dict__)class Rect2():def __init__(self,a,b):self.a=aself.b=bdef getPeri(self):print("周长为:",(self.a+self.b)*2)def getArea(self):print("面积为:",self.a*self.b)
r2=Rect2(5,6)
r2.getPeri()
r2.getArea()
print(r2.__dict__)
二.类的成员
1.类的成员,静态定义
定义属性并访问
数据成员是指在类中定义的变量,即属性,根据定义位置,又可以分为类属性和实例属性。
① 类属性
类属性是指定义在类中,并且在函数体外的属性。
类属性可以在类的所有实例之间共享值,也就是在所有实例化的对象中公用。
类属性可以通过类名称或者实例名访问。
class Goose:'''鹅类'''neck = "脖子较长"wing = "振翅频率高"leg = "腿位于身体的中心支点,行走自如"def __init__(self):print("我属于鹅类!我有以下特征:")print(self.neck)print(self.wing)print(self.leg)
geese = Goose()
2.动态添加属性
在Python中除了可以通过类名称访问类属性,还可以动态地为类和对象添加属性
class Goose:'''鹅类'''neck = "脖子较长"wing = "振翅频率高"leg = "腿位于身体的中心支点,行走自如"def __init__(self):print("我属于鹅类!我有以下特征:")print(Goose.neck)print(Goose.wing)print(Goose.leg)geese = Goose()
#在类外,动态的为对象添加属性
geese.beak = "喙的基部较高,长度和头部的长度几乎相等"
print("鹅的喙:",geese.beak)
3.实例属性
实例属性,在JAVA中类似于对象的属性(官方不是这个叫法),实例属性是指在类的方法中的属性,就是__init__()构造方法中的参数,只作用于当前实例中。
class Goose:'''鹅类'''neck = "脖子较长"wing = "振翅频率高"leg = "腿位于身体的中心支点,行走自如"def __init__(self,name):self.name = nameprint("我属于鹅类!我有以下特征:")print(self.neck)print(self.wing)print(self.leg)print("实例属性,geese特有:",name)geese = Goose("娃哈哈")
4.实例属性和类属性的区别
class Dog:# 类属性(所有狗共享)species = "Canis familiaris"def __init__(self, name):# 实例属性(每个狗独立)self.name = name# 创建两个实例
dog1 = Dog("Buddy")
dog2 = Dog("Max")# 访问类属性(通过类名或实例名)
print(Dog.species) # 输出: Canis familiaris
print(dog1.species) # 输出: Canis familiaris# 访问实例属性(只能通过实例名)
print(dog1.name) # 输出: Buddy
print(dog2.name) # 输出: Max# 尝试通过类名访问实例属性(报错!)
#print(Dog.name) # AttributeError: type object 'Dog' has no attribute 'name'# 修改类属性
Dog.species = "Canis lupus"
print(dog1.species) # 输出: Canis lupus(所有实例同步更新)
print(dog2.species) # 输出: Canis lupus# 通过实例修改"类属性"(实际是创建同名实例属性)
dog1.species = "Golden Retriever"
print(dog1.species) # 输出: Golden Retriever(仅影响dog1)
print(dog2.species) # 输出: Canis lupus(dog2不受影响)# 类属性本身未被修改
print(Dog.species) # 输出: Canis lupus
总结如下:
特性 | 类属性 | 实例属性 |
---|---|---|
定义位置 | 类内部,__init__ 方法之外 | 通常在 __init__ 方法内 |
归属 | 属于类 | 属于实例 |
访问方式 | 类名或实例名均可访问 | 只能通过实例名访问 |
共享性 | 所有实例共享 | 每个实例独立 |
修改影响 | 修改后影响所有实例 | 修改仅影响当前实例 |
5.访问限制
为了保证类内部的某些属性或方法不被外部所访问,可以在属性或方法名前面添加单下划线(_foo)、双下划线(__foo)或首尾加双下划线(__foo__),从而限制访问权限。
其中,单下划线、双下划线、首尾双下划线的作用如下:
首尾双下划线表示定义特殊方法,一般是系统定义名字,如__init__(),一般自定义不推荐这种定义
以单下划线开头的表示protected(保护)类型的成员,只允许类本身和子类进行访问,但
不能使用“from module import *”语句导入
双下划线表示private(私有)类型的成员,只允许定义该方法的类本身进行访问,而且也
不能通过类的实例进行访问,但是可以通过“类的实例名._类名_xxx”方式访问。
class MyClass:def __init__(self):self.public_value = "Public" # 公开属性,可任意访问self._protected_value = "Protected" # 保护属性(单下划线开头)self.__private_value = "Private" # 私有属性(双下划线开头)def public_method(self):print("This is a public method.")def _protected_method(self):print("This is a protected method.")def __private_method(self):print("This is a private method.")def __special_method__(self):print("This is a special method (magic method).")class SubClass(MyClass):"""继承MyClass类"""def show_protected(self):print("接受保护属性 :", self._protected_value)def try_private(self):# 直接访问私有属性会报错# print(self.__private_var) # 报错:AttributeError# 但可以通过 _类名__私有属性 的方式访问print("Accessing private from subclass:", self._MyClass__private_var)# 实例化
obj = MyClass()
sub_obj = SubClass()# 1. 公开成员(无下划线)
print(obj.public_value) # 输出: Public
obj.public_method() # 输出: This is a public method.# 2. 保护成员(单下划线开头)
print(obj._protected_value) # 输出: Protected(可以访问但不推荐)
obj._protected_method() # 输出: This is a protected method.
sub_obj.show_protected() # 输出: Accessing protected from subclass: Protected# 3. 私有成员(双下划线开头)
# print(obj.__private_var) # 报错:AttributeError
# obj.__private_method() # 报错:AttributeError
print(obj._MyClass__private_var) # 输出: Private(通过名称重整访问)
obj._MyClass__private_method() # 输出: This is a private method.
sub_obj.try_private() # 输出: Accessing private from subclass: Private# 4. 特殊方法(首尾双下划线)
obj.__special_method__() # 输出: This is a special method (magic method).
三.类的方法
1.实例方法
实例方法是指在类中定义的函数。该函数是一种在类的实例上操作的函数。同__init__()方法一样,实例方法的第一个参数必须是self,并且必须包含一个self参数.
语法:
def 方法名(self,参数1,参数2....):
方法内容
调用形式:
只能通过类名.方法名(参数)
class Goose:def __init__(self,beak,wing,claw):print("我是鹅类!我有以下特征:")print(beak)print(wing)print(claw)def fly(self,state):print(state)
'''*******************调用方法**********************'''
beak_1 = "喙的基部较高,长度和头部的长度几乎相等"
wing_1 = "翅膀长而尖"
claw_1 = "爪子是蹼壮的"
wildGoose = Goose(beak_1,wing_1,claw_1)
wildGoose.fly("我飞行的时候,一会排成个人字,一会排成个一字")
2.类方法:
在该方法前面必须加上@classmethod(注解,装饰器),来表明这是一个类方法,且必须带一个参数cls(跟类方法的self一样,但是为了区分两者,这里要写作cls)
调用方式:
推荐 类名.方法名调用,
不推荐 对象名.方法名
作用:
用于操作类属性(而不是实例属性)
常用于工厂方法(创建类的不同实例)。
class Dog:"""species为一个类属性@classmethod后的类方法一般为了操作类属型(调用修改等等)"""species = "Canine" # 类属性def __init__(self, name):self.name = name@classmethoddef get_species(cls):return cls.species # 访问类属性@classmethoddef from_birth_year(cls, name, birth_year):age = 2025 - birth_yearreturn cls(name) # 返回一个新的 Dog 实例,并将name传给新的实例(毕竟建造一个实例需要参数)# 通过类名调用类方法
print(Dog.get_species()) # 输出: Canine# 类方法作为工厂方法创建实例
dog = Dog.from_birth_year("Max", 2018)
print(dog.name) # 输出: Max
注意:return cls(name)意味着
# 返回一个新的 Dog 实例,并将name传给新的实例(毕竟建造一个实例需要参数)
3..静态方法
在该方法前面必须加上@staticmethod(注解),来表明这是一个静态方法,可以无参数,
调用方式:
推荐 类名.方法名调用,
不推荐 对象名.方法名
作用:
与类相关,但不依赖类或实例的状态(即不访问
self
或cls
)。类似于普通函数,但逻辑上属于类。
class MathUtils:@staticmethoddef add(a, b):return a + b@staticmethoddef multiply(a, b):return a * b# 通过类名调用静态方法
print(MathUtils.add(3, 7)) # 输出: 10# 也可以实例化后调用(但不推荐,静态方法不依赖实例)
utils = MathUtils()
print(utils.multiply(2, 4)) # 输出: 8
4.特殊方法:
(1)析构方法__del__()
Python中的垃圾回收主要采用的是引用计数。引用计数是一种内存管理技术,它通过引用计数器记录所有对象的引用数量,当对象的引用计数器数值为0时,就会将该对象视为垃圾进行回收。
getrefcount()函数是 sys模块中用于统计对象引用数量的函数,其返回结果通常比预期的结果大1。这是因为getrefcount()函数也会统计临时对象的引用。当一个对象的引用计数器数值为0时,就会调用_del__()方法,这个方法就是类的析构方法。系统就会销毁这个对象,收回对象所占用的内存空间。
所以析构方法(即__del_()方法)是用于销毁对象时系统自动调用的方法。每个类中也都默认有一个__del__()目方法,可以显式地定义析构方法。
注意:析构方法不是销毁方法,而是在销毁之前释放资源,销毁方法是靠py底层代码来完成
(2)__str__()
简单来说就是相当于JAVA中的toString方法
若是不重写,就会用py自带的方法
class Person:def __init__(self, name, age):self.name = nameself.age = agep = Person("Alice", 25)
print(p) # 输出: <__main__.Person object at 0x7f8b1c1b3d90>
若是重写:
class Person:def __init__(self, name, age):self.name = nameself.age = agedef __str__(self):return f"Person(name={self.name}, age={self.age})"p = Person("Alice", 25)
print(p) # 输出: Person(name=Alice, age=25)
四.继承:
跟Java一样有object类为总的父类
1.单继承
基本语法:
class 子类名(父类名)
# 父类
class Animal:def speak(self):print("动物发出声音")def eat(self):print("父类动物在吃东西")# 子类
class Dog(Animal): # Dog继承Animaldef speak(self): # 重写父类方法print("汪汪汪!")# 使用
animal = Animal()
animal.speak() # 输出:动物发出声音dog = Dog()
dog.speak() # 输出:汪汪汪!
dog.eat() #子类继承父类方法eat
super关键字编程super方法,用法相同
2.多继承
1.在职研究生继承了Student和staff类
2.两者都有showinfo方法,调用对象.方法名,会优先调用第一个继承的父类 的方法,这里即Student类的showinfo方法
若想调用
Teacher
的方法,可以:调整继承顺序:
class Person(Teacher, Student)
直接指定:
Teacher.showinfo(p)
在子类中重写方法并手动选择调用哪个父类的方法是
3. 方法重写(Override)
子类可以重写父类的方法以改变其行为
# 父类
class Animal:def speak(self):print("动物发出声音")def eat(self):print("父类动物在吃东西")# 子类
class Dog(Animal): # Dog继承Animaldef speak(self): # 重写父类方法print("汪汪汪!")# 使用
animal = Animal()
animal.speak() # 输出:动物发出声音dog = Dog()
dog.speak() # 输出:汪汪汪!
4. super()函数
# 父类
class Animal:def speak(self):print("动物发出声音")def eat(self):print("父类动物在吃东西")# 子类
class Dog(Animal): # Dog继承Animaldef speak(self): # 重写父类方法print("汪汪汪!")super().eat()# 使用
dog = Dog()
dog.speak() # 输出:汪汪汪!
5.强制转换
对于Python继承机制
class Animal:def eat(self):print("动物吃东西")class Dog(Animal): # Dog继承自Animaldef eat(self):print("狗吃骨头")def bark(self):print("汪汪叫")class Cat(Animal):def eat(self):print("猫吃鱼")
# 向上转型(自动) - 子类转父类
animal = Dog() # 狗是动物(自动转换)
animal.eat() # 输出: 狗吃骨头def trans(animal,obj):if isinstance(animal, obj):dog = animal dog.bark() else:print("不能将动物转为动物子类")trans(animal,Dog)
"""狗吃骨头
汪汪叫
"""
dog = Dog()
cat = Cat()
trans(cat,Dog)#不能将动物转为动物子类
动物= 狗可以,动物=猫 可以,但是不能 狗= 动物
要想狗 = 动物必须强制转换,狗 = (狗)动物
class Animal:def __init__(self, name):self.name = nameclass Dog:def __init__(self, animal): # 构造函数中实现转换self.name = animal.namedef bark(self):print(f"{self.name}在汪汪叫")animal = Animal("旺财")
dog = Dog(animal) # 强制将Animal转为Dog
dog.bark() # 输出: 旺财在汪汪叫
五.多态
首先Python中的多态根JAVA不一样,JAVA的多态必须要在继承的基础之上,但是py的多态,根本不关心你是不是继承,是不是相同的数据类型。只要不同的类中有相同的方法,即可实现多态。
所以要定义一个接口,实现类的多态化。
class Animal:def eat(self):print("人吃五谷杂粮")class Dog:def eat(self):print("狗吃骨头")class Cat: def eat(self):print("猫吃鱼")def jiekou(obj):obj.eat()# 创建实例
animal = Animal()
dog = Dog()
cat = Cat()# 多态调用
jiekou(animal) # 输出: 人吃五谷杂粮
jiekou(dog) # 输出: 狗吃骨头
jiekou(cat) # 输出: 猫吃鱼