Python 是一种支持面向对象编程(Object-Oriented Programming, OOP)的语言,可以很容易在Python中创建一个类和对象。
面向对象编程是一种编程范式,它使用“对象”来设计应用和软件。在面向对象的程序中,数据(属性)和功能(方法)被封装在对象中,并通过对象间的交互来实现程序的功能。
一、面向对象的基本特征
- 类(Class): 用来描述具有相同的属性和方法的对象的
集合
。类是对象的蓝图或模板,它定义了对象的属性和方法
。 - 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
对象是类的实例
,具有类定义的属性和方法。 - 属性(Attribute):
属性是对象的数据部分
,它存储了对象的状态信息。 - 方法(Method):方法是对象的行为的定义,即
对象可以执行的操作
。即类中定义的函数。 - 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。
类变量通常不作为实例变量使用。 - 数据成员:类变量或者实例变量,用于处理类及其实例对象的相关的数据。
- 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,
这个过程叫方法的覆盖(override),也称为方法的重写。 - 局部变量:定义在方法中的变量,只作用于当前实例的类。
- 实例变量:在类的声明中,属性是用变量来表示的。
这种变量就称为实例变量,是在类声明的内部但是在类的其他成员方法之外声明的。 - 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。
继承也允许把一个派生类的对象作为一个基类对象对待。 - 实例化:创建一个类的实例,类的具体对象。
二、面向对象编程
1、类的使用
注意:需要在单独的文件中定义一个类。
类的一般语法:
class ClassName:
'类的帮助信息' #类文档字符串
class_suite #类体(由类成员、方法、数据属性组成)
示例1:
# 定义一个名为Dog的类
class Dog:
'基类'
dogCount = 0 #类变量
def __init__(self, name, age):
# 初始化方法,当创建类的新实例时自动调用
self.name = name # 对象的属性
self.age = age
Dog.dogCount += 1
def bark(self):
# 类的方法
print(f"{self.name} says Woof!")
def totaldogCount(self):
# 类的方法
print("Total Employee %d" % Dog.dogCount)
# 创建类的实例(对象)通过 __init__ 方法接收参数
my_dog = Dog("Buddy", 3)
# 访问对象的属性,使用.来访问属性
print(my_dog.name) # 输出:Buddy
print(my_dog.age) # 输出:3
# 调用对象的方法
my_dog.bark() # 输出:Buddy says Woof!
my_dog.totaldogCount()
运行结果:
Buddy
3
Buddy says Woof!
Total Employee 1
在这个示例中:定义了一个名为 Dog 的类
,它有一个初始化方法 init 和两个方法 bark和totaldogCount。创建了一个 Dog 类的实例 my_dog
,设置了它的属性 name 和 age
。访问了 my_dog 的属性并调用了它的方法
。
- dogCount 是
类变量
,其值将在这个类的所有实例之间共享
。可以在内部类或外部类使用 Dog.dogCount访问。 - 第一种方法
__init__()方法
是一种特殊的方法,被称为类的构造函数或初始化方法,当创建了这个类的实例时就会调用该方法。
self 代表类的实例
,self 在定义类的方法时是必须有的
,虽然在调用时不必传入相应的参数。
2、内置类属性
当创建一个类后,系统会自动为该类赋予一些内置类属性。这些属性名通常使用双下划线(__)包围,以与普通属性名区分。以下是一些常见的内置类属性:
init:类的初始化方法,当创建类的新实例时会自动调用。通常,我们在这里进行属性的初始化操作。__init__的第一个参数总是self,代表创建的实例本身。
new:一个静态方法,用于创建对象实例。它相当于构造器,负责对象的创建。__new__的第一个参数是cls,代表类本身。
str:一个特殊方法,用于定义当使用print函数或str()函数时对象的字符串表示形式。
doc:一个属性,用于存储对象的文档字符串。通过访问对象的__doc__属性,我们可以获取其文档字符串。
dict:这个属性可以作用在文件、类或类的对象上,最终返回的结果为一个字典,包含了对象或类的属性。
bases : 类的所有父类构成元素(包含了一个由所有父类组成的元组)
还有其他一些内置类属性,如__file__、name、module、base、__bases__等,它们各自具有特定的用途和含义。
这些内置类属性都是Python语言本身提供的,用于支持面向对象编程的各种特性和功能。
在编写Python代码时,了解这些内置类属性的用法和含义,可以帮助我们更有效地利用Python的面向对象编程特性。
对示例1查看内置类属性:
print ("Dog.__doc__:", Dog.__doc__)
print ("Dog.__name__:", Dog.__name__)
print( "Dog.__module__:", Dog.__module__)
print ("Dog.__bases__:", Dog.__bases__)
print ("Dog.__dict__:", Dog.__dict__)
运行结果:
Dog.__doc__: None
Dog.__name__: Dog
Dog.__module__: __main__
Dog.__bases__: (<class 'object'>,)
Dog.__dict__: {'__module__': '__main__', 'dogCount': 1, '__init__': <function Dog.__init__ at 0x00000281DAB9B380>, 'bark': <function Dog.bark at 0x00000281DAB9B420>, 'totaldogCount': <function Dog.totaldogCount at 0x00000281DAB9B4C0>, 'prt': <function Dog.prt at 0x00000281DAB9B560>, '__dict__': <attribute '__dict__' of 'Dog' objects>, '__weakref__': <attribute '__weakref__' of 'Dog' objects>, '__doc__': None}
3、self代表类的实例,而非类
示例2;
class onetest:
def prt(self):
print(self)
print(self.__class__)
t = onetest()
t.prt()
class twotest:
def prt(too):
print(too)
print(too.__class__)
运行结果:
<__main__.onetest object at 0x000002A8CA8CF1D0>
<class '__main__.onetest'>
<__main__.twotest object at 0x000002A8CA8CF200>
<class '__main__.twotest'>
这个示例可知,self代表的是类的实例,代表当前对象的地址,而self.__class__则是指向类。self不是关键字,换成其他的too也可以执行。
4、对象销毁(垃圾回收)
Python 使用了引用计数来跟踪和回收垃圾。
在 Python 内部记录着所有使用中的对象各有多少引用。
一个内部跟踪变量,称为一个引用计数器。
当对象被创建时, 就创建了一个引用计数, 当这个对象不再需要时, 也就是说, 这个对象的引用计数变为0 时, 它被垃圾回收。但是回收不是"立即"的, 由解释器在适当的时机,将垃圾对象占用的内存空间回收。
Python 的垃圾收集器是一个引用计数器和一个循环垃圾收集器。
__del__在对象销毁的时候被调用,当对象不再被使用时,__del__方法运行。
示例如下:
class demo:
def __init__(self,x=0,y=0,z=0):
self.x = x
self.y = y
self.z = z
def add(self):
self.z = self.x + self.y
print(self.z)
return self.z
def __del__(self):
class_name = self.__class__.__name__
print(class_name, "销毁")
# 创建类的对象
dem = demo(1,2,5)
#访问对象的属性
print(dem.x,dem.y,dem.z)
#调用对象的函数or方法
dem.add()
# 再次创建类的对象
dem1 = demo()
dem2=dem1
dem3=dem2
print(id(dem1),id(dem2),id(dem3))
运行结果:
1 2 5
3
1602445688368 1602445688368 1602445688368
demo 销毁
demo 销毁
5、类的继承
类的继承允许创建一个新类(称为子类或派生类),继承自一个或多个已存在的类(称为父类或基类)。通过继承,子类可以自动获得父类的属性和方法,并可以添加或覆盖自己的属性和方法。这提供了一种重用代码和组织代码层次结构的强大机制
。
示例:
# 定义一个父类
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("Subclass must implement this method")
# 定义一个子类,继承自Animal类
class Dog(Animal):
def __init__(self, name, breed):
# 调用父类的初始化方法
super().__init__(name)
self.breed = breed
def speak(self):
# 实现父类中未实现的方法
return f"{self.name} barks!"
# 创建一个Dog类的实例
my_dog = Dog("小黄", "Labrador")
# 访问实例的属性
print(my_dog.name) # 输出: 小黄
print(my_dog.breed) # 输出: Labrador
# 调用实例的方法
print(my_dog.speak()) # 输出: 小黄 barks!
运行结果:
小黄
Labrador
小黄 barks!
在示例中,Dog 类继承
了 Animal 类。
Dog 类通过调用 super().__init__(name)
来调用父类 Animal 的 init 方法,以初始化从 Animal 继承的属性 name。且Dog 类添加了自己的属性
breed,并实现了 speak 方法。
super() 函数是调用父类方法
的一种常见方式。super() 返回一个临时对象,它绑定到父类,并允许调用父类的方法。这种方式特别有用当类继承自多个父类,且需要明确调用特定的父类方法时。
类的继承可以形成复杂的层次结构,一个类可以继承自多个父类(多重继承
),但通常应该谨慎使用,以避免复杂的继承和复杂的依赖关系。在Python中,多重继承是允许的,但可能会导致一些难以预料的行为,特别是在处理方法解析顺序(MRO,Method Resolution Order)时。
注意点:
(1) 若子类需要覆盖
父类的方法(即使用自己的实现替代父类的实现),可以简单地在子类中定义同名方法
。当在子类的实例上调用该方法时,Python 会优先使用子类中的定义
。
示例:
class Parent: # 定义父类
def myMethod(self):
print('调用父类方法')
class Child(Parent): # 定义子类
def myMethod(self):
print('调用子类方法')
# 创建子类实例
dem = Child()
dem.myMethod() # 子类调用重写方法
运行结果:
调用子类方法
(2) 若父类中的方法不应该被子类直接覆盖,或者需要在子类实现之前执行一些操作,可以在父类中使用 raise NotImplementedError
来抛出一个异常。这样,若子类没有实现该方法
,当尝试调用时就会触发异常。
6、类的私有(属性和方法)
(1)类的私有属性
通过在属性名前面加上两个下划线(__)来定义的。这是一种约定俗成的命名方式,不是Python本身的一种强制机制。Python会将这样的属性名“变形”(mangle),使其变成在类外部无法直接访问的形式。但这不意味着私有属性是完全不可访问的,它只是一种防止在类外部直接访问的机制。
示例:
class MyClass:
def __init__(self):
self.__private_attribute = "This is a private attribute"
def get_private_attribute(self):
return self.__private_attribute
# 尝试直接访问私有属性(会报错)
# instance = MyClass()
# print(instance.__private_attribute) # AttributeError: 'MyClass' object has no attribute '__private_attribute'
# 通过类内部提供的方法访问私有属性
instance = MyClass()
print(instance.get_private_attribute()) # 输出: This is a private attribute
以上示例中,__private_attribute
是一个私有属性,它在类的外部是不可见的。尝试直接访问它会引发一个AttributeError。但是,通过在类内部定义一个方法(如get_private_attribute),则可对私有属性进行访问。
注意:
Python没有真正的私有属性。
虽然
变形机制使得私有属性在类外部难以直接访问
,但仍然可以通过类的__dict__属性或者使用getattr函数,结合属性名的变形规则来访问私有属性。子类也可以访问父类的私有属性
。私有属性主要是一种编程约定,用于表明某些属性
不应该在类的外部被直接
访问或修改。私有属性通常用于
存储那些不应该被外部直接修改的内部状态
,或者用于实现类的内部逻辑。通过提供公共的方法
来访问和修改这些属性,可以更好地控制对属性的访问,并隐藏
实现细节。
(2)类的私有方法
通过在方法名前面加上两个下划线(__)
来定义的。和私有属性一样,私有方法并不是完全不可访问的。它们仍然可以通过类的内部逻辑或继承关系被访问,只是不鼓励在类外部直接调用。
示例:
class MyClass:
def __init__(self):
self.public_attribute = "Public"
def public_method(self):
print("This is a public method.")
def __private_method(self):
print("This is a private method.")
# 这里可以访问和修改类的私有属性和其他方法
def call_private_method(self):
# 通过内部方法调用私有方法
self.__private_method()
# 尝试直接调用私有方法(会报错)
# instance = MyClass()
# instance.__private_method() # AttributeError: 'MyClass' object has no attribute '__private_method'
# 通过公共方法间接调用私有方法
instance = MyClass()
instance.call_private_method() # 输出: This is a private method.
这个示例中,__private_method
是一个私有方法。尝试在类外部直接调用它会引发一个AttributeError。但是,通过在类内部定义一个公共方法(如call_private_method),可间接地调用私有方法。
注意:
Python并没有真正的私有方法。同私有属性。
在实际编程中,私有方法通常用于实现类的内部逻辑,这些逻辑不需要被类的外部用户知道或调用。通过只提供公共的接口来访问和修改类的状态,可以更好地封装类的实现细节,并控制对类内部逻辑的访问。
7、关于重载
在Python中,类方法或函数的重载与一些其他编程语言(如Java或C++)中的概念有所不同。在Java或C++中,方法重载是指可以定义多个同名函数,但它们具有不同的参数类型或数量。然而,Python并不直接支持这种形式的重载
。
在Python中,函数或方法的参数类型和数量在定义时并不固定,也就是说,Python是一种动态类型语言
。因此,Python的函数可以接受任意数量和类型的参数。这意味着Python的函数在某种程度上已经具有某种“重载”的能力,即同一个函数可以处理不同类型的参数或不同数量的参数。
虽Python没有直接的方法重载语法,但可通过使用默认参数和可变参数来实现类似的效果。例如,定义一个函数,它接受一个或两个参数,根据传递的参数数量执行不同的操作。当只传递一个参数时,它执行一种操作;当传递两个参数时,它执行另一种操作。
示例:
print("-----运算符重载----")
class Vector:
def __init__(self, a, b):
self.a = a
self.b = b
def __str__(self):
return 'Vector (%d, %d)' % (self.a, self.b)
def __add__(self, other):
return Vector(self.a + other.a, self.b + other.b)
v1 = Vector(5, 20)
v2 = Vector(10, -2)
print(v1 + v2) #输出 Vector (15, 18)
此外,Python中的类方法也可以利用这种机制来模拟方法重载。通过定义具有默认参数的类方法,可以根据传递的参数的不同来执行不同的操作。
注意:
虽Python可通过这种方式模拟方法重载,但并不是Python的推荐做法。
Python的设计哲学更倾向于简洁和明确,通常更倾向于使用明确的函数或方法名来描述其功能,而不是依赖于参数的数量或类型来决定函数的行为。
8、“魔法”之特殊功能
在python中方法名如果是__xxxx__()
的,那么就有特殊的功能。对此先介绍单下划线、双下划线、头尾双下划线。
单下划线(_):例如_foo 它表示这个变量或函数是类的内部使用,不建议在类外部直接访问。主要用作一个“弱内部使用”的标记。
双下划线(__):表示的是私有类型(private)的变量,这种私有性并不是绝对的,仍然可以通过一些方式访问和修改这些变量或函数。
头尾双下划线(如__var__):例如__init__用于初始化对象,__str__用于定义对象的字符串表示等。这些方法在Python的魔术方法(magic methods)中扮演着重要角色,用于实现对象的各种特殊操作。一般是系统定义名字 。
示例:
class House:
'定义一个雨类'
def __init__(self,newname,newvalue):#初始化方法
self.newname = newname
self.newvalue = newvalue
def __str__(self):
return "your house name is :%s,value is %d" %(self.newname,self.newvalue)
def introduce(self):
print("你的房子:%s, 价值:%d" % (self.newname, self.newvalue))
#创建一个对象
Lily = House("茅屋",10000000)
print("关于对象属性介绍",Lily.newname,Lily.newvalue) #输出:关于对象属性介绍 茅屋 10000000
print(Lily) #输出: your house name is :茅屋,value is 10000000
Lily.introduce() #输出: 你的房子:茅屋, 价值:10000000
- 当使用print输出对象的时候,只要定义了__str__(self)方法,那么就会打印从在这个方法中return的数据。
- __str__方法需要返回一个字符串,当做这个对象的描写。