Python青少年简明教程:类和对象入门
Python支持多种编程范式(programming paradigms),即支持多种不同的编程风格和方法。初学者开始重点学习关注的编程范式,一般而言是面向过程编程和面向对象编程。面向过程编程(Procedural Programming)它关注于通过过程(或称为函数、子程序)来组织程序的逻辑。它是命令式编程(Imperative Programming)的一种具体风格。
【Python也支持函数式编程(Functional Programming)的一些特性,如lambda函数、map()、filter()等。
Python也支持事件驱动编程(Event-Driven Programming),这编程范式特别适用于创建响应式和交互式的应用程序。Python提供了多种方式来实现事件驱动编程:使用标准的(内置的)库tkinter(Python的标准GUI库,适合创建简单的桌面应用), 标准库中的asyncio模块(提供了对异步编程的支持。它可以处理事件循环、协程、任务和事件驱动编程), 一些第三方库(如PyQt用于创建GUI应用,pygame:用于游戏开发)等。】
前面的介绍的编程,主要是面向过程编程(Procedural Programming)它关注于通过过程(或称为函数、子程序)来组织程序的逻辑。它是命令式编程(Imperative Programming)的一种具体风格。
作为对比,先概括面向过程编程风格的特点。面向过程编程是命令式编程的一种具体风格。它主要关注于:
将程序分解为一系列的过程(也称为函数或子程序)
这些过程按顺序执行,每个过程执行特定的任务
强调代码的重用和模块化
示例:
def calculate_area(length, width):
return length * width
def calculate_perimeter(length, width):
return 2 * (length + width)
# 使用函数
room_length = 5
room_width = 4
area = calculate_area(room_length, room_width)
perimeter = calculate_perimeter(room_length, room_width)
print(f"房间面积:{area}平方米")
print(f"房间周长:{perimeter}米")
面向对象编程呢?这是另一种重要的范式,它主要关注于:
将数据和操作数据的方法组织到对象中
使用类来创建对象
支持封装、继承和多态等概念
示例:
class Room:
def __init__(self, length, width):
self.length = length
self.width = width
def calculate_area(self):
return self.length * self.width
def calculate_perimeter(self):
return 2 * (self.length + self.width)
# 使用类
my_room = Room(5, 4)
print(f"房间面积:{my_room.calculate_area()}平方米")
print(f"房间周长:{my_room.calculate_perimeter()}米")
下面将介绍Python面向对象编程知识。
Python是一种面向对象编程(OOP)的语言,类和对象是其核心概念。
类(Class)
类 是对象的蓝图或模板,它定义了一组属性和方法,这些属性和方法被具体的对象实例所共享。
基本语法:
class ClassName:
# 类体
pass
【注:pass 是一个特殊语句,它主要用作占位符,不会改变程序的执行流程,但pass 是一个语句,是一个语法上的无操作语句,它的出现能避免不写任何语句时的语法错误。】
下面给出定义类的包含比较全面的语法:
class ClassName:
# 类属性
class_attribute = value
# 初始化方法
def __init__(self, parameter1, parameter2):
self.instance_attribute1 = parameter1
self.instance_attribute2 = parameter2
# 实例方法
def instance_method(self):
# 方法体
pass
# 类方法
@classmethod
def class_method(cls):
# 方法体
pass
# 静态方法
@staticmethod
def static_method():
# 方法体
pass
类和对象的说明:
属性 (Attributes): 对象具有的数据。
方法 (Methods): 对象可以执行的操作。
实例化 (Instantiation): 使用类创建对象的过程。
self: 在方法定义中,self 代表当前对象,用于访问修改对象的属性和方法。
实例属性:定义在 __init__ 方法中的属性,用 self 引用。每个对象都有自己独立的实例属性。
类属性:在类体内直接定义,用 ClassName.attribute 或 self.__class__.attribute 引用。所有对象共享类属性。
实例方法:定义在类内部,以 self 作为第一个参数的方法。用于操作对象实例的属性。
类方法:使用 @classmethod 装饰器定义,以 cls 作为第一个参数的方法。用于操作类属性。
静态方法:使用 @staticmethod 装饰器定义,不需要 self 或 cls 参数。通常与类或实例无关。
必须的部分:
class ClassName: 这是定义类的基本语法,是必须的。
常用但不是必须的部分:
__init__ 方法:虽然不是严格必需的,但在大多数情况下都会定义,用于初始化对象。
实例属性:通常在 __init__ 中定义,但也可以在其他方法中定义。
可选但常见的部分:
类属性:不是必须的,但在需要所有实例共享某些属性时很有用。
实例方法:定义对象的行为,是面向对象编程的核心,但并非每个类都必须有。
类方法和静态方法:这些是更高级的概念,根据具体需求使用。
另外,Python 使用命名约定(如单下划线前缀)来表示私有或保护成员,而不是严格的访问控制。
需要注意,重要的是理解这些组件的作用,并根据具体情况选择使用。过度设计(如在不需要的地方使用类方法或静态方法)可能会使代码不必要地复杂。
Python类中各种成员的用途和注意事项:
☆类属性(Class Attributes)
用途:
存储所有实例共享的数据
定义类级别的常量
跟踪类的全局状态
注意事项:
可以通过类名或实例访问
修改时要小心,因为会影响所有实例
可能被实例属性覆盖
☆实例属性(Instance Attributes)
用途:
存储每个实例特有的数据
定义对象的状态
注意事项:
通常在__init__方法中初始化,使用self定义的变量
只能通过实例访问
每个实例可以有不同的值
☆实例方法(Instance Methods)
用途:
定义对象的行为
操作实例的状态
注意事项:
第一个参数总是self,代表实例本身
可以访问和修改实例属性
通过实例调用
☆类方法(Class Methods)
用途:
操作类属性
实现替代构造器
注意事项:
使用@classmethod装饰器
第一个参数通常命名为cls,代表类本身
可以通过类或实例调用
不能直接访问实例属性
☆静态方法(Static Methods)
用途:
实现与类相关但不需要访问类或实例状态的功能
组织代码结构
注意事项:
使用@staticmethod装饰器
不需要特殊的首参数
可以通过类或实例调用(不建议通过实例调用)
不能访问类或实例属性
☆魔术方法(Magic Methods),也称为特殊方法
用途:
定制类的特殊行为,用于实现特定的功能,如对象初始化、字符串表示、运算符重载等。
注意事项:
以双下划线开始和结束,如__init__、str、__len__等
在特定情况下自动调用
可以极大地增强类的功能和灵活性
☆属性装饰器(Property Decorators)
用途:
将方法调用伪装成属性访问(可以像访问属性一样访问)
实现计算属性
控制属性的访问、设置和删除
注意事项:
使用@property装饰器
可以定义getter、setter和deleter
提供了一种优雅的方式来封装数据访问
☆内部类(Nested Classes)
用途:
将相关类组织在一起
封装辅助类
注意事项:
定义在另一个类的内部
可以访问外部类的属性
通常用于实现辅助功能或数据结构
☆私有成员(Private Members)
用途:
隐藏实现细节
防止直接访问和修改
注意事项:
以双下划线__开头
Python通过名称改写机制实现
可以通过特殊方式访问,不是绝对私有
☆保护成员(Protected Members)
用途:
表示应该被视为内部使用的成员
在继承中使用
注意事项:
以单下划线_开头
这只是一个约定,并不强制执行,实际可以被外部访问,但不应该
特别提示,实际使用时,合理使用各种成员类型,有些部分不一定出现,选择合适的类型可以使您的代码更加清晰和高效。
Python类的成员类型和概念很多,一次性全部掌握是有难度的。这是很正常的学习过程,可以先集中精力关注您不需要一次掌握所有内容。随着您编写更多的Python代码,这些概念会变得越来越清晰。最常用的部分,如实例属性、实例方法、类方法和静态方法。基础示例:
class Car:
total_cars = 0 # 类属性
def __init__(self, make, model):
self.make = make # 实例属性
self.model = model # 实例属性
Car.total_cars += 1
def display_info(self): # 实例方法
return f"{self.make} {self.model}"
@classmethod
def get_total_cars(cls): # 类方法
return cls.total_cars
@staticmethod
def honk(): # 静态方法
return "Beep beep!"
# 使用类
my_car = Car("Toyota", "Corolla")
print(my_car.display_info()) # 输出: Toyota Corolla
print(Car.get_total_cars()) # 输出: 1
print(Car.honk()) # 输出: Beep beep!
这个例子涵盖了基本的类属性、实例属性、实例方法、类方法和静态方法。您可以从这里开始,逐步理解每个概念的作用和使用场景。
Python 中类的方法确实与普通函数比较
在 Python 中,类的方法确实与普通函数在很多方面都是类似的。两张比较如下:
相似之处:
a) 定义方式:
类方法和普通函数都使用 def 关键字定义。
b) 参数传递:
两者都可以接受位置参数、关键字参数、默认参数、*args 和 **kwargs。
c) 返回值:
两者都可以有返回值,使用 return 语句。
d) 文档字符串:
两者都可以使用文档字符串(docstrings)来描述其功能。
e) 装饰器:
两者都可以使用装饰器来修改或增强功能。
主要区别:
a) self 参数:
类方法的第一个参数通常是 self,代表类的实例。普通函数没有这个要求。
b) 访问范围:
类方法可以访问类的属性和其他方法,而普通函数不能直接访问类的内部结构。
c) 调用方式:
类方法通常通过类的实例调用,而普通函数直接调用。
d) 特殊方法:
类可以有特殊方法(如 __init__, __str__ 等),这在普通函数中不存在。
让我们通过一个例子来说明这些相似点和区别:
# 普通函数
def regular_function(x, y, *args, **kwargs):
"""这是一个普通函数的文档字符串"""
print(f"Regular function: x={x}, y={y}, args={args}, kwargs={kwargs}")
# 类定义
class ExampleClass:
def __init__(self, name):
self.name = name
def class_method(self, x, y, *args, **kwargs):
"""这是一个类方法的文档字符串"""
print(f"{self.name}: x={x}, y={y}, args={args}, kwargs={kwargs}")
@staticmethod
def static_method(x, y):
"""这是一个静态方法"""
print(f"Static method: x={x}, y={y}")
# 使用普通函数
regular_function(1, 2, 3, 4, a=5, b=6) # 输出:Regular function: x=1, y=2, args=(3, 4), kwargs={'a': 5, 'b': 6}
# 使用类方法
obj = ExampleClass("MyObject")
obj.class_method(1, 2, 3, 4, a=5, b=6) # 输出:MyObject: x=1, y=2, args=(3, 4), kwargs={'a': 5, 'b': 6}
# 使用静态方法
ExampleClass.static_method(1, 2) # 输出:Static method: x=1, y=2
这个例子展示了普通函数和类方法在定义和使用上的相似性,同时也突出了它们的一些区别,特别是 self 参数的使用和调用方式的不同。
总的来说,Python 的设计使得类方法在使用上感觉非常像普通函数,这是 Python 简洁和一致性设计哲学的体现。这种相似性使得从函数式编程转向面向对象编程变得相对容易。
下面介绍封装(Encapsulation)、继承(Inheritance)和多态(Polymorphism)等内容,很重要,但涉及内容广泛,在此仅作简要介绍,作为初学者可以先作为了解内容。
封装(Encapsulation)
封装是将数据(属性)和行为(方法)隐藏在类内部,只通过公开的方法访问。
私有属性和方法:通过在属性或方法名前加两个下划线 __ 实现。示例:
class Person:
def __init__(self, name, age):
self.__name = name # 私有属性
self.__age = age
def get_name(self):
return self.__name
def get_age(self):
return self.__age
person = Person("Alice", 30)
print(person.get_name()) # 输出: Alice
print(person.get_age()) # 输出: 30
# print(person.__name) # 会报错,无法直接访问私有属性
在 Python 中,高级封装可以属性装饰器(@property)和property()函数。
Python中的property:@property装饰器和 property() 函数,它们都是用来创建托管属性(managed properties)的机制,允许你对属性的获取、设置和删除操作进行精细控制。这两种方法本质上是实现相同功能(功能等价)的不同语法。这是一种更现代、更简洁的语法,通常在新代码中更常用。property()函数这是较早的语法,但仍然完全有效和有用。@property装饰器实际上是property()函数的语法糖。
选择哪种主要取决于个人偏好和具体的使用场景。
Python使用装饰器(@property)来实现getter和setter的功能。例如:
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
"""Getter for radius"""
return self._radius
@radius.setter
def radius(self, value):
"""Setter for radius"""
if value <= 0:
raise ValueError("半径必须为正数")
self._radius = value
@property
def area(self):
"""Getter for area"""
return 3.14 * self._radius ** 2
circle = Circle(5)
print(circle.radius) # 输出: 5
circle.radius = 10 # 使用setter
print(circle.radius) # 输出: 10
print(circle.area) # 输出: 约 314.0
# circle.radius = -1 # 会抛出 ValueError: 半径必须为正数
说明(解释):
a. @property装饰器:
将方法转换为只读属性
通常用作getter
b. @[属性名].setter装饰器:
定义属性的setter方法
允许设置属性值时进行验证或处理
c. 只读属性:
只定义@property而不定义setter,创建只读属性
property()函数
property() 函数是 Python 内置函数,用于创建和返回 property 对象。它提供了另一种方式来定义属性,而不是使用装饰器语法。
property() 函数的基本语法是:
property(fget=None, fset=None, fdel=None, doc=None)
其中:
fget:获取属性值的函数(getter)
fset:设置属性值的函数(setter)
fdel:删除属性的函数(可选)
doc:属性的文档字符串(可选)
例如(一个完整的、可运行的使用property() 函数示例):
class Circle:
def __init__(self, radius):
self._radius = radius
def get_radius(self):
print("Getting radius")
return self._radius
def set_radius(self, value):
print("Setting radius")
if value <= 0:
raise ValueError("半径必须为正数")
self._radius = value
def del_radius(self):
print("Deleting radius")
del self._radius
radius = property(get_radius, set_radius, del_radius, "圆的半径属性")
# 使用这个类
circle = Circle(5)
print(circle.radius) # 获取半径
circle.radius = 10 # 设置半径
print(circle.radius) # 再次获取半径
del circle.radius # 删除半径
# 查看属性的文档
print(Circle.radius.__doc__)
# 尝试设置无效值
try:
circle.radius = -1
except ValueError as e:
print(f"错误: {e}")
说明(解释):
我们定义了 get_radius、set_radius 和 del_radius 方法。
使用 property() 函数创建了 radius 属性,将这些方法关联起来。
当我们访问、修改或删除 radius 属性时,相应的方法被调用。
我们还可以访问属性的文档字符串。
当尝试设置无效值时,会引发 ValueError。
使用 property() 函数的优点是它提供了一种更灵活的方式来定义属性,特别是在需要动态创建属性或在运行时修改属性行为时。然而,对于大多数简单的情况,使用 @property 装饰器语法通常更为简洁和常见。
运行这个程序的输出是:
Getting radius
5
Setting radius
Getting radius
10
Deleting radius
圆的半径属性
Setting radius
错误: 半径必须为正数
继承(Inheritance)
继承是面向对象编程中的一个重要概念,它允许一个类(子类)继承另一个类(父类)的属性和方法。
定义子类的语法:
class SubClass(ParentClass):
# 子类定义
示例:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("Subclass must implement this method")
class Cat(Animal):
def speak(self):
return f"{self.name} says meow"
class Dog(Animal):
def speak(self):
return f"{self.name} says woof"
cat = Cat("Whiskers")
dog = Dog("Buddy")
print(cat.speak()) # 输出: Whiskers says meow
print(dog.speak()) # 输出: Buddy says woof
多重继承(Multiple Inheritance)
Python 支持多重继承,一个子类可以继承多个父类。
语法:
class SubClass(ParentClass1, ParentClass2):
# 子类定义
示例:
class Swimmable:
def swim(self):
return "I can swim"
class Flyable:
def fly(self):
return "I can fly"
class Duck(Swimmable, Flyable):
pass
duck = Duck()
print(duck.swim()) # 输出: I can swim
print(duck.fly()) # 输出: I can fly
多态(Polymorphism)
在 Python 中,多态(Polymorphism)是指不同的对象对同一操作的不同响应方式。这意味着,您可以用相同的方式处理不同类型的对象,而不需要了解它们具体的类型。多态通常通过继承与方法重写(override)来实现。例如:
class Shape:
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius ** 2
def print_area(shape):
print(f"The area is: {shape.area()}")
# 创建不同的形状
rect = Rectangle(5, 4)
circ = Circle(3)
# 用相同的函数处理不同的对象
print_area(rect) # 输出: The area is: 20
print_area(circ) # 输出: The area is: 28.26
说明,在这个例子中:
Shape 是一个基类,定义了 area 方法。
Rectangle 和 Circle 继承自 Shape,并重写了 area 方法。
print_area 函数接受一个 Shape 对象,并调用其 area 方法。
我们可以传入 Rectangle 或 Circle 的实例,print_area 函数会正确调用相应的 area 方法。
这个例子展示了如何通过继承和方法重写来实现多态,也展示了如何用相同的方式(调用 print_area 函数)处理不同类型的对象。
方法重写(Method Overriding)
子类可以重写父类的方法,提供自己的实现。
示例:
class Bird:
def speak(self):
return "Bird sound"
class Parrot(Bird):
def speak(self):
return "Parrot says squawk"
bird = Bird()
parrot = Parrot()
print(bird.speak()) # 输出: Bird sound
print(parrot.speak()) # 输出: Parrot says squawk
最后总结一下Python中的self的作用。
self小结:
Python中的self是一个在面向对象编程中非常重要的概念。self 是一个约定俗成的名称,通常用于代表一个类的实例。它作为实例方法的第一个参数来访问对象的属性和方法。 self 的作用:
1)用于访问和修改实例变量(也称为实例的属性)。
2)用于调用其他实例方法。
示例:
class Person:
def __init__(self, name, age):
self.name = name # 使用 self 设置实例变量
self.age = age
def introduce(self):
print(f"我是 {self.name},今年 {self.age} 岁") # 使用 self 访问实例变量
def have_birthday(self):
self.age += 1 # 使用 self 修改实例变量
self.introduce() # 使用 self 调用其他实例方法
# 创建实例
person = Person("Alice", 30)
person.introduce() # 输出:我是 Alice,今年 30 岁
person.have_birthday() # 输出:我是 Alice,今年 31 岁
关于self重要说明
self 必须作为第一个参数传递给实例方法,但在调用时不需要传递,Python 会自动处理。
self不是Python的关键字,而是个常用约定,但你可以使用其他名称,但是不建议这样做,以保持代码的可读性和一致性。
在__init__中,self指向新创建的实例。(__init__是一个特殊方法,用于初始化新创建的对象。)
访问实例变量时总是需要使用self前缀。
实例方法的定义需要包含self,在类方法 (使用@classmethod装饰器) 和静态方法 (使用@staticmethod装饰器) 中,self 不再使用,类方法使用cls作为第一个参数;静态方法不使用特殊的第一个参数。
在子类中,self仍然指向当前实例,即使方法是从父类继承的。
附录
Python面向对象程序设计 https://blog.csdn.net/cnds123/article/details/108354860
Python中的property介绍 https://blog.csdn.net/cnds123/article/details/129420059