在上一小节中,我们介绍了什么是面向对象编程,因此也衍生出了对象和类的概念.然后又大概的讲了一下,对象是什么?类又是什么?类和对象之间的关系,以及类在python中的基本使用语法和查找顺序,那么今天我们接着将类和对象的一些其他玩法.
python中一切皆对象
相信大家在学习python的时候,老是可以看到一些 python内一切皆对象的概念.
是的python中确实是一切皆对象.
为什么这么讲呢?
因为在python3中统一了类型和类的概念,即类型实例化返回的也是一个对象
来个案例介绍吧
我们看看自定义的类,和我们之前学的数据类型是不是都是类
这是我们自定义的类
class Student:
n = 0
def __init__(self, name, age, gender):
Student.n += 1
self.name = name
self.age = age
self.gender = gender
def choose(self): # self = obj1
print('hello %s' %self.name) # obj1.name
obj1 = Student("egon1", 18, "male") # 自定义类的实例化 过程, obj1为一个实例,也就是一个对象
print(type(obj1)) # <class '__main__.Student'> 这个对象的类型为 __main__.Student
print(Student) # <class '__main__.Student'> 类的类型也是 __main__.Student
obj1.choose() # 对象可以通过名称调用类的方法
Student.choose(obj1) # 类也可以通过名称调用类的方法
这是一个列表类型
l1 = list([1,2,3]) # 列表类型实例化过程, l1为一个实例,也就是一个对象
print(type(l1)) # # <class 'list'> 这个对象的类型为 list
print(list) # <class 'list'> 这个列表类型也为 list
l1.append(4) # 对象可以通过名称调用类型的方法
list.append(l1,4) # 类型可以通过名称调用类型的方法
print(l1)
我们可以发现,他们在使用上完全一样,只是一个为自定义的类,一个为内置的类型,所以python3中统一了名称类和类型. 不仅是列表 其他的类型都是这样.
那一切皆对象这样做的好处是什么?
我们在使用内置的数据类型的时候,我们可以发现,我们可以调用其下好多的功能和属性方法.这大大加快了我们开发的效率.
类中自带的装饰器
有了这个概念,我们在学习一下类中的装饰器的应用
先来一个需求吧,计算出用户的BMI指数
BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)
成人的BMI数值:
过轻:低于18.5
正常:18.5-23.9
过重:24-27
肥胖:28-32
非常肥胖, 高于32
体质指数(BMI)=体重(kg)÷身高^2(m)
EX:70kg÷(1.75×1.75)=22.86
那就涉及到一个问题我怎么才能让用户访问我的方法和访问属性一样,只需要对象.bmi
就可以知道对应的属性了
我们根据我们之前的知识我们解决方案是:
class People:
def __init__(self, name, weight, height):
self.name = name
self.weight = weight
self.height = height
self.bmi = weight / (height ** 2) # 根据对象的体重和身高,为对象添加一个新属性bmi
# 于是调用阶段
p1 = People('tom', 65, 1.76) # 20.983987603305785
print(p1.bmi) # 访问属性的方式
用装饰器 property 的解决方案
class People:
def __init__(self, name, weight, height):
self.name = name
self.weight = weight
self.height = height
@property
def bmi(self):
return self.weight / (self.height ** 2)
p1 = People('tom', 65, 1.76) # 20.983987603305785
print(p1.bmi) # 访问属性的方式
俩次的结果是一样的,没有好坏之分,要看应用场景
所以我们装饰器property
的功能就是将被装饰的函数方法变为一个数据属性
完全伪装方法
再来一个案例介绍:
将类内的name属性隐藏,但是让外部访问看起来和正常一样
解决方案1:
class People1:
def __init__(self, name):
self.__name = name
# @property 将功能伪装成一个数据
@property
def name(self): # 只需要将名称变为name 就可以达到伪装的需求
return self.__name
# @name.setter 为name这个属性 加上可以改值得需求
@name.setter
def name(self, x): # x表示你要修改成值
if type(x) is not str:
raise Exception("名字必须是字符串类型")
self.__name = x
# @name.deleter 为name属性 加上可以删除的需求
@name.deleter
def name(self):
print('不能被删除')
jkey = People1('jkey')
# 对对象的name属性进行查改删操作
print(jkey.name) # 查
jkey.name = 'JKey' # 改
print(jkey.name)
del jkey.name # 删
# print(jkey.name()) # 并且不可以同过被装饰的函数的名称来对name进行操作
方案2:
class People2:
def __init__(self, name):
self.__name = name
def get_name(self):
return self.__name
def set_name(self, x):
if type(x) is not str:
raise Exception("名字必须是字符串类型")
self.__name = x
def del_name(self):
print('不能被删除')
# 注意 放入property括号内的名称顺序一定要是这样
name = property(get_name, set_name, del_name)
liu = People2('liu')
print(liu.name) # 查
liu.name = 'Liu' # 改
print(liu.name)
print(liu.get_name()) # 注意:这种方法可以通过类内的函数名来直接访问
del liu.name # 删
俩种方法也没有谁好谁坏,但个人推荐使用第一种,加了装饰器更具有强制性,方式二就有了俩种查改删的操作.
绑定方法和非绑定方法
案例介绍
class People:
def __init__(self, name):
self.name = name
# 但凡在类中定义一个函数,默认就是绑定给对象的,应该由对象来调用
# 特性:当对象来调用时,会将对象当作第一个参数自动传入
def tell(self):
print(self.name)
# 类中定义的函数被classmethod装饰过,绑定给类,应该由类来调用
# 特性:当类来调用时,会将类当作第一个参数传入
@classmethod
def f1(cls):
print(cls)
# 类中定义的函数被staticmethod装饰,这个函数就是一个非绑定的方法,即为一个普通函数,谁都可以调用
# 特性:无 谁来调用都是一个普通的函数 没有自动传参的效果
@staticmethod
def f2(x, y):
print(x, y)
对象使用三种方法
p1 = People('jkey') # 实例化一个对象 p1
#
# 当调用的方法为一个默认函数时,将对象本身当作第一个参数传入,即使用语法可以直接调用
p1.tell() # jkey
# 当调用的方法为一个被classmethod装饰的函数时,将类当作第一个参数传入,即使用语法也可以直接调用
p1.f1() # <class '__main__.People'>
# 当调用方法为一个非绑定方法,使用语法就是该函数的使用语法,即该传参传参
p1.f2(123, 321) # 123 321
类使用三种方法
# 当调用的方法为一个默认函数时,是一个普通函数,即使用语法该传参传参
People.tell(p1) # p1 为一个实例化好的对象 返回值为 jkey
# 当调用方法为一个被classmethod装饰过的函数,即 将类自己本身当作第一个参数传入
People.f1() # <class '__main__.People'>
# 当调用方法为一个非绑定方法,使用语法就是该函数的使用语法,即该传参传参
People.f2(456, 654) # 456 654
应用场景介绍
既然我们知道了,如何定义和使用不同的方法,那就可以来一些实际需求
下面一些案例方便大家理解
定义阶段
import settings
class People2:
def __init__(self, name, age, gender):
self.id = self.create_id()
self.name = name
self.age = age
self.gender = gender
# 我们的需求是,打印出一个每个人的个人信息
# 我们可以先定义一个无参的函数,然后根据具体的功能代码,看看需要传入的参数
# 这时候我们发现我们需要的参数就是一个对象,于是我们的函数就变成了
# def tell_info(对象):
# print('<%s:%s:%s>' % (对象.name, 对象.age, 对象.gender))
# 在类中这种默认没有被装饰器装饰的函数就是给对象使用的,刚好就可以满足这个函数的需求
# 这个"对象"这个参数,在我们的类里面可以写成更专业的名字self于是就可以改成
def tell_info(self):
print('<%s:%s:%s>' %(self.name, self.age, self.gender))
# 当我们的个人信息来自一个文件当中时,我们要使其满足实例化的功能,并将文件中的数据绑定给每个实例化的对象
# 这个时候,我们就可以将功能设置为,一个需要将类自己传入的函数方法,因为实例化一个对象时,是通过类加括号实现的,于是其代码为:
@classmethod
def from_conf(cls):
print(cls)
return cls(settings.NAME, settings.AGE, settings.GENDER)
# 当我们的一个函数内部为一个不需要传入对象也不需要类的功能时,就就可以设置成一个普通的函数
# 下面代码功能是返回一个随机的id
@staticmethod
def create_id():
import uuid # 产生一个随机的uid号
return uuid.uuid1()
调用阶段
p1 = People2("egon",18,"male")
p2 = People2.from_conf() # <class '__main__.People2'>
print(p2.__dict__) # {'id': UUID('87d7b41b-53d6-11eb-b7a4-28c63f06b912'), 'name': 'JKey', 'age': 18, 'gender': 'male'}
print(p1.create_id()) # 7d7b41c-53d6-11eb-96a1-28c63f06b912
print(People2.create_id()) # 7d7b41c-53d6-11eb-96a1-28c63f06b912
需要注意的是当这个函数为一个被classmethod装饰过的函数时,应该是由类去调用,但是也可以通过对象去调用,而且使用的语法是一样的,但本质是将类当作第一个参数传入
小总结:
1.当我们的类里面的函数是一个没有被任何装饰器装饰时,应该是给对象使用的,并且当被对象调用时,有特性:将对象当作第一个参数传入
2.当我们的类里面的函数被装饰器classmethod装饰过之后,应该是一个绑定给类使用的方法,当被类调用时,有特性:将类当作第一个参数传入
3.当我们类里面的函数被装饰器staticmethod装饰过之后,是一个普通的函数,即调用时,该传参传参
继承
继承是创建新类的一种方式
新建的类称之为子类
被继承的类称之为父类、基类、超类
继承的特点是:子类可以遗传父类的属性
类是用来解决对象与对象之间冗余问题的
而继承则是来解决类与类之间冗余问题的
在python中支持多继承
举例说明:
class Parent1(object):
"""在python2中需要通过类自己去绑定一个基类为object"""
pass
class Parent2:
"""而在python3中默认都继承了object这个基类"""
pass
class Sub1(Parent1):
"""使用继承的方法就是在类名后面加括号,括号内为基类名"""
pass
class Sub2(Parent1, Parent2):
"""基类名可以为多个"""
pass
__bases__
属性 通过__bases__
可以查看类的基类有哪些
print(Sub1.__bases__) # (<class '__main__.Parent1'>,)
print(Sub2.__bases__) # (<class '__main__.Parent1'>, <class '__main__.Parent2'>)
但凡是继承了object类的子类以及该子类的子子孙孙类都是新式类
反之就是经典类
在python2中有新式类与经典类之分,在python3中全都是新式类
python2
python3
print(Parent1.__bases__) # (<class 'object'>,)
print(Parent2.__bases__) # (<class 'object'>,)
示例1 调用obj.f2() ,会发生什么事
class Bar:
def f1(self):
print('Bar.f1')
def f2(self):
print('Bar.f2')
self.f1() # obj.f1()
class Foo(Bar):
def f1(self):
print("Foo.f1")
obj = Foo()
obj.f2() # Bar.f2 Foo.f1
解析:
我们一步步分析,我们都知道对象去找方法的时候,它的查找顺序是: 对象自己 对象绑定的类 对象绑定的类的基类 ....
所以这个obj.f2(),我们先查找的应该是obj这个对象,我们可以发现obj这个对象没有方法f2,然后我们再去Foo这个类
里面找,我们可以发现还是,没找到,于是我们就去Foo的基类Bar中找,这时候,我们发现我们找到了,然后执行Bar类里
面的f2的代码体,先打印了 "Bar.f2" 然后我们执行了self.f1(),这个self是对象本身,所以就等价于 obj.f1() 然后我们又
可以在obj这个对象里面找,发现没有,又去Foo类里面找,于是我们找到了,然后就 执行了Foo的代码体,于是就打印了 Foo.f1 结束
示例2 我们将上面的案例,改一下,将f1都隐藏一下,于是就变成了下方代码
class Bar:
def __f1(self): # _Bar__f1
print('Bar.f1')
def f2(self):
print('Bar.f2')
self.__f1() # self._Bar_f1
class Foo(Bar):
def __f1(self): # _Foo__f1
print("Foo.f1")
obj = Foo()
obj.f2() # Bar.f2 Bar.f1
解析:
我们再来分析一波,再根据对象查找顺序,先找obj对象本身没找到,然后去类Foo,也没找到,然后就去Foo的父类 Bar ,找到了,执行了f2的内部代码 先打印了 'Bar.f2'. 然后执行了 self.__f1()
因为这时候我们还是在Bar类的内部,我们根据将这个隐藏属性 就变成了 self._Bar_f1() -> obj._Bar_f1(),然后我们再找一边,发现对象自己这没有,去Foo找,注意:Foo的 __f1
变成了 _Foo__f1
所以,我们还是没找到,于是到Bar中找 _Bar__f1
发有了,于是又打印了 Bar.f1.结束
总结:
1.今天我们学习了,python中一切皆对象的概念,在python3中统一了类和类型,即我们之前学的数据类型都是内置的类,而我们现在用的class类都是我们自定义的类,
2.类内的一些装饰器
2.1@property
装饰器 用来将 类中的 功能方法 伪装成 数据属性
2.2 @要伪装的功能方法名.setter
装饰器 用来为 被伪装的功能方法给予可修改的功能
2.3 @要伪装的功能方法名.deleter
装饰器 用来为 被伪装的功能方法给与可删除的功能
3.绑定方法和非绑定方法
3.1绑定方法之为对象绑定,即类中没有被装饰器装饰的函数,应该给对象使用,当对象调用时,会将对象本身当作第一个参数传入
3.2绑定方法之为类绑定,即类中被装饰器classmethod装饰的函数,应该给类使用,当类来调用时,会将类当作第一个参数传入
3.3非绑定方法,即类中被装饰器staticmethod装饰的函数,可以被类或者对象调用,就是一个普通函数
4.继承 继承是为了解决 类与类之间代码冗余问题的.
4.1python2中分新式类和经典类
- 新式类指的是继承了object的类,那么它或者它的子代都是新式类
- 经典类 即 没有继承object的类
4.2 python3中都是新式类.即默认就继承了object