前言
在python中,类是一种用于创建新类型对象的结构,它允许我们将数据和功能(属性和方法)封装到一个单独的逻辑单元中。类可以被看作是创建对象(实例)的蓝图或模板。类(Class)和对象(Object)是面向对象编程(OOP)的核心概念。
概念
类(Class)
类是一个用于创建对象的模板或蓝图。它定义了一组属性和方法,这些属性和方法将被它创建的对象所共享。类本质上是一种数据结构,它封装了数据以及操作这些数据的函数。
- 属性:类中定义的变量。它们代表了该类所有对象共有的数据。
- 方法:类中定义的函数。它们用于描述该类所有对象共有行为或功能。
通过定义一个类,你实际上是在定义一种新类型的数据结构,这种结构包含了特定类型、对象所具有的属性、可以对这些属性执行哪些操作。
对象(Object)
对象是根据类被创建出来的实例。每个对象都拥有独立于其他对象的属性值集合,但所有对象都共享同一个方法集合(即那些在其对应类中被定义过)。简单来说:
- 每个实例或对象都拥有从相同模板或蓝图(即:class)继承而来但独立存在、可以各自不同状态信息。
- 实例化是根据类创建对象的过程
类与对象之间关系
- 类型与实例关系:从概念上讲,你可以把“class”看作“type”,把由此“type”生成出来具体存在事物看作“instance”。例如,“Dog”可以是一个表示所有狗通性特点模板名字;而你家那条叫做"Rex"具体小狗则可视作根据"Dog"模板生成出来、带有自己特定状态信息如年龄、品种等信息记录项事物。
- 共享与唯一性原则:所有由同一个class生成出来instances会共享该class定义的代码逻辑项,不随各自instance状态变化而变化;同时每个instance也会拥有属于自己唯一且可能随时间改变状态信息记录项。
- 继承性原则: 类之间可以通过继承机制分享通用代码逻辑项,并允许子级别class在保持父级别已经提供功能基础上添加或修改部分以满足更加具体需求场景使用要求。
类定义
一个基本的类定义包括关键字class
,后面跟着类名和冒号。在其下方缩进的代码块中,可以定义属性(变量)和方法(函数)。
要使用一个类,你需要根据该类创建对象。每个对象都会有自己独立的属性集合。
class MyClass:
def __init__(self, value): # 特殊方法__init__用于初始化对象,构造器
self.my_attribute = value
def my_method(self):
return self.my_attribute * 2
# 实例化对象
obj = MyClass(99)
print(obj)
print(obj.my_method())
print(obj.my_attribute)
# 直接修改对象的属性
obj.my_attribute = 100
print(obj)
print(obj.my_method())
print(obj.my_attribute)
类的命名规范
-
驼峰命名法:Python中的类名通常使用驼峰命名法(CamelCase)。这意味着类名中每个单词的首字母都大写,而且单词之间不使用下划线分隔。例如,
MyClass
、DataProcessor
、CarEngine
。 -
描述性命名:类名应该简洁且富有描述性,清楚地表明类的用途或功能。例如,避免使用模糊的名称如
Foo
或Bar
,而应选择更具描述性的名称,如Customer
、EmailParser
。 -
避免缩写:尽量避免使用缩写,因为缩写可能会使代码的可读性降低。完整的单词能更好地表达类的意图和功能。
-
首字母大写:按照Python的约定,类名的首字母应该大写,即使它是一个缩写。例如,
URLParser
而不是urlParser
。 -
避免使用内置类型名称:避免使用像
str
、list
或dict
这样的Python内置类型名称作为类名,以免造成混淆。 -
命名长度:虽然没有硬性的长度限制,但一般推荐类名不要过长,以保持代码的清晰和易于维护。
特殊方法
Python提供了多个特殊方法(也称为魔术方法),它们由双下划线包围。这些特殊方法提供了与Python语言功能集成的方式。
初始化和构造
-
__init__(self, [...])
: 对象初始化方法,在创建新实例后被调用。 -
__new__(cls, [...])
: 实际的对象构造函数,是一个静态方法,在__init__
之前被调用。
class TestClassMethod:
# 定义变量,如果不赋值则必须指定类型
# 但是类型注解 value_a: int 表示期望 value_a 是一个整数类型,但它并不会阻止你将其他类型赋给该属性。
value_a: int
value_b = "value_b"
# 初始化方法
def __init__(self, init_value: int): # 类型注解本身并不会强制变量类型,在运行时仍然可以给带有类型注解的变量赋予任何类型的值。
print(f'初始化方法调用... init_value : {init_value}')
# 如果不指定 self. 则为局部变量,无法在这里赋值
# value_a = init_value
self.value_a = init_value
# 实际的对象构造函数,是一个静态方法,在__init__之前被调用。
# 在生产环境编写代码时通常不需要自己手动重写 __new__ 方法;除非你需要控制对象创建过程。对于大多数情况,默认行为已经足够。
@staticmethod
# 名为 __new__ 的实例方法并非特殊方法。特殊方法 __new__ 应当被定义为静态方法(使用装饰器 @staticmethod),或者更常见地作为类方法(使用装饰器 @classmethod)。
def __new__(cls, *args, **kwargs):
print(f'对象构造函数调用... cls : {cls}')
return super(TestClassMethod, cls).__new__(cls)
method = TestClassMethod(1)
print(method.value_a)
# 虽然类中定义了类型为int,但是照常可以赋值字符串
method.value_a = 'aa'
print(method.value_a)
print(method.value_b)
print(dir(method))
表示和格式化
-
__repr__(self)
: 定义对象的“官方”字符串表示。 -
__str__(self)
: 定义对象的“非正式”或可打印字符串表示。 -
__format__(self, format_spec)
: 定义当使用format()
函数时对象的格式。
__repr__(self)
- 目的:创建一个代表对象的官方字符串表示形式。
- 调用时机:当你调用内置函数
repr()
时;当没有定义__str__
时,在使用print函数打印对象或者在解释器中直接输入变量名查看对象时;还有就是在调试过程中经常会看到这个表示。 - 特点:应该尽可能返回一个明确且不模糊的字符串表示形式,最好能够通过这个字符串来重新创建该对象。因此,通常返回值会看起来像是一个有效的Python表达式。
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point({self.x}, {self.y})"
__str__(self)
- 目的:创建一个对用户友好(非技术用户)的字符串表示形式。
- 调用时机:当你使用内置函数
str()
或者使用print函数打印一个对象时。 - 特点:返回值应该是可读性强、信息友好型描述。
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Point located at ({self.x}, {self.y})"
区别
虽然两者都提供了关于对象信息展示方式,但主要区别在于目标受众和它们各自所承担角色:
-
__repr__
: 面向开发者和调试器。其目标是明确无误地表达出如何构造出相同数据内容的新实例。 -
__str__
: 面向最终用户。其目标是提供友好易懂且便于阅读理解信息。
如果只实现了其中之一,则未实现者将默认使用已实现者进行替代。如果都没有实现,则会默认使用继承自object类中相应方法。
建议至少定义一个__repr__()
方法,在大多数情况下它将足够用来输出有意义并可辨识资源信息,并作为后备方案以防未定义 __str__()
.
运算符重载(算术、比较运算符)
-
__add__(self, other)
: 加法运算符+
。 -
__sub__(self, other)
: 减法运算符-
。 -
__mul__(self, other)
: 乘法运算符*
。 -
__truediv__(self, other)
: 真除法运算符/
. -
__floordiv__(self, other)
: 地板除法运算符//
. -
__mod__(self, other)
: 取模(余数)操作%
. -
__eq__(self, other)
: 等于操作=
. -
__ne__(self, other)
: 不等于操作不等于操作.
class TestClassMethod:
value = 100
def __add__(self, other):
return self.value + other
def __sub__(self, other):
return self.value - other
def __mul__(self, other):
return self.value * other
def __truediv__(self, other):
return self.value / other
def __floordiv__(self, other):
return self.value // other
def __mod__(self, other):
return self.value % other
def __eq__(self, other):
return self.value == other
def __ne__(self, other):
return self.value != other
# 大于
def __gt__(self, other):
return self.value > other
# 大于等于
def __ge__(self, other):
return self.value >= other
# 小于
def __lt__(self, other):
return self.value < other
# 小于等于
def __le__(self, other):
return self.value <= other
method = TestClassMethod()
print(method + 100)
print(method - 100)
print(method * 100)
print(method / 100)
print(method // 100)
print(method % 100)
print(method == 100)
print(method != 100)
print(method > 100)
print(method >= 100)
print(method < 100)
print(method <= 100)
属性访问
属性访问相关的特殊方法可以帮助我们自定义属性的获取、设置和删除行为:
class TestClassMethod:
def __getattr__(self, name):
# 当尝试获取一个不存在的属性时调用
print(f"【报错】!!尝试获取不存在的属性: {name} ")
return '不存在的属性!'
def __setattr__(self, name, value):
# 当对属性赋值时调用
print(f"调用赋值方法 {name} {value}")
# self.name = value 不能这样赋值,会导致递归调用
super().__setattr__(name, value)
def __delattr__(self, name):
# 当删除一个属性时调用
print(f"调用删除属性的方法 {name} ")
super().__delattr__(name)
method = TestClassMethod()
method.value = 1
print(method.value)
print(method.value2)
del method.value
类属性
在Python中,类的属性可以分为两种:类属性和实例属性。
类属性
-
类属性访问
- 类属性可以通过类名直接访问。
- 实例也可以访问类属性,但是如果实例具有与类属性同名的实例属性,则会优先访问实例属性。
-
修改类属性
- 通过类名修改类属性会影响到所有访问该属性的实例,因为它们共享同一个类变量。
- 如果通过实例修改看似“修改”了一个类变量(即
instance.attribute = value
),其实是在该实例中创建了一个同名的实例变量,并不会影响到其他实例或者是该变量在原来的那个class中。
类属性是属于类本身的变量,它被所有该类的实例共享。这意味着如果你修改了一个类的类属性,这个改变会影响到所有该类的实例。
class MyClass:
class_attribute = 0
# 修改类属性将影响所有实例
MyClass.class_attribute = 1
instance1 = MyClass()
instance2 = MyClass()
print(instance1.class_attribute) # 输出: 1
print(instance2.class_attribute) # 输出: 1
实例(对象)属性
实例属性是属于特定对象(即某个具体实例)的变量。每个对象都有自己独立的一套实例属性,修改一个对象的实例属性不会影响其他对象。
-
对象属性访问
- 对象属性只能通过实例对象访问。它们是绑定到特定实例的,因此每个实例都有自己独立的对象属性副本。
-
修改对象属性
- 对象属性的修改仅限于该特定实例。修改实例的属性不会影响到类的其他实例。
- 如果尝试通过实例修改一个不存在的属性,Python会自动在该实例上创建这个属性。
class MyClass:
def __init__(self, value):
self.instance_attribute = value
# 每个对象都有自己独立的 instance_attribute 属性。
instance1 = MyClass(10)
instance2 = MyClass(20)
print(instance1.instance_attribute) # 输出: 10
print(instance2.instance_attribute) # 输出: 20
# 修改一个对象的 instance_attribute 不会影响另一个。
instance1.instance_attribute = 30
print(instance1.instance_attribute) # 输出: 30
print(instance2.instance_attribute) # 输出: 20
属性引用和修改规则
- 类变量属于整个类,所有基于此类创建出来的对象共享这个变量。
- 如果需要对所有对象都生效地更改某个值,请操作这个值所属类上定义好了的那个变量。
- 对象属性是绑定到类的实例上的,因此它们对于每个实例来说都是唯一的。
- 修改一个实例的属性不会影响到其他实例。
- 通过实例可以访问类属性,但是如果实例有一个与类属性同名的对象属性,则访问的是对象属性,这体现了Python中的“名称遮蔽”特性。(在Python中,“名称遮蔽”(Name Shadowing)是指子作用域中的名称覆盖了外部作用域中相同名称的变量。这种特性在类属性与实例属性之间的交互中尤为常见。)
class TestClassModifyAttr:
attribute = 0
def __init__(self, value):
self.attribute = value
@property
def test_method(self):
"""
当你尝试访问或者修改一个对象(即某个具体实例)
上不存在但在其对应类型定义中存在作为`@property`装饰器修饰过得方法时,
Python解释器将调用相应方法来处理此次访问或者修改行为。
"""
print("调用 test_method")
a = TestClassModifyAttr(999)
print(a.attribute)
print(a.test_method)
# 如果你尝试访问或者修改一个不存在于任何地方(既不是该类型定义中也不是其基类型定义中)
# 且未通过`@property`装饰器修饰过得方法时,
# 则解释器将调用 `__getattr__()` 或 `__setattr__()` 特殊方法来处理此次访问或者修改行为。
print(a.test_method2)