欢迎关注博主 Mindtechnist 或加入【Linux C/C++/Python社区】一起学习和分享Linux、C、C++、Python、Matlab,机器人运动控制、多机器人协作,智能优化算法,滤波估计、多传感器信息融合,机器学习,人工智能等相关领域的知识和技术。
python面向对象 | 类和对象
- 三大编程范式
- 类的属性
- 类和实例对象
- 对象和实例属性
- 类和实例的作用域
- 静态与组合
- 静态属性
- 类方法
- 静态方法
- 组合
专栏:《python从入门到实战》
三大编程范式
三大编程范式是指:面向过程编程、函数式编程和面向对象编程。三种编程范式只是习惯不同,但是并没有高低之分。正如那句话所说,天下武功门派没有高低之分,只有习武之人的高低之分。
我们都知道C语言是面向过程的,C++是面向对象的。但是,并不是说C语言一定就只能面向过程,我们用C也可以实现面向对象(实际上在Linux内核中就有很多面向对象的思想),而C++也可以面向过程编程,只能说C++提供了一些特性可以更好地支持面向对象。总之,三大编程范式没有高低之分,也没有和具体的语言绑定这一说。
需要注意的是,函数式编程 ≠ 用函数编程 ,函数式编程就是用python内的函数来表达出数学层面的函数,二者结合就是函数式编程,即数学函数+python函数
。
编程最开始是无组织无结构的,从简单的控制流中按步骤写指令,从指令中提取出重复的代码块或逻辑,组织到一起,便实现了代码复用,这也标志着代码由无结构走向了结构化,写程序的过程也具有了逻辑性。如果我们定义函数,并在函数外定义变量,将变量以参数的形式传递给函数,这就实现了数据与动作的分离,如果更进一步,我们把数据和动作内嵌到一个结构里面,那我们就有了一个对象系统。也就是说,对象是数据和函数的结合物。
注意:类和对象是有区别的,类是抽象的概念,抽象出共同属性和方法,对象是一个实例,是类实例化出来的对象。面向对象编程并不等于面向类编程,函数和类都可以实现面向对象编程。
下面是一个用函数实现面向对象的例子
def city(name, addr, type):
def init(name, addr, type):
c = {
'name':name,
'addr':addr,
'type':type,
'visit':visit,
}
return c
def visit(c):
print('%s %s %s 欢迎您' %(c['addr'], c['type'], c['name']))
return init(name, addr, type)
c1 = city('济南', '山东', '省会')
类的属性
声明一个类和声明一个函数很相似,我们使用函数实现面向对象程序设计的时候,是通过返回值得到具体对象的,同样class()也表示执行这个类,也是实例化的过程,和函数一样,它会返回一个对象。
注意:python2分为经典类和新式类,python3统一都是新式类。
class c1:
‘经典类’
pass
class c2(parent_class):
‘新式类’
pass
类的属性有两种,数据属性和函数属性。
数据属性:变量(实例对象只有数据属性)。
函数属性:方法,即函数。
print(classname) #打印类的属性 – 属性名列表
print(classname.__dict__) #查看类的属性字典 – 属性的键值对
print(classname.__dict__[‘attrname’]) #获取某一个属性
classname.__dict__[‘funcname’]() #运行类的某一个属方法
python为类内置的特殊属性
类名.__name__# 类的名字(字符串)
类名.__doc__# 类的文档字符串
类名.__base__# 类的第一个父类 – python中所有类都有一个共同父类
类名.__bases__# 类所有父类构成的元组
类名.__dict__# 类的字典属性
类名.__module__# 类定义所在的模块
类名.__class__# 实例对应的类(仅新式类中)
类和实例对象
类的作用域和函数一样,调用类的属性时,会先在自己作用域寻找,自己作用域找不到再去上一层作用域寻找。
实例只有数据属性,但是实例对象可以调用类的数据属性。
实际上,实例就是用通过类的__init__产生的,它只包含__init__方法内的一些属性。
实例没有方法属性,但是实例可以调用类的方法属性,注意调用的方法是类的,而不是实例自己的。
如果想调用类的方法,需要通过类去调用(实例对象无法调用类的方法,因为不在作用域内),并把实例作为参数传给classname.function(obj)。
类中的方法,一般都要设置一个参数self,当使用实例调用类的方法时,默认会把实例对象本身传给self。实例调用方法会自动传参self,但是类调用方法需要手动传入实例参数。
实例是由类中的__init__方法产生的,由__init__传参并返回的对象,实力只含有__init__内的数据属性,但是实例可以访问类的数据属性(__init__外部的),因为实例的作用域也就是__init__的作用域在类的作用域之内,(可以访问上一层作用域的变量),但是实例是没有方法属性的,实例想调用方法只能去找类要,并把自己传过去。(类中的方法都有一个默认参数,在第一个位置,也就是self,它用于指向实例自己)。
可以对类的数据属性和方法属性进行增删改查;
对实例的数据属性也可以增删改查,也可以对实例增加方法属性,调用的时候相当于调用实例自己的方法,需要手动把实例自己(不传自己也行,实际上传啥都可以)传进去赋给self(实例调用类的方法时是自动传参给self的,传的是自己)。- 但是不要给实例增加方法,应该把实例的数据属性和方法分离,同一个类创建的实例调用的都是类的方法,这样数据和方法就分离了。
对象和实例属性
实际上,实例化就是类名()
的过程,其返回结果是一个对象,加上括号的行为和函数的运行非常相似。类和函数一样有作用域,我们可以把类看作是最外层的函数,也就是最大的作用域。实例化会自动触发init函数的运行,最后返回一个值,也就是实例,我们需要的实例属性就存放在init函数的局部作用域里。类有类的属性字典,即类的作用域,而实例有实例的属性字典,即实例的作用域。也就是说,当我们使用对象的方法时obj.func()
会先在自己的作用域找,找不到再去外层类的字典中去找,都找不到才会报错。
类和实例的作用域
#---------------------------------------------------------
class People:
name = ‘su’
def __init__(self, age): #通过自动运行__init__进行实例化
self.age = age #实例的作用域在__init__内
def test(self, string)
print(‘test %s’ %string)
p1 = People(‘18’)
p1.name = ‘yy’
#相当于在实例的字典中增加一个属性,和类的字典毫无关系
print(People.name) # su
print(p1.name) #yy
#---------------------------------------------------------
name = ‘su’
class People:
def __init__(self, age):
self.age = age
def test(self, string)
print(‘test %s’ %string)
p1 = People(‘18’)
print(p1.name) #err – 只会在类的作用域内部找
#---------------------------------------------------------
class People:
def __init__(self): #__init__除了不能返回值,和普通函数无区别
age = input(‘input age’)
self.age = age
def test(self, string)
print(‘test %s’ %string)
#千万不要这样做
#---------------------------------------------------------
name = ‘su’
class People:
def __init__(self, age):
self.age = age
print(‘name: --’, name) #可以打印出
def test(self, string)
print(‘test %s’ %string)
p1 = People(‘18’)
#只有以点.去访问的时候,才遵循在类的内部去找的原则,因为.是成员运算符
#---------------------------------------------------------
name = ‘su’
class People:
name = ‘tt’
def __init__(self, age):
self.age = age
print(‘name: --’, name) #su
def test(self, string)
print(‘test %s’ %string)
p1 = People(‘18’)
print(People.name)
print(p1.name)
# .调用方式在类内部寻找,遵循类的作用域,在类和实例的字典中去找
#不用.访问,那么就和类还有实例没关系,就是访问普通变量,它就不会在类还有实例的字典内找变量,而是查找普通变量
#---------------------------------------------------------
class People:
name = ‘tt’
l = [‘a’, ‘b’]
def __init__(self, age):
self.age = age
def test(self, string)
print(‘test %s’ %string)
p1 = People(‘18’)
print(p1, l)
p1.l = [1, 2, 3] #改的是实例,在实例的字典中加了一个列表
#给实例新增加了一个新属性,= 表示新增
print(People.l)
print(p1.l)
p1.l.append(‘c’) #这里没有用等号,所以不会给实例p1增加新属性
#这就是改的类的属性了
print(p1.__dict__) #实例的属性字典
print(People__dict__) #类的属性字典
静态与组合
静态属性
@property - 装饰器
class People:
def __init__(self, name, age):
self.name = name
self.age = age
@property
def get_attr(self):
#print(‘%s - %s’ %( self.name, self.age))
return [name, age]
p1 = People(‘su’, 17)
p1.get_attr #调用的时候不再加()就可以执行
#调用方法属性和调用数据属性一样,这样就隐藏了背后的实现逻辑
#调用者调用的时候,只能看到结果,但是无法得知背后的逻辑实现
#把方法属性封装起来像数据属性一样
类方法
@classmethod
class People:
tag = 1
def __init__(self, name, age):
self.name = name
self.age = age
@property
def get_attr(self):
#print(‘%s - %s’ %(self.name, self.age))
return [name, age]
def get_tag(self):
print(‘%s’ %self.tag)
People.get_tag(x) #原则上可以传入任何参数,但是底层self.tag限制了必须传入一个实例
p1 = People(‘su’, 17)
People.get_tag(p1)
#我的需求是,只打印类的方法,我不想和实例捆绑在一块
class People:
tag = 1
def __init__(self, name, age):
self.name = name
self.age = age
@property
def get_attr(self):
#print(‘%s - %s’ %(self.name, self.age))
return [name, age]
@classmethod
def get_tag(cls): #参数是一个类名
print(‘%s’ %cls.tag)
People.get_tag() #将类和实例分离 – 提供了由类调用的方法 – 类方法
#类方法也会自动传参,会把调用的类名自动传入给cls
#类方法只能调用类属性,不能调用实例属性,实例属性是由self调用的
#只要参数带self,那么这个方法就和实例绑定了
#cls就是和类绑定
#实例也能调用类方法 – 但是最好不要这样做,类方法出现的意义就是给类调用
静态方法
@staticmethod
class People:
def __init__(self, name, age):
self.name = name
self.age = age
@property
def get_attr(self):
print(‘%s - %s’ %(self.name, self.age))
@staticmethod #类的工具包
def do_thing(a, b, c):
print(a, b, c)
def do_thing2(a, b, c):
print(a, b, c)
print(People.__dict__)
p1 = People(‘su’, 17)
print(p1.__dict__)
People.do_thing(1, 2, 3) #ok – 不会自动传参
p1.do_thing(1, 2, 3) #ok – 不会自动传参
People.do_thing2(1, 2, 3) #ok
p1.do_thing2(1, 2, 3) #error #p1会自动把自己传进去 – 参数错误
# @property – 将方法和实例绑定 – 用self调用实例属性 – 自动传入实例本身给self
# @classmethod – 将方法和类绑定(和实例就没关系了)- 用cls调用类的属性 – 自动传入本身给cls
# @staticmethod – 既不和实例绑定也不和类绑定,和实例和类都没关系了,是一个类的工具包 – 不会自动传参
# 静态方法staticmethod只是名义上归属类管理,不能使用类变量和实例变量,是类的工具包 – 既没有self也没有cls,无法通过.去调用属性
组合
组合是做关联的,类与类之间没有共同点,但是它们之间是有关联的,这时就用组合去解决,去建立关联。(实例对象做参数,类做参数)
这里面的Head()、Foot()等就是一个对象,类名加()本身就是实例化的意思,所以上面的效果和直接传递一个实例对象的参数效果是一致的。