面向对象程序设计
类与对象
1、类与对象的基本概念:
- 在Python中,类是一种数据结构,用于封装数据以及操作数据的方法。
- 对象是类的实例,它可以访问类中定义的属性和方法。
定义一个类:
在python中通常用class来定义一个类,命名的话通常采用驼峰命名法,即每个单词的首字母大写。
代码示例:
创建一个类
class MyClass:
pass
定义一个实例对象
ac = MyClass()
2、属性与方法
往类中添加属性,类可以包含属性,即对象的状态。可以通过在类中直接赋值来定义属性。
class Person:
# 定义属性
name = "John"
age = 30
方法是与类相关联的函数,它们定义了类的行为。在类的定义方法时,第一个参数通常是self,表示对当前对象的引用。
class Person:
name = "John"
age = 30
# 定义方法
def greet(self):
return f"Hello, my name is {self.name} and I'm {self.age} years old."
访问属性和调用方法:
通过对象实例可以访问类的属性和调用类的方法。使用点号 .
来访问对象的属性和调用方法。
print(person.name) # 输出:John
print(person.age) # 输出:30
print(person.greet()) # 输出:Hello, my name is John and I'm 30 years old.
3、构造函数
构造函数在 Python 中是一个特殊的方法,用于在创建类的实例时进行初始化操作。在 Python 中,构造函数的名称是 __init__
,它允许你在创建类的实例时执行必要的初始化操作,例如设置对象的属性或执行其他初始化任务。在构造函数中,你可以将传递给类的参数用于初始化对象的属性。
代码示例:
class Person:
def __init__(self, name, age):
# 初始化属性
self.name = name
self.age = age
def greet(self):
return f"Hello, my name is {self.name} and I'm {self.age} years old."
# 创建对象实例并传递参数
person1 = Person("Alice", 25)
person2 = Person("Bob", 30)
# 调用对象方法
print(person1.greet()) # 输出:Hello, my name is Alice and I'm 25 years old.
print(person2.greet()) # 输出:Hello, my name is Bob and I'm 30 years old.
4、析构函数
析构函数在 Python 中是一个特殊的方法,用于在对象被销毁时执行清理操作。在 Python 中,析构函数的名称是 __del__
,它在对象被销毁时自动调用。需要注意的是,析构函数不是必须的,因为 Python 具有自动内存管理机制(垃圾回收器),它负责回收不再使用的对象。
析构函数基本语法:
class ClassName:
def __del__(self):
# 清理操作
# 释放资源、关闭文件等
析构函数__del__
在对象被销毁时会自动调用,它允许你执行必要的清理操作,例如释放资源、关闭文件、断开数据库连接等。然而,由于 Python 具有自动内存管理,所以通常情况下你不需要显式地定义析构函数。
在实际编程中,通常推荐使用上下文管理器(with语句)来确保资源的正确释放。上下文管理器可以更加可靠地管理资源,确保在退出上下文时执行必要的清理操作,而不依赖于对象的销毁时机。
代码示例(采用上下文管理器来释放资源):
class FileHandler:
def __enter__(self):
self.file = open('example.txt', 'w')
return self.file
def __exit__(self, exc_type, exc_value, traceback):
self.file.close()
# 使用上下文管理器
with FileHandler() as f:
f.write('Hello, world!')
在上面的示例中,FileHandler
类实现了上下文管理器的协议,它在 __enter__
方法中打开文件,并在 __exit__
方法中关闭文件。通过使用 with
语句,可以确保文件在退出上下文时被正确关闭,即使发生异常也能够正常关闭文件,避免资源泄露。
类的封装
- 公有成员(Public):
- 在 Python 中,所有没有以下划线
_
开头的属性和方法都是公有的。 - 公有成员可以在类的内部和外部访问。
- 在 Python 中,所有没有以下划线
- 私有成员(Private):
- 以两个下划线
__
开头的属性和方法是私有的。 - 私有成员只能在类的内部访问,外部无法直接访问。
- 以两个下划线
- 受保护成员(Protected):
- 以一个下划线
_
开头的属性和方法是受保护的。 - 受保护成员只能在类的内部及其子类中访问,外部不建议直接访问。
- 以一个下划线
代码示例:
class MyClass:
def __init__(self):
self.public_attribute = 'Public' # 公有属性
self._protected_attribute = 'Protected' # 受保护属性
self.__private_attribute = 'Private' # 私有属性
def public_method(self):
return 'This is a public method.' # 公有方法
def _protected_method(self):
return 'This is a protected method.' # 受保护方法
def __private_method(self):
return 'This is a private method.' # 私有方法
def set(self,value):
self.__rivate_attribute = value #私有成员可以在类的内部进行访问,外部无法直接访问
def get(self):
return self.__rivate_attribute
# 创建对象
obj = MyClass()
# 访问公有成员
print(obj.public_attribute) # 输出: Public
print(obj.public_method()) # 输出: This is a public method.
# 访问受保护成员
print(obj._protected_attribute) # 输出: Protected
print(obj._protected_method()) # 输出: This is a protected method.
# 尝试访问私有成员,将会引发 AttributeError 错误
# print(obj.__private_attribute)
# print(obj.__private_method())
#利用私有成员的特性创建方法进行访问
value = 100
obj.set(value)
revalue = obj.get()
print(revalue)#输出100
与面向对象有关的python内置函数
1、super()函数
在 Python 中,当子类重写父类的方法时,可以使用 super()
来调用父类的方法,以便在子类中扩展父类方法的功能。
super函数的作用:
- 调用父类的方法:最常见的用法是在子类中重写父类的方法时,使用
super()
调用父类的方法,以便在子类中保留父类方法的功能,并在其基础上进行扩展。 - 支持多继承:当子类同时继承多个父类时,
super()
会按照方法解析顺序(MRO,Method Resolution Order)依次调用各个父类的方法。
代码示例:
class ParentClass:
def parent_method(self):
print("Parent method")
class ChildClass(ParentClass):
def child_method(self):
super().parent_method()
print("Child method")
# 创建子类对象并调用方法
child = ChildClass()
child.child_method()
# 输出:
# Parent method
# Child method
2、hasattr()函数、getsattr()函数和setattr()函数
hasattr()
是 Python 中的一个内置函数,用于检查对象是否具有指定的属性或方法。
函数语法:
hasattr(object, attribute)
参数介绍:
object
是要检查的对象。attribute
是要检查的属性或方法的名称。
代码示例:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person("Alice", 30)
print(hasattr(person, "name")) # True,因为 person 对象有名为 "name" 的属性
print(hasattr(person, "age")) # True,因为 person 对象有名为 "age" 的属性
print(hasattr(person, "say_hello")) # False,因为 person 对象没有名为 "say_hello" 的方法
getattr()
是 Python 中的一个内置函数,用于获取对象的属性值。
函数语法:
getattr(object, name[, default])
参数介绍:
object
:表示要获取属性的对象。name
:表示要获取的属性的名称。default
:可选参数,表示如果属性不存在时返回的默认值,默认为None
。
代码示例:
class ParentClass:
def parent_method(self):
print("Parent method")
class ChildClass(ParentClass):
def child_method(self):
getattr(super(), 'parent_method')() # 使用getattr()调用父类方法
print("Child method")
# 创建子类对象并调用方法
child = ChildClass()
child.child_method()
# 输出:
# Parent method
# Child method
在上述代码中,getattr(super(), 'parent_method')()
这一行代码通过 getattr()
函数动态获取了父类 ParentClass
中的 parent_method
方法,并调用了该方法。这种方式在一些特定的场景下会比直接使用 super()
更加灵活,特别是当需要根据条件来选择调用不同的方法时。
setattr()函数
函数功能用于设置对象的属性值。
代码示例:
class MyClass:
pass
obj = MyClass()
setattr(obj, 'attr', 42)
print(getattr(obj, 'attr')) # 输出: 42
3、property()函数
函数原型:
property(fget=None, fset=None, fdel=None, doc=None)
参数介绍:
fget
:用于获取属性值的方法(getter)。如果未提供,则属性将变为只读。fset
:用于设置属性值的方法(setter)。如果未提供,则属性将变为只读。fdel
:用于删除属性值的方法(deleter)。doc
:可选,属性的文档字符串。
该函数常用于定义类的属性,从而实现对属性的封装和控制。
class MyClass:
def __init__(self):
self._attr = None
def get_attr(self):
return self._attr
def set_attr(self, value):
self._attr = value
def del_attr(self):
del self._attr
attr = property(get_attr, set_attr, del_attr)
obj = MyClass()
obj.attr = 42
print(obj.attr) # 输出: 42
上面这些函数都可以进行重写的。
python对象的特殊方法
-
__init__()
方法:用于在创建对象时执行初始化操作。class Animal: def __init__(self): self.name = ''
-
__str__()
方法:用于获取对象的字符串表示。class Animal: def __str__(self): return 'Animal: ' + self.name
-
__eq__()
方法:用于比较对象的相等性。class Animal: def __eq__(self, other): return self.name == other.name
-
__copy__()
方法:用于创建对象的浅拷贝。class Animal: def __copy__(self): return Animal(self.name)
-
__deepcopy__()
方法:用于创建对象的深拷贝。import copy class Animal: def __deepcopy__(self, memo): return Animal(copy.deepcopy(self.name))
-
__del__()
方法:用于在对象被删除时执行一些操作。class Animal: def __del__(self): print('Animal: ', self.name, 'is deleted.')
类的继承
在面向对象编程中,继承是一种重要的概念,它允许一个类(称为子类或派生类)继承另一个类(称为父类、基类或超类)的属性和方法。子类可以重用父类的代码,并且可以添加自己的特定功能。python支持单继承和多继承两种方式。
1、单继承
单继承指一个子类只能继承一个父类的特性。
代码示例:
#代码原理架构
class ParentClass:
# 父类的属性和方法
class ChildClass(ParentClass):
# 子类继承父类,并可以添加自己的属性和方法
#代码实例
class Animal:
def __init__(self, name):
self.name = name
def sound(self):
pass
class Dog(Animal):
def sound(self):
return "Woof!"
# 创建子类对象
my_dog = Dog("Buddy")
print(my_dog.name) # 输出: Buddy
print(my_dog.sound()) # 输出: Woof!
2、多继承
多继承指一个子类可以继承多个父类的特性。
代码示例:
#代码原理架构
class ParentClass1:
# 父类1的属性和方法
class ParentClass2:
# 父类2的属性和方法
class ChildClass(ParentClass1, ParentClass2):
# 子类继承多个父类,并可以添加自己的属性和方法
#代码实例
class Bird:
def fly(self):
return "Flying"
class Horse:
def run(self):
return "Running"
class Pegasus(Bird, Horse):
pass
# 创建子类对象
my_pegasus = Pegasus()
print(my_pegasus.fly()) # 输出: Flying
print(my_pegasus.run()) # 输出: Running
需要注意的是,在多继承中可能会出现方法解析顺序(MRO)的问题,Python 使用 C3 线性化算法来确定方法的搜索顺序。
继承是面向对象编程中非常强大的工具,它允许我们构建更加灵活和可复用的代码结构。但是设计的时候要保持清晰逻辑的设计思路,避免出现深度继承链和过度复杂的继承关系,保证代码的可读性。
Mixin类介绍
Mixin类是面向对象编程中的一个概念,用于在多重继承中重用类的方法和属性,以提高代码的复用性。Mixin类通常不会被单独实例化,而是作为其他类的父类,通过多重继承的方式将其方法和属性混入到目标类中。
注意事项:
- Mixin 实现的功能需要是通用的,并且是单一的,可按需继承。
- Mixin 只用于拓展子类的功能,不能影响子类的主要功能,子类也不能依赖 Mixin。比如上例中
Person
继承不同的 Mixin 只是增加了一些功能,并不影响自身的主要功能。如果是依赖关系,则是真正的基类,不应该用 Mixin 命名。 - Mixin 类自身不能进行实例化,仅用于被子类继承。
类的多态
多态允许不同类的对象对同一消息做出不同的响应。多态性允许以统一的方式处理不同类的对象,从而增加了代码的灵活性、可扩展性和可维护性。在面向对象编程中,多态性通常通过继承和方法重写来实现。
方法重写
方法重写(又称为覆盖)是子类修改从父类继承来的方法的过程。当子类需要一个与父类相同名称但实现逻辑不同的方法时,子类可以重写这个方法,提供新的实现逻辑。
重写语法规则:
在Python中,子类可以通过定义一个与父类方法同名的方法来重写父类的方法。当调用该方法时,Python会首先在子类中查找是否存在该方法的定义,如果存在,则调用子类中的方法;如果不存在,则会继续在父类及其继承链中查找。
代码示例:
class ParentClass:
def method(self):
print("Parent method")
class ChildClass(ParentClass):
def method(self):
print("Child method")
# 创建子类对象并调用方法
child = ChildClass()
child.method() # 输出: Child method
在子类中,可以使用super()函数来调用父类的方法,从而在子类中扩展父类方法的功能。
class ParentClass:
def method(self):
print("Parent method")
class ChildClass(ParentClass):
def method(self):
super().method() # 调用父类方法
print("Additional functionality")
# 创建子类对象并调用方法
child = ChildClass()
child.method()
# 输出:
# Parent method
# Additional functionality
注意事项:
- 命名一致性:子类重写方法时,方法名必须与父类中要重写的方法名完全一致,否则Python不会认为它是重写而是新增了一个方法。
- 参数一致性:子类重写方法时,参数列表必须与父类中要重写的方法参数列表一致。
- 继承深度:Python支持多重继承,因此在使用方法重写时,需要考虑继承链的深度,以避免混淆和歧义。
str和repr重写
这里的话我再讲一下对__str__
和__repr__
的重写。
class Person:
__slots__ = ('name', 'age', 'sex')
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def main():
person_a = Person('Tom', 30, 'male')
print(person_a)
if __name__ == "__main__":
main()
|--------------------------------------|
#输出
<__main__.Person object at 0x0000021F485A4AC0>
|--------------------------------------|
class Person:
__slots__ = ('name', 'age', 'sex')
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def __str__(self):
return f"Name: {self.name}, Age: {self.age}, Sex: {self.sex}"
def main():
person_a = Person('Tom', 30, 'male')
print(person_a) # 输出对象的字符串表示形式
if __name__ == "__main__":
main()
#输出
Name: Tom, Age: 30, Sex: male
上面的第二个代码我通过重写__str__
方法,实现了自定义对象的字符串的表示形式,使其更加直观和易读。
str()返回的用户看到的字符串,而repr()返回的程序开发人员看到的字符串,也就是说,repr()是为调试服务的。
class Person:
__slots__ = ('name', 'age', 'sex')
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def __str__(self):
return f"Name: {self.name}, Age: {self.age}, Sex: {self.sex}"
__repr__ = __str__
def main():
person_a = Person('Tom', 30, 'male')
print(person_a)
person_a
if __name__ == "__main__":
main()
#输出
Name: Tom, Age: 30, Sex: male
getattr重写
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person("Alice", 30)
print(person.name)
print(person.sex)
-------------------
#输出结果
Alice
AttributeError: 'Person' object has no attribute 'sex'
这里的话报错的时候实际上是调用了getattr函数,所以下面我们队其进行重写,使其输出结果更加具有观赏性。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __getattr__(self.attr):
if attr in ('name','age'):
print(attr)
else:
print('no this attribute : %s'% attr)
person = Person("Alice", 30)
print(person.name)
print(person.sex)
--------------------
#输出结果
Alice
no this attribute : sex
特殊的变量:slots(插槽)
在python的实例化对象中,类属性和实例化属性都是可以动态增加的,但动态的增加对程序的严谨性造成了困扰。由于一个类可以产生很多的实例化对象,如果每一个对象都对自己的属性进行定义,那么程序的可维护性极低,为了解决这个问题,在python中,提供了一个特殊的系统变量slots,它可以对我们实例化对象的属性名进行限制。
在 Python 中,__slots__
是一个特殊的变量,用于限制类实例可以拥有的属性。使用 __slots__
可以明确指定一个类中允许的属性,从而在一定程度上减少实例所占用的内存空间,提高程序的执行效率。
__slots__
用于限制类实例可以拥有的属性,只有在__slots__
中列出的属性名才会被允许,其他属性名则会引发AttributeError 异常。(注意:是类实例)
class Person:
__slots__ = ('name', 'age')
def __init__(self, name, age):
self.name = name
self.age = age
def main():
person_a = Person('Tom',30)
print(person_a.name)
if __name__ == "__main__":
main()
#输出
Tom
上面的就是对slots的一种运用。再举几个例子,让各位对slots有一个更清晰的理解。
class Person:
__slots__ = ('name', 'age')
def __init__(self, name, age):
self.name = name
self.age = age
def main():
person_a = Person('Tom',30)
person_a.address = 'beijing' #不在slots列表中,而且person_a是实例化对象。
Person.sex = 'male'
print(person_a.name)
print(person_a.adderess)
if __name__ == "__main__":
main()
#输出
Tom
AttributeError: 'Person' object has no attribute 'address'
由于我已经设置了插槽,而且里面没有address属性,所以会爆出AttributeError错误,但是上面代码中的Person.sex = 'male’为什么没有报错,因为我是直接往类中添加属性,是不会报错的。
class Person:
__slots__ = ('name', 'age')
def __init__(self, name, age):
self.name = name
self.age = age
def main():
person_a = Person('Tom',30)
Person.sex = 'male'
print(person_a.name)
person_b = Person('Jack',25)
print(person_b.sex)
if __name__ == "__main__":
main()
#输出
Tom
male
我的代码中并未尝试为 person_b
实例对象添加 sex
属性,因此不会报错。在 main()
函数中,我只是创建了一个 person_b
实例对象,并尝试打印 sex
属性的值,但在 Person
类中并没有定义 sex
属性,因此打印的是类属性 sex
的值。
通过 Person.sex = 'male'
这行代码,我给 Person
类添加了一个类属性 sex
,而不是给实例对象添加属性。因此,当我尝试访问 person_b.sex
时,由于 person_b
实例对象并没有 sex
属性,Python 解释器会尝试在类中查找这个属性,找到了类属性 sex
,所以不会报错。
讲到这里差不多可以理清slots的作用了,实在理解不了的多看几遍就行了。
重载运算符
在 Python 中,可以通过定义特定的方法来实现运算符重载,从而改变内置类型的行为。以下是一些常见的运算符重载方法:
__add__(self, other)
:实现加法运算符+
的重载。__sub__(self, other)
:实现减法运算符-
的重载。__mul__(self, other)
:实现乘法运算符*
的重载。__truediv__(self, other)
:实现除法运算符/
的重载。__floordiv__(self, other)
:实现地板除运算符//
的重载。__mod__(self, other)
:实现取模运算符%
的重载。__pow__(self, other[, modulo])
:实现幂运算符**
的重载。__lt__(self, other)
:实现小于比较运算符<
的重载。__le__(self, other)
:实现小于等于比较运算符<=
的重载。__eq__(self, other)
:实现等于比较运算符==
的重载。__ne__(self, other)
:实现不等于比较运算符!=
的重载。__gt__(self, other)
:实现大于比较运算符>
的重载。__ge__(self, other)
:实现大于等于比较运算符>=
的重载。__neg__(self)
:实现一元负号运算符-
的重载。__pos__(self)
:实现一元正号运算符+
的重载。__abs__(self)
:实现绝对值函数abs()
的重载。__str__(self)
:实现str()
函数的重载,用于将对象转换为字符串。__repr__(self)
:实现repr()
函数的重载,用于返回对象的“官方”字符串表示形式。
这些方法中的每一个都接受两个参数:self
和 other
。self
是方法所属的对象,other
是与之进行运算的另一个对象。
下面我举一个例子展示如何在python中重载加法运算符+:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
if isinstance(other, Point):
return Point(self.x + other.x, self.y + other.y)
else:
raise TypeError("Unsupported operand type(s) for +: '{}' and '{}'".format(type(self), type(other)))
# 创建两个 Point 对象
point1 = Point(1, 2)
point2 = Point(3, 4)
# 使用加法运算符进行运算
result = point1 + point2
print(result.x,result.y)
-------------------------------
#输出结果
4 6
在这个例子中,我们定义了一个 Point
类,其中重载了 __add__()
方法来实现加法运算符的重载。当我们使用 +
运算符将两个 Point
对象相加时,__add__()
方法会被调用,并返回一个新的 Point
对象,其 x
和 y
分别为两个对象的对应属性之和。