Python 类与对象基础 + 函数编写详解
一、类与对象的基本概念
-
类 (Class)
-
蓝图/模板,描述一类事物的 属性 和 行为
-
例如:
汽车类
可以有属性(颜色、品牌)和方法(启动、刹车)
-
-
对象 (Object)
-
类的具体实例
-
例如:
我的车 = 汽车类("红色", "特斯拉")
-
二、类的定义与使用
1. 基本语法
class 类名: def __init__(self, 参数1, 参数2): # 构造函数 self.属性1 = 参数1 self.属性2 = 参数2 def 方法名(self, 参数): # 实例方法 # 方法体 return 结果
2. 示例代码
#!/usr/bin/python3 #类定义 class people: #定义基本属性 name = '' age = 0 #定义私有属性,私有属性在类外部无法直接进行访问 __weight = 0 #定义构造方法 def __init__(self,n,a,w): self.name = n self.age = a self.__weight = w def speak(self): print("%s 说: 我 %d 岁。" %(self.name,self.age)) # 实例化类 p = people('runoob',10,30) p.speak() #runoob 说: 我 10 岁。
3. 关键点
-
self
:代表类的实例,必须作为方法的第一个参数 -
__init__
:构造函数,在创建对象时自动调用 -
属性访问:
对象名.属性
(如my_dog.name
) -
方法调用:
对象名.方法()
(如my_dog.bark()
)
三、函数的定义与使用
1. 基本语法
def 函数名(参数1, 参数2=默认值): """函数文档字符串(可选)""" # 函数体 return 返回值 # 可选
2. 示例代码
def add(a, b=1): # b有默认值 """计算两数之和""" return a + b result = add(3, 5) # 调用 print(result) # 输出:8
3. 函数类型
类型 | 示例 | 特点 |
---|---|---|
无参函数 | def greet(): | 直接调用 greet() |
有参函数 | def add(a, b): | 需传参 add(3, 5) |
默认参数 | def log(msg, level=1): | log("Error") 或 log("Error", 2) |
返回函数 | def sqrt(x): return x**0.5 | 必须用 result = sqrt(4) 接收返回值 |
四、类 vs 函数的区别
特性 | 类 (Class) | 函数 (Function) |
---|---|---|
目的 | 封装数据+行为 | 执行特定任务 |
调用方式 | 先实例化对象,再调方法 | 直接调用 |
状态保存 | 通过属性维护状态 | 无状态(除非用全局变量) |
示例 | car.drive() | calculate_sum(3, 5) |
五、实际应用场景
-
适合用类的情况
-
需要维护状态(如游戏角色、银行账户)
-
多个方法操作同一组数据
-
-
适合用函数的情况
-
独立功能(如数学计算、字符串处理)
-
无状态操作(如
len()
,max()
)
-
继承 和 魔法方法(如__str__
)
一、继承(Inheritance)
1. 基本概念
-
继承:子类(派生类 DerivedClassName)会继承父类(基类 BaseClassName)的属性和方法。
-
语法:
class 子类名(父类名):
-
作用:代码复用 + 逻辑分层
2. 示例代码
单继承
class Animal: # 父类 def __init__(self, name): self.name = name def speak(self): print(f"{self.name} 发出声音") class Dog(Animal): # 子类继承Animal def __init__(self, name, breed): super().__init__(name) # 调用父类的__init__ self.breed = breed # 子类新增属性 # 重写父类方法 def speak(self): print(f"{self.name}(品种:{self.breed})汪汪叫!") # 使用 dog = Dog("阿黄", "金毛") dog.speak() # 输出:阿黄(品种:金毛)汪汪叫! #!/usr/bin/python3 #类定义 class people: #定义基本属性 name = '' age = 0 #定义私有属性,私有属性在类外部无法直接进行访问 __weight = 0 #定义构造方法 def __init__(self,n,a,w): self.name = n self.age = a self.__weight = w def speak(self): print("%s 说: 我 %d 岁。" %(self.name,self.age)) #单继承示例 class student(people): grade = '' def __init__(self,n,a,w,g): #调用父类的构函 people.__init__(self,n,a,w) self.grade = g #覆写父类的方法 def speak(self): print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade)) s = student('ken',10,60,3) s.speak()#ken 说: 我 10 岁了,我在读 3 年级
多继承
#!/usr/bin/python3 #类定义 class people: #定义基本属性 name = '' age = 0 #定义私有属性,私有属性在类外部无法直接进行访问 __weight = 0 #定义构造方法 def __init__(self,n,a,w): self.name = n self.age = a self.__weight = w def speak(self): print("%s 说: 我 %d 岁。" %(self.name,self.age)) #单继承示例 class student(people): grade = '' def __init__(self,n,a,w,g): #调用父类的构函 people.__init__(self,n,a,w) self.grade = g #覆写父类的方法 def speak(self): print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade)) #另一个类,多继承之前的准备 class speaker(): topic = '' name = '' def __init__(self,n,t): self.name = n self.topic = t def speak(self): print("我叫 %s,我是一个演说家,我演讲的主题是 %s"%(self.name,self.topic)) #多继承 class sample(speaker,student): a ='' def __init__(self,n,a,w,g,t): student.__init__(self,n,a,w,g) speaker.__init__(self,n,t) test = sample("Tim",25,80,4,"Python") test.speak() #方法名同,默认调用的是在括号中参数位置排前父类的方法 #我叫 Tim,我是一个演说家,我演讲的主题是 Python
3. 关键点
-
super()
:调用父类的方法(如super().__init__()
,super(Child,c).myMethod()
) -
方法重写:子类可覆盖父类的方法(如
speak()
)#!/usr/bin/python3 class Parent: # 定义父类 def myMethod(self): print ('调用父类方法') class Child(Parent): # 定义子类 def myMethod(self): print ('调用子类方法') c = Child() # 子类实例 c.myMethod() # 子类调用重写方法 super(Child,c).myMethod() #用子类对象调用父类已被覆盖的方法 结果: 调用子类方法 调用父类方法
-
多继承:
class Child(Parent1, Parent2):
(谨慎使用) #方法名同,默认调用的是在括号中参数位置排前父类的方法
4. 类属性与方法
类的私有属性
__private_attrs:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时 self.__private_attrs。
#!/usr/bin/python3 class JustCounter: __secretCount = 0 # 私有变量 publicCount = 0 # 公开变量 def count(self): self.__secretCount += 1 self.publicCount += 1 print (self.__secretCount) counter = JustCounter() counter.count() counter.count() print (counter.publicCount) print (counter.__secretCount) # 报错,实例不能访问私有变量 1 2 2 Traceback (most recent call last): File "test.py", line 16, in <module> print (counter.__secretCount) # 报错,实例不能访问私有变量 AttributeError: 'JustCounter' object has no attribute '__secretCount'
类的方法
在类的内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self,且为第一个参数,self 代表的是类的实例。
self 的名字并不是规定死的,也可以使用 this,但是最好还是按照约定使用 self。
类的私有方法
__private_method:两个下划线开头,声明该方法为私有方法,只能在类的内部调用 ,不能在类的外部调用。self.__private_methods。
#!/usr/bin/python3 class Site: def __init__(self, name, url): self.name = name # public self.__url = url # private def who(self): print('name : ', self.name) print('url : ', self.__url) def __foo(self): # 私有方法 print('这是私有方法') def foo(self): # 公共方法 print('这是公共方法') self.__foo() x = Site('菜鸟教程', 'www.runoob.com') x.who() # 正常输出 x.foo() # 正常输出 x.__foo() # 报错
类的专有方法:
-
init : 构造函数,在生成对象时调用
-
del : 析构函数,释放对象时使用
-
repr : 打印,转换
-
setitem : 按照索引赋值
-
getitem: 按照索引获取值
-
len: 获得长度
-
cmp: 比较运算
-
call: 函数调用
-
add: 加运算
-
sub: 减运算
-
mul: 乘运算
-
truediv: 除运算
-
mod: 求余运算
-
pow: 乘方
运算符重载
Python同样支持运算符重载,我们可以对类的专有方法进行重载
#!/usr/bin/python3 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(2,10) v2 = Vector(5,-2) print (v1 + v2)
二、魔法方法(Magic Methods)
1. 基本概念
-
以双下划线
__
包围的特殊方法(如__str__
) -
Python 自动调用它们实现特定功能(如打印对象、运算符重载)
2. 常用魔法方法
方法名 | 作用 | 触发场景 |
---|---|---|
__init__ | 构造函数 | obj = Class() |
__str__ | 定义对象的打印格式 | print(obj) |
__len__ | 定义对象的长度 | len(obj) |
__add__ | 重载 + 运算符 | obj1 + obj2 |
3. 示例代码
class Book: def __init__(self, title, author): self.title = title self.author = author # 控制打印对象的输出 def __str__(self): return f"《{self.title}》 - {self.author}" # 重载 + 运算符(合并书名) def __add__(self, other): return Book(f"{self.title}&{other.title}", "合辑") # 使用 book1 = Book("Python入门", "张三") book2 = Book("算法导论", "李四") print(book1) # 输出:《Python入门》 - 张三 combined = book1 + book2 print(combined) # 输出:《Python入门&算法导论》 - 合辑
4. 为什么用 __str__
?
-
直接打印对象时,默认输出
<__main__.Book object at 0x...>
-
定义
__str__
后,print(obj)
会输出更友好的信息
三、继承 + 魔法方法综合案例
class Person: def __init__(self, name, age): self.name = name self.age = age def __str__(self): return f"{self.name} ({self.age}岁)" class Student(Person): def __init__(self, name, age, student_id): super().__init__(name, age) self.student_id = student_id # 重写__str__,追加学号信息 def __str__(self): return f"{super().__str__()} | 学号:{self.student_id}" # 使用 stu = Student("小明", 18, "S12345") print(stu) # 输出:小明 (18岁) | 学号:S12345
四、练习题
-
继承练习
-
创建
Shape
父类(属性:颜色;方法:area()
) -
子类
Circle
和Rectangle
分别实现自己的area()
计算逻辑
-
-
魔法方法练习
-
为
Circle
类添加__eq__
方法,实现两个圆比较半径是否相等 -
示例:
print(circle1 == circle2)
-
五、总结
技术 | 核心要点 | 典型应用场景 |
---|---|---|
继承 | super() 调用父类,方法重写 | 代码复用,扩展功能 |
魔法方法 | 双下划线命名,自动触发(如 __str__ ) | 定制对象行为/运算符 |
六、 扩展思考
如果父类的 __str__
不存在怎么办?
-
Python 会调用
object.__str__
(默认返回类名和内存地址) -
因此建议所有基类都明确定义
__str__
命名空间
-
内置名称(built-in names), Python 语言内置的名称,比如函数名 abs、char 和异常名称 BaseException、Exception 等等。
-
全局名称(global names),模块中定义的名称,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量。
-
局部名称(local names),函数中定义的名称,记录了函数的变量,包括函数的参数和局部定义的变量。(类中定义的也是)
命名空间查找顺序:
假设我们要使用变量 runoob,则 Python 的查找顺序为:局部的命名空间 -> 全局命名空间 -> 内置命名空间。
# var1 是全局名称 var1 = 5 def some_func(): # var2 是局部名称 var2 = 6 def some_inner_func(): # var3 是内嵌的局部名称 var3 = 7
作用域
作用域就是一个 Python 程序可以直接访问命名空间的正文区域。
在一个 python 程序中,直接访问一个变量,会从内到外依次访问所有的作用域直到找到,否则会报未定义的错误。
Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。
变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。
Python 的作用域一共有 4 种,分别是:
有四种作用域:
-
L(Local):最内层,包含局部变量,比如一个函数/方法内部。
-
E(Enclosing):包含了非局部(non-local)也非全局(non-global)的变量。比如两个嵌套函数,一个函数(或类) A 里面又包含了一个函数 B ,那么对于 B 中的名称来说 A 中的作用域就为 nonlocal。
-
G(Global):当前脚本的最外层,比如当前模块的全局变量。
-
B(Built-in): 包含了内建的变量/关键字等,最后被搜索。
LEGB 规则(Local, Enclosing, Global, Built-in):Python 查找变量时的顺序是: L –> E –> G –> B。
-
Local:当前函数的局部作用域。
-
Enclosing:包含当前函数的外部函数的作用域(如果有嵌套函数)。
-
Global:当前模块的全局作用域。
-
Built-in:Python 内置的作用域。
在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内置中找。
g_count = 0 # 全局作用域global def outer(): o_count = 1 # 闭包函数外的函数中, def inner(): i_count = 2 # 局部作用域local
Python 中只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的,也就是说这些语句内定义的变量,外部也可以访问。
全局变量和局部变量
定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域。
局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。
在函数内部声明的变量只在函数内部的作用域中有效,调用函数时,这些内部变量会被加入到函数内部的作用域中,并且不会影响到函数外部的同名变量.
局部变量优先级高于全局变量,因此如果局部变量和全局变量同名,函数内部会使用局部变量。
total = 0 # 这是一个全局变量 # 可写函数说明 def sum(arg1, arg2): # 返回2个参数的和." total = arg1 + arg2 # total在这里是局部变量. print("函数内是局部变量 : ", total) return total # 调用sum函数 sum(10, 20) print("函数外是全局变量 : ", total) #函数内是局部变量 : 30 #函数外是全局变量 : 0 def my_function(): x = 5 # 局部变量 print(x) # 访问局部变量 x my_function() # 输出 5 print(x) # 报错: NameError: name 'x' is not defined
global 和 nonlocal关键字
当内部作用域想修改外部作用域的变量时,就要用到 global 和 nonlocal 关键字了。
以下实例修改全局变量 num:
#!/usr/bin/python3 num = 1 def fun1(): global num # 需要使用 global 关键字声明,内部想修改外部 print(num) num = 123 print(num) fun1() print(num) #1 #123 #123
如果要修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量则需要 nonlocal 关键字了
#!/usr/bin/python3 def outer(): num = 10 def inner(): nonlocal num # nonlocal关键字声明 num = 100 print(num) inner() print(num) outer() #100 #100
特殊情况:
#!/usr/bin/python3 a = 10 def test(): a = a + 1 print(a) test() #以上程序执行,报错信息如下 Traceback (most recent call last): File "test.py", line 7, in <module> test() File "test.py", line 5, in test a = a + 1 UnboundLocalError: local variable 'a' referenced before assignment #错误信息为局部作用域引用错误,因为 test 函数中的 a 使用的是局部,未定义,无法修改。 #修改 a 为全局变量: #!/usr/bin/python3 a = 10 def test(): global a a = a + 1 print(a) test() #11 #也可以通过函数参数传递: #!/usr/bin/python3 a = 10 def test(a): a = a + 1 print(a) test(a) #11
总结
-
全局变量在函数外部定义,可以在整个文件中访问。
-
局部变量在函数内部定义,只能在函数内访问。
-
使用
global
可以在函数中修改全局变量。 -
使用
nonlocal
可以在嵌套函数中修改外部函数的变量。