面向对象是一种编程范式,它将程序中的数据和操作数据的方法组合成对象,并通过对象之间的交互来实现程序的功能。在面向对象编程中,对象是程序的基本单元,每个对象都有自己的状态和行为。状态是对象的属性,行为是对象的方法。通过封装、继承和多态等特性,面向对象编程可以更好地管理和维护程序的复杂性,提高代码的可重用性和可维护性。
在Python中,一切皆为对象,包括数字、字符串、函数等,都是对象,因此Python是一种完全面向对象的编程语言。
一种数据类型就是类。例如整数,浮点数,字符串。
Python 中通过关键字 class
可以定义一个自定义数据类型,基本语法如下:
class 类名:
属性
方法
注意:Python 中类名规则同变量,一般使用 大驼峰 表示;
创建一个 Test
类用于表示测试中的一个点;
class Test:
"""
表示测试中的一个点
"""
print(Test)
运行结果: # 输出类本身
某种数据类型的一个具体的数据称为这个类的一个对象或者实例。通过类创建对象叫做实例化。
所谓的面向对象,就是把一些数据抽象成类的思想。
除了基本数据类型实例化的过程中用到的特殊的语法规范外,所有自定义类型进行实例化都是通过调用类名来实现的,非常简单。
语法如下:
类名(参数)
看起来和调用函数一样。
给上面创建的 Test
类创建一个实例。
class Test:
"""
表示测试中的一个点
"""
test1 = Test() # test1是Test类的对象或者实例;
print(test1)
运行结果: <__main__.Test object at 0x7fd01c1d4d50>
类和对象的特征数据称为属性。
类的属性是用来表明这个类是什么的。
类的属性分为实例属性与类属性两种。
实例属性用于区分不同的实例;
类属性是每个实例的共有属性。
区别:实例属性每个实例都各自拥有,相互独立;而类属性有且只有一份,是共有的属性。
直接在类中定义的变量(与class语句只有一个缩进),就是类属性。
给 Test
类创建一个 name
属性用来表示名称。
class Test:
"""
表示测试中的一个点
"""
name = '测试'
类属性可以直接通过类名和对象以句点法访问。
语法格式如下:
类名.类属性名
对象.类属性名
案例
class Test:
"""
表示测试中的一个点
"""
name = '测试'
print(Test.name) # 使用类名.类属性名
运行结果: 测试
test1 = Test() # 设置对象名
print(test1.name) # 使用对象名.类属性名
运行结果: 测试
print(test1.name1) # 使用对象名.未知类属性名
注意:如果不存在属性则抛出AttributeError的异常
对象的特征数据称为对象属性。
通过句点法对象.对象属性
以赋值的方式可以直接定义对象属性。
测试中的每个点都有 x坐标 和 y坐标,通过类 Test
创建一个对象表示点(x=1,y=2)
class Test:
"""
表示测试中的一个点
"""
name = '测试'
test1 = Test() # 设置对象名
test1.x = 1 # 为对象设置对象属性 x
test1.y = 2 # 为对象设置对象属性 y
print(test1.x) # 使用 对象.对象属性名 x
运行结果: 1
print(test1.y) # 使用 对象.对象属性名 y
运行结果: 2
访问对象属性时,首先会检查对象是否拥有此属性,如果没有则去创建对象的类中查找有没有同名的类属性,如果有则返回,如果都找不到则抛出AttributeError的异常
定义在类中的函数称为方法。
通过调用的方式的不同,分为 对象方法、类方法、静态方法、魔术方法。
定义在类中的普通方法,一般通过对象调用称为对象方法。
为了讲清楚对象方法的定义和调用,我们先看下面的案例。
定义函数 my_test
, 它接收一个 Test 对象,然后打印这个点的 x,y坐标。
class Test:
"""
表示测试中的一个点
"""
name = '测试'
def my_test(test1): # 这个是对象方法
print('x坐标是{},y坐标是{}'.format(test1.x,test1.y))
对象方法向属性一样,可以通过句点法进行调用。
类名.方法名(参数)
对象.方法名(参数)
通过类名调用方法时,和普通函数没有区别;
因此,定义对象方法会习惯性的把第一个形参定义为self,表示调用对象本身;
class Test:
"""
表示测试中的一个点
"""
name = '测试'
def my_test(self): # 第一个形参定义为self,表示调用对象本身;
print('x坐标是{},y坐标是{}'.format(test1.x,test1.y))
test1 = Test() # 设置对象名
test1.x = 1 # 为对象设置对象属性 x
test1.y = 2 # 为对象设置对象属性 y
test1.my_test() # 使用 对象.对象属性名
Test.my_test(test1) # 使用 类名.对象方法名
在类中通过装饰器 @classmethod 可以把一个方法变成类方法。
一个类方法把类自己作为第一个实参,就像一个实例方法把实例自己作为第一个实参。
定义一个类方法 base_test
用来返回坐标原点。
class Test:
"""
表示测试中的一个点
"""
name = '测试'
def my_test(self):
"""
对象方法
:return:
"""
print('x坐标是{},y坐标是{}'.format(test1.x,test1.y))
@classmethod
def base_test(cls):
"""
类方法
:return:
"""
bt = cls()
cls.x = 0
cls.y = 0
return bt
test1 = Test() # 设置对象名
test1.base_test().my_test() # 通过 对象.类方法名.对象方法 全链路调用
Test.base_test().my_test() # 通过 类名.类方法名.对象方法 全链路调用
运行结果: x坐标是0,y坐标是0
class Circle():
pi = 3.14
@classmethod
def pi0(cls):
return cls.pi
def pi1(self):
return self.pi
print(Circle.pi0()) # 没有实例化 能直接访问pi() 方法
print('--' * 10)
circle1 = Circle()
print(circle1.pi0()) # 也可以通过实例访问pi()方法
print('--' * 10)
print(circle1.pi1())
通过类本身或者是该类的实例都可以调用类方法。
注意:实例属性访问优先级比类属性高,所以我们访问时优先访问实例属性,它将屏蔽掉对类属性的访问。
在类中可以定义一些特殊的方法用来实现特殊的功能,也称为魔术方法。
__init__
又叫构造方法,初始化方法,在调用类名实例化对象时,构造方法会被调用,类名括号()
后的参数会传递给构造方法,对象属性一般在这个方法中定义。
构造方法用于创建对象时使用,每当创建一个类的实例对象时,Python 解释器都会自动调用它。 语法格式如下:
def __init__(self,...):
代码块
上面案例中的 Test
类实例化后,需要手动创建对象属性x
和y
,这显然容易出错和不规范,正确的做法应该是在构造方法中定义属性x
和y;
class Test:
"""
表示测试中的一个点
"""
name = '测试'
def __init__(self,x,y): # 初始化一个属性x,y(不要忘记self参数,他是类下面所有方法必须的参数)
self.x = x
self.y = y
def my_test(self):
"""
对象方法
:return:
"""
print('x坐标是{},y坐标是{}'.format(self.x,self.y))
@classmethod
def base_test(cls):
"""
类方法
:return:
"""
bt = cls(0,0)
return bt
test1 = Test(1,3) # 设置对象名
test1.my_test()
test2 = Test(x=3,y=1)
test2.my_test()
运行结果:
x坐标是1,y坐标是3
x坐标是3,y坐标是1
面试喜欢问的问题:创建类时,类方法中的 self 是什么?
self 代表类的实例,是通过类创建的实例 (注意,在定义类时这个实例我们还没有创建,它表示的我们使用类时创建的那个实例)
在类中通过装饰器 @staticmethod 可以把一个方法变静态方法。
静态方法不会接收隐式的第一个参数,它和普通的函数一样,只是被封装到类中。
通过类和对象都可以调用。
在 Test 类中定义一个静态方法,用来计算两个数的和。
class Test:
"""
表示测试中的一个点
"""
name = '测试'
def __init__(self,x,y):
"""
初始化
:param x:
:param y:
"""
self.x = x
self.y = y
def my_test(self):
"""
对象方法
:return:
"""
print('x坐标是{},y坐标是{}'.format(self.x,self.y))
@classmethod
def base_test(cls):
"""
类方法
:return:
"""
bt = cls(0,0)
return bt
@staticmethod
def st_test(x,y):
"""
静态方法
:param x:
:param y:
:return:
"""
return x + y
print(Test.st_test(3,5)) # 通过 类.静态方法名调用
运行结果: 8
t = Test(1,4)
print(t.st_test(x=3,y=10)) # 通过 对象.静态方法名调用
运行结果: 13
构造方法:构造方法是一种特殊的方法,它在创建类的实例对象时被调用。在Python中,构造方法是通过__init__()方法来实现的。它用于初始化对象的属性,通常会在创建对象时自动调用。
普通方法:普通方法是类中默认的方法类型,它需要一个实例化对象来调用,并且第一个参数必须是self,表示实例化对象本身。
类方法:类方法是通过@classmethod装饰器修饰的方法,它不需要实例化对象即可调用,并且第一个参数是cls,表示类本身。类方法可以访问类属性和类方法。
静态方法:静态方法是通过@staticmethod装饰器修饰的方法,它不需要实例化对象即可调用,也不需要cls参数,因为它既不能访问实例属性和方法,也不能访问类属性和方法。静态方法通常用于工具函数或者辅助函数的实现。
总结:构造方法是用于初始化对象的属性的特殊方法,普通方法需要实例化对象调用,类方法和静态方法都不需要实例化对象即可调用,类方法需要传入cls参数,可以访问类属性和类方法,静态方法不能访问实例属性和方法,也不能访问类属性和方法。
当定义一个类时,可以从现有的类继承,新的类称为子类(Subclass),被继承的类称为基类,父类或超类(Base class,Super class).
子类可以继承父类的属性和方法。
假如需要定义几个类,而类与类之间有一些公共的属性和方法,这时我就可以把相同的属性和方法作为基类的成员,而特殊的方法及属性则在本类中定义。这样子类只需要继承基类(父类),子类就可以访问到基类(父类)的属性和方法了,它提高了代码的可扩展性和重用行。
创建一个类用来表示继承 Test 类。
class Test:
"""
表示测试中的一个点
"""
name = '测试'
def __init__(self,x,y):
"""
初始化
:param x:
:param y:
"""
self.x = x
self.y = y
def my_test(self):
"""
对象方法
:return:
"""
print('x坐标是{},y坐标是{}'.format(self.x,self.y))
@classmethod
def base_test(cls):
"""
类方法
:return:
"""
bt = cls(0,0)
return bt
@staticmethod
def st_test(x,y):
"""
静态方法
:param x:
:param y:
:return:
"""
return x + y
class Test1():
"""
未继承 Test 类
"""
print(dir(Test1))
运行结果: ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
class Test:
"""
表示测试中的一个点
"""
name = '测试'
def __init__(self,x,y):
"""
初始化
:param x:
:param y:
"""
self.x = x
self.y = y
def my_test(self):
"""
对象方法
:return:
"""
print('x坐标是{},y坐标是{}'.format(self.x,self.y))
@classmethod
def base_test(cls):
"""
类方法
:return:
"""
bt = cls(0,0)
return bt
@staticmethod
def st_test(x,y):
"""
静态方法
:param x:
:param y:
:return:
"""
return x + y
class Test1(Test):
"""
继承 Test 类
"""
print(dir(Test1))
Test.base_test()
t = Test(1,3)
br = t.base_test()
运行结果: ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'base_test', 'my_test', 'name', 'st_test']
在上面的案例中 Test1 类继承了 Test 类。
对于 Test1 来说 Test 是它的父类,对于 Test 类来说 Test1 是他的子类。
虽然在 Test1 类中没有定义任何的属性和方法,但它自动继承了父类 Test 的属性和方法。
class Test():
pass
class Test1(Test):
pass
T = Test()
T1 = Test1()
子类与父类的关系是 “is” 的关系,如上 Test1 继承于 Test 类,我们可以说:
“T”是 Test 类的实例,但“T”不是 Test1 类的实例。
“T1”是 Test 类的实例,“T1”也是 Test1 类的实例。
后续写
后续写
Python 类中如果有属性不希望被外部访问,我们可以在属性命名时以双下划线开头( __xxx__ ),那么该属性就不能使用原变量名访问,使得该属性变为本类私有的(伪私有)。
#错误实例
class Test:
"""
表示测试中的一个点
"""
name = '测试'
def __base_test(self):
"""
私有方法
:return:
"""
print('这是私有化方法')
Test.__base_test() #通过类.__base_test 访问报错
运行结果:
AttributeError: type object 'Test' has no attribute '__base_test'
#正确案例
class Test:
"""
表示测试中的一个点
"""
name = '测试'
def __base_test(self):
"""
私有方法
:return:
"""
print('这是私有化方法')
test = Test() # test是Test()的对象。
test._Test__base_test()
运行结果:
这是私有化方法
私有化方法后,我们只能在类的内部使用该方法,不能被外部调用。
同属性控制,方法需要访问被限制的方法也是 _类名__xx 如 test._Test__base_test。
在计算机编程中,自省是指这种能力:检查对象以确定它是什么类型、它有哪些属性和哪些方法。自省向程序员提供了极大的灵活性和控制力。
检查一个对象是否是某个或某些类型的实例
print(isinstance(1,int))
运行结果: True
print(isinstance('str',str))
运行结果: True
print(isinstance('str',list))
运行结果: False
检查一个类是否是某个或某些类的子类
print(issubclass(bool,int))
运行结果: True
反射就是动态的操作对象。
简单的讲就是根据字符串形式的属性名方法名操作对应的对象。
检查一个对象是否有给定名称的属性
print(hasattr([1,2,3,4],'append'))
运行结果: True
print(hasattr({"set":'sex'},'pop'))
运行结果: True
返回一个对象给定名称的属性
import requests
print(getattr(requests,'post'))
运行结果:
class Test():
name = '测试'
print(getattr(Test,'name'))
运行结果:测试