类是封装对象的属性和行为
的载体,具有相同属性和行为
的一类实体被称为类,类是一种抽象概念。编写类时,你定义一大类对象都有的通用行为。基于类创建对象时,每个对象都自动具备这种通用行为,然后可根据需要赋予每个对象独特的个性。
面向对象程序设计具有三大基本特征:封装、继承、多态。
- 封装将对象的属性和行为封装起来,而将对象的属性和行为封装起来的载体就是类,类通常对客户隐藏其实现细节,这就是封装思想。
- 继承是实现重复利用的重要手段,子类通过继承复用了父类的属性和行为的同时,又添加了子类特有的属性和行为。
- 多态是将父类对象应用于子类的特征。
面向对象与面向过程编程的区别
- 面向过程是分析出解决问题的步骤,然后用函数将这些步骤一一实现,使用的时候另行调用。面向过程只考虑在函数中封装代码逻辑,而不考虑函数的归属关系。
- 面向对象是把解决问题的事物提取为多个对象,而建立对象的目的也不是为了完成一个个步骤,而是为了描述某个事物在解决问题的过程中所发生的行为。面向对象是一个更大的封装,根据对象职责明确函数的归属关系。
一. 创建和使用类
下面来编写一个表示小狗的简单类Dog,它表示的不是特定的小狗,而是任何小狗。
1. 定义类
根据Dog类创建的每个实例都将存储名字和年龄,我们赋予了每条小狗蹲下(sit())和打滚(roll_over())的能力:
class Employee():
"所有员工的基类"
empCount = 0
def __init__(self, name, salary):
self.name = name
self.salary = salary
Employee.empCount += 1
def displaycount(self):
print('总共员工人数:', Employee.empCount)
def displayEmployee(self):
print('name:', self.name, ',salary:', self.salary)
2. 类和对象的属性
2.1. 形参self(类比java的this)
- 在
__init__()
方法的定义中,形参self必不可少
,而且必须位于其他形参的前面。为何必须在方法定义中包含形参self呢?因为Python调用这个方法来创建Dog实例时,将自动传入实参self
。- 每个与实例相关联的方法?ing 调用都自动传递实参self,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。
2.2. 类属性(对于java的静态变量)
如上 empCount = 0
,只能由类修改,对象仅可以访问。
2.3. python内置类属性
在Python中,模块的内置属性可以有多种,其中常见的包括:
属性 | 描述 |
---|---|
__name__ |
类名 |
__file__ |
模块的文件路径 |
__doc__ |
模块的文档字符串 |
__module__ |
类定义所在模块 |
__dict__ |
类的属性 |
__bases__ |
类的所有父类 |
print("doc属性", Employee.__doc__)
print("name属性", Employee.__name__)
print("module属性", Employee.__module__)
print("bases", Employee.__bases__)
print("dict属性", Employee.__dict__)
doc属性 所有员工的基类
name属性 Employee
module属性 __main__
bases (<class 'object'>,)
dict属性 {'__module__': '__main__', '__doc__': '所有员工的基类', 'empCount': 0, '__init__':
<function Employee.__init__ at 0x1008f4cc0>,
'displaycount': <function Employee.displaycount at 0x1008f5080>,
'displayEmployee': <function Employee.displayEmployee at 0x1008f58a0>,
'__dict__': <attribute '__dict__' of 'Employee' objects>,
'__weakref__': <attribute '__weakref__' of 'Employee' objects>}
2.4. 对象属性
对象属性是对象特征的描述,如: = name
, self.salary = salary
。
2.5. 私有属性
私有属性是指类内部的属性,它们在命名上以双下划线开头,私有属性的存在主要是为了限制直接访问和修改,从而保护类的内部状态不被外部随意更改。
类的方法可以访问其对象的私有属性,但在类外部直接访问会引发AttributeError异常。
class MyClass:
def __init__(self):
self.__private_attr = 10 # 私有属性
def get_private_attr(self):
return self.__private_attr
def set_private_attr(self, value):
self.__private_attr = value
# 在类内部访问私有属性
obj = MyClass()
print(obj.get_private_attr()) # 输出: 10
# 尝试在类外部访问私有属性,会引发 AttributeError
# print(obj.__private_attr) # 报错: AttributeError: 'MyClass' object has no attribute '__private_attr'
# 可以通过名称重整的方式访问私有属性,但不推荐这样做
print(obj._MyClass__private_attr) # 输出: 10
3. 函数
3.1. 构造方法
__init__()
是一个特殊方法,每当你根据类创建新实例时,Python都会自动运行它。务必确保__init__()的两边都有两个下划线
,否则当你使用类来创建实例时,将不会自动调用这个方法,进而引发难以发现的错误。__init__()
中的第一个实参self(代表对象本身)不需要显示传递。
3.2. 析构函数
行为 | 说明 |
---|---|
定义 | 在Python中,析构函数是一种特殊的方法,用于在对象被销毁(即对象的生命周期结束时)时执行特定的清理操作。Python中的析构函数使用特殊的方法名__del__ 来定义。 |
自动触发 | Python 的垃圾回收机制会自动管理内存并调用对象的析构函数。析构函数的调用时机不是确定的,而是在对象不再被引用且垃圾回收时触发的。 |
清理工作 | 析构函数通常用于执行一些清理工作,比如释放资源(如文件描述符、数据库连接等)、关闭文件、解锁资源等。 |
class MyClass:
def __init__(self, name):
self.name = name
# 可以添加的清理行为
def __del__(self):
print(f"{self.name} 对象被销毁")
# 创建对象
obj1 = MyClass("Object 1")
obj2 = MyClass("Object 2")
# 手动删除一个对象的引用,触发垃圾回收
del obj1
# 手动删除另一个对象的引用,触发垃圾回收
del obj2
# Python 解释器会在程序结束时自动调用剩余对象的析构函数
3.3. 对象方法
对象方法是在类中定义的,以关键字self作为第一个参数。在对象方法中可以使用self关键字定义和访问对象属性,同时对象属性会覆盖类属性。
class Car:
def __init__(self, brand, model):
self.brand = brand
self.model = model
self.mileage = 0
def drive(self, distance):
print(f"Driving the {self.brand} {self.model}")
self.mileage += distance
def display_info(self):
print(f"{self.brand} {self.model}, Mileage: {self.mileage} miles")
# 创建 Car 类的实例
my_car = Car("Toyota", "Camry")
# 调用对象方法
my_car.drive(50)
my_car.display_info()
# 再次调用对象方法
my_car.drive(30)
my_car.display_info()
3.4. 私有方法
私有方法采用双前置下划线的形式表示,只能在类内调用。也可用object._class_method
的方式调用私有方法。
class Car():
def __init__(self, age, color, brand='奥迪'):
self.__brand = brand
self._age = age
self.color = color
def __desc(self):
print('this is a {} car'.format(self.color))
car = Car(9, 'red')
# car.__desc()#报错 AttributeError: 'Car' object has no attribute '__desc'
# 可以通过`object._class_method`访问方法
car._Car__desc() # this is a red car,可以访问私有方法
3.5. 内置类方法
方法名 | 描述 |
---|---|
__dir__() |
输出本类和父类所有属性和方法名 |
__str__ |
返回对象的字符串表示。 |
__repr__ |
显示对象的一些必要信息 |
__len__() |
返回对象的长度。 |
__getitem__() |
允许通过索引访问元素。 |
__getattr__ |
获取属性的值 |
__delatr__ |
删除某属性 |
__setattr__ |
设置属性的值 |
__call__ |
使实例对象可以像函数一样被调用。 |
__eq__() |
定义对象之间的相等比较。 |
__hash__() |
返回对象的哈希值。 |
这些方法通过重写特定的双下划线方法(也称为魔术方法),来定义类的行为,使得对象可以表现出各种不同的特性和行为。
二. 创建对象实例
1. 创建实例
if __name__ == '__main__':
my_dog=Dog("Willie",6)
print(f"My dog's name is {my_dog.name}.")
print(f"My dog is {my_dog.age} years old.")
创建dog实例时: my_dog=Dog("Willie",6)
,Python使用实参’Willie’和6调用Dog类的方法__init__()。方法__init__()创建一个表示特定小狗的实例,并使用提供的值来设置属性name和age。
接下来,Python返回一个表示这条小狗的实例,而我们将这个实例赋给了变量my_dog。
(类似于java)在这里,命名约定很有用:通常可认为首字母大写的名称(如Dog)指的是类,而小写的名称(如my_dog)指的是根据类创建的实例。
2. 访问属性
要访问实例的属性,可使用句点表示法。
my_dog.name
3. 调用方法
使用句点表示法来调用Dog类中定义的任何方法。
class Dog:
--snip--
my_dog = Dog('Willie', 6)
my_dog.sit()
my_dog.roll_over()
三. 使用类和实例
接下来再看一个类似的例子:
class Car:
"""一次模拟汽车的简单尝试。"""
def __init__(self, make, model, year):
"""初始化描述汽车的属性。"""
self.make = make
self.model = model
self.year = year
def get_descriptive_name(self):
"""返回整洁的描述性信息。"""
long_name = f"{self.year} {self.make} {self.model}"
return long_name.title()
if __name__ == '__main__':
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
1. 给属性指定默认值
创建实例时,有些属性无须通过形参来定义,可在方法__init__()中为其指定默认值。
class Car:
def __init__(self, make, model, year):
"""初始化描述汽车的属性。"""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 5
def get_descriptive_name(self):
"""--snip--"""
def read_odometer(self):
"""打印一条指出汽车里程的消息。"""
print(f"This car has {self.odometer_reading} miles on it.")
if __name__ == '__main__':
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
my_new_car.read_odometer()
2. 修改属性的值
我们能以三种方式修改属性的值:直接通过实例进行修改,通过方法进行设置,以及通过方法进行递增(增加特定的值)。
直接修改属性的值
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
my_new_car.odometer_reading = 23
my_new_car.read_odometer()
通过方法修改属性的值
class Car:
...
def update_odometer(self, mileage):
"""将里程表读数设置为指定的值。"""
self.odometer_reading = mileage
if __name__ == '__main__':
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
my_new_car.update_odometer(23)
my_new_car.read_odometer()
可对方法update_odometer()进行扩展,使其在修改里程表读数时做些额外的工作。下面来添加一些逻辑,禁止任何人将里程表读数往回调:
def update_odometer(self, mileage):
"""
将里程表读数设置为指定的值。
禁止将里程表读数往回调。
"""
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
通过方法对属性的值进行递增
def increment_odometer(self, miles):
"""将里程表读数增加指定的量。"""
self.odometer_reading += miles
if __name__ == '__main__':
my_used_car = Car('subaru', 'outback', 2015)
print(my_used_car.get_descriptive_name())
my_used_car.update_odometer(23_500)
my_used_car.read_odometer()
my_used_car.increment_odometer(100)
my_used_car.read_odometer()
注意 你可以使用类似于上面的方法来控制用户修改属性值(如里程表读数)的方式,但能够访问程序的人都可以通过直接访问属性来将里程表修改为任何值。
要确保安全,除了进行类似于前面的基本检查外,还需特别注意细节。
且我们应该把属性变成私有化,这样就不能直接任意修改里程表读数了。 如下是不好的操作 ing
my_used_car.odometer_reading=1000
my_used_car.read_odometer()