Python 面向对象

Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的。本章节我们将详细介绍Python的面向对象编程。

如果你以前没有接触过面向对象的编程语言,那你可能需要先了解一些面向对象语言的一些基本特征,在头脑里头形成一个基本的面向对象的概念,这样有助于你更容易的学习Python的面向对象编程。

接下来我们先来简单的了解下面向对象的一些基本特征。

面向对象技术简介:

类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
实例变量:定义在方法中的变量,只作用于当前实例的类。
继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
实例化:创建一个类的实例,类的具体对象。
方法:类中定义的函数。
对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

面向过程与面向对象编程的区别:

  • 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了
  • 面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为

面向过程与面向对象的优缺点:

面向过程:

  • 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源
  • 缺点:没有面向对象易维护、易复用、易扩展

面向对象:

  • 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
  • 缺点:性能比面向过程低

1 类与对象

类(class):用来描述具有相同属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例

类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用

对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法

1.1 类定义

语法:

class ClassName:
   '类的帮助信息'   #类文档字符串
   class_suite  #类体

应用:

class Student:                  #会产生一个类的名称空间,用来存放类内部的变量及函数
    school =  '北京昌平学校'      #类的数据属性
    def learn(self):            #类的函数属性
        print('is learning')
print(Student.__dict__)         #查看类的名称空间
#执行结果:
{'__module__': '__main__', 'school': '北京昌平学校', 'learn': <function Student.learn at 0x000000000259B8C8>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}

print(Student.school)           #实际上,创建一个类之后,可以通过类名访问其属
#执行结果:
北京昌平学校

操作类的属性(属性引用):

class Student:
    school =  '北京昌平学校'
    def learn(self):
        print('is learning')
Student.x = '王二'                        #增加,实际等同Student.__dict__['x'] = 123456,但Python3不支持这种操作
Student.y = '张三'
Student.school = '华北电力大学'          #修改school
print(Student.__dict__)
结果:
{'__module__': '__main__', 'school': '华北电力大学', 'learn': <function Student.learn at 0x0000000001E7A8C8>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None, 'x': '王二', 'y': '张三'}shanc

删除:

del Student.y                          #删除
print(Student.__dict__)
结果:
{'__module__': '__main__', 'school': '华北电力大学', 'learn': <function Student.learn at 0x0000000001E8A8C8>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None, 'x': '王二'}

1.2 类对象

应用:

class Student:
    school =  '华北电力大学'
    def learn(self):
        print('is learning')
obj1 = Student()            #类名加括号,会产生一个该类的实际存在的对象,该过程称为实例化,其结果obj1也可被称为实例
print(obj1)                 #obj1是一个空对象

#执行结果:
<__main__.Student object at 0x00000000028D97F0>

很多类都倾向于将对象创建为有初始状态的。因此类可能会定义一个名为 __init__() 的特殊方法,像下面这样:

def __init__(self):             #类定义了 __init__() 方法的话,类的实例化操作会自动调用 __init__() 方法
    self.data = []

__init__() 方法可以有参数,参数通过 __init__() 传递到类的实例化操作上。例如:

class Student:
    school = '华北电力大学'
    def __init__(self,name,age,sex):             #在实例化时,产生对象之后执行
        self.name = name
        self.age = age
        self.sex = sex
    def learn(self):
        print('is learning')
obj1 = Student('wang',25,'male')                 #先产生一个空对象obj1 2、Student.__init__(obj1,'wang',25,'male')
print(obj1.__dict__)                             #查看对象的名称空间
obj1.id = '1'                                    #同样,对象属性也能被引用,等同obj1.__dict__['id'] = '1',Python3是支持的
print(obj1.name,obj1.age,obj1.sex,obj1.id)

#执行结果:
{'name': 'wang', 'age': 25, 'sex': 'male'}
wang 25 male 1

1.3 属性查找

class Student:
    school = '华北电力大学'
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
    def learn(self):
        print('%s is learning'%self.name)
obj1 = Student('王二',25,'male')
obj2 = Student('张三',18,'male')
print(obj1.school)                  # obj1和obj2都能访问类里面的数据属性
print(obj2.school)
print(id(obj1.school))              #属性id也一致,类的数据属性是共享给所有对象的
print(id(obj2.school))
print(obj1.learn)                   #类的函数属性是绑定给所有对象的(绑定方法),内存地址也不一样
print(obj2.learn)
obj1.learn()                        #绑定给谁,就由谁来调用,谁来调用就把谁本身当做第一个参数传入(自动传值)

#结果: 华北电力大学 华北电力大学 31928928 31928928 <bound method Student.learn of <__main__.Student object at 0x0000000001E79B38>> <bound method Student.learn of <__main__.Student object at 0x0000000001E79B70>> 王二 is learning

删除:

obj1.school = '华北大学'             #修改obj1属性
print(obj1.school)                  #查找顺序:对象-->类
print(obj2.school)                  #obj2不受影响

给类加一个计数器功能,每实例化一次就加1

class Student:
    count = 0
    school = '华北电力大学'
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
        Student.count += 1
    def learn(self):
        print('%s is learning'%self.name)
obj1 = Student('王二',25,'male')
print(Student.count)
obj2 = Student('张三',18,'male')
print(Student.count)
obj3 = Student('李四',18,'male')
print(Student.count)

1.4 绑定到对象的方法的特殊之处

class OldboyStudent:
    school='华北电力大学'
    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex
    def learn(self):
        print('%s is learning' %self.name) #新增self.name
    def eat(self):
        print('%s is eating' %self.name)
    def sleep(self):
        print('%s is sleeping' %self.name)
s1=OldboyStudent('王二','男',18)
s2=OldboyStudent('张三','女',38)
s3=OldboyStudent('李四','男',78)

 类中定义的函数(没有被任何装饰器装饰的)是类的函数属性,类可以使用,但必须遵循函数的参数规则,有几个参数需要传几个参数

OldboyStudent.learn(s1)
OldboyStudent.learn(s2)
OldboyStudent.learn(s3)
结果:
王二 is learning
张三 is learning
李四 is learning

类中定义的函数(没有被任何装饰器装饰的),其实主要是给对象使用的,而且是绑定到对象的,虽然所有对象指向的都是相同的功能,但是绑定到不同的对象就是不同的绑定方法

强调:绑定到对象的方法的特殊之处在于,绑定给谁就由谁来调用,谁来调用,就会将‘谁’本身当做第一个参数传给方法,即自动传值(方法__init__也是一样的道理)

s1.learn() #等同于OldboyStudent.learn(s1)
s2.learn() #等同于OldboyStudent.learn(s2)
s3.learn() #等同于OldboyStudent.learn(s3)

注意:绑定到对象的方法的这种自动传值的特征,决定了在类中定义的函数都要默认写一个参数self,self可以是任意名字,但是约定俗成地写出self。

1.6 对象之间的交互

class Garen:        #定义英雄盖伦的类,不同的玩家可以用它实例出自己英雄;
    camp='Demacia'  #所有玩家的英雄(盖伦)的阵营都是Demacia;
    def __init__(self,nickname,aggressivity=58,life_value=455): #英雄的初始攻击力58...;
        self.nickname=nickname  #为自己的盖伦起个别名;
        self.aggressivity=aggressivity #英雄都有自己的攻击力;
        self.life_value=life_value #英雄都有自己的生命值;
    def attack(self,enemy):   #普通攻击技能,enemy是敌人;
        enemy.life_value-=self.aggressivity #根据自己的攻击力,攻击敌人就减掉敌人的生命值。

我们可以仿照garen类再创建一个Riven类

class Riven:
    camp='Noxus'  #所有玩家的英雄(锐雯)的阵营都是Noxus;
    def __init__(self,nickname,aggressivity=54,life_value=414): #英雄的初始攻击力54;
        self.nickname=nickname  #为自己的锐雯起个别名;
        self.aggressivity=aggressivity #英雄都有自己的攻击力;
        self.life_value=life_value #英雄都有自己的生命值;
    def attack(self,enemy):   #普通攻击技能,enemy是敌人;
        enemy.life_value-=self.aggressivity #根据自己的攻击力,攻击敌人就减掉敌人的生命值。 

实例出俩英雄

>>> g1=Garen('草丛伦')
>>> r1=Riven('锐雯雯')

交互:锐雯雯攻击草丛伦,反之一样

>>> g1.life_value
455
>>> r1.attack(g1)
>>> g1.life_value
401 

2 继承

面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。继承完全可以理解成类之间的类型和子类型关系。

需要注意的地方:继承语法 class 派生类名(基类名)://... 基类名写在括号里,基本类是在类定义的时候,在元组之中指明的。

在python中继承中的一些特点:

  • 1:在继承中基类的构造(__init__()方法)不会被自动调用,它需要在其派生类的构造中亲自专门调用。
  • 2:在调用基类的方法时,需要加上基类的类名前缀,且需要带上self参数变量。区别于在类中调用普通函数时并不需要带上self参数
  • 3:Python总是首先查找对应类型的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。(先在本类中查找调用的方法,找不到才去基类中找)。

如果在继承元组中列了一个以上的类,那么它就被称作"多重继承" 。

语法:

派生类的声明,与他们的父类类似,继承的基类列表跟在类名之后,如下所示:

class SubClassName (ParentClass1[, ParentClass2, ...]):
   'Optional class documentation string'
   class_suite

实例:

class ParentClass1: #定义父类
    pass
class ParentClass2: #定义父类
    pass
class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass
    pass
class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类
    pass

查看继承

print(SubClass1.__bases__) #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类
结果:(<class '__main__.ParentClass1'>,)
print(SubClass2.__bases__)
结果:(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)

经典类与新式类

1.只有在python2中才分新式类和经典类,python3中统一都是新式类
2.在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类
3.在python2中,显式地声明继承object的类,以及该类的子类,都是新式类
3.在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类
#关于新式类与经典类的区别,我们稍后讨论

解决代码重用实例:

class Hero:
def __init__(self,nickname,aggressivity,life_value):
self.nickname=nickname
self.aggressivity=aggressivity
self.life_value=life_value

def move_forward(self):
print('%s move forward' %self.nickname)

def move_backward(self):
print('%s move backward' %self.nickname)

def move_left(self):
print('%s move forward' %self.nickname)

def move_right(self):
print('%s move forward' %self.nickname)

def attack(self,enemy):
enemy.life_value-=self.aggressivity
class Garen(Hero):
pass

class Riven(Hero):
pass

g1=Garen('草丛伦',100,300)
r1=Riven('锐雯雯',57,200)

print(g1.life_value)
r1.attack(g1)
print(g1.life_value)

'''
运行结果

243

'''

3 方法重写

如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。

class Parent:        # 定义父类
   def myMethod(self):
      print ('调用父类方法')

class Child(Parent): # 定义子类
   def myMethod(self):
      print ('调用子类方法')

c = Child()          # 子类实例
c.myMethod()         # 子类调用重写方法

#执行结果:
调用子类方法

4 组合

组合指的是:在一个类中以另外一个类的对象作为数据属性,称为类的组合

实例:

class People:
    school = '哈佛'
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex

class Student(People):
    def __init__(self,name,age,sex):
        People.__init__(self,name,age,sex)
        self.course = []                #学生可以学习多名课程
    def tell_info(self):
        print('<name:%s age:%s sex:%s>' %(self.name,self.age,self.sex))

class Teacher(People):
    def __init__(self,name,age,sex):
        People.__init__(self,name,age,sex)
        self.course = []                #老师可以教多名课程
        self.students = []              #老师可以教多个学生

class Course:                   #单独定义一个course类
    def __init__(self,course_name,course_period,course_price):
        self.course_name=course_name
        self.course_period=course_period
        self.course_price=course_price
    def tell_info(self):
        print('<课程名:%s 周期:%s 价格:%s>' %(self.course_name,self.course_period,self.course_price))

S_obj1 = Student('Yim',25,'male')
S_obj2 = Student('Lin',22,'male')
T_obj1 = Teacher('Jim',18,'male')
python = Course('Python','6mons',100)
java = Course('Java','10mons',200)

T_obj1.course.append(python)        #把课程加进去
T_obj1.course.append(java)
T_obj1.students.append(S_obj1)      #把学生加进去
T_obj1.students.append(S_obj2)
S_obj1.course.append(python)

for i in T_obj1.course:         #查看老师教的课程
    i.tell_info()           #Course('Python','6mons',100).tell_info()     Course('Java','10mons',200).tell_info()

for i in T_obj1.students:       #查看老师有多少名学生
    i.tell_info()

for i in S_obj1.course:         #查看学生学习的课程
    i.tell_info()

#执行结果:
<课程名:Python 周期:6mons 价格:100>
<课程名:Java 周期:10mons 价格:200>
<name:Yim age:25 sex:male>
<name:Lin age:22 sex:male>
<课程名:Python 周期:6mons 价格:100>

5 继承实现原理

继承顺序:

 

实例:

class A(object):
    def test(self):
        print('from A')

class B(A):
    def test(self):
        print('from B')

class C(A):
    def test(self):
        print('from C')

class D(B):
    def test(self):
        print('from D')

class E(C):
    def test(self):
        print('from E')

class F(D,E):
    # def test(self):
    #     print('from F')
    pass
f1=F()
f1.test()
print(F.__mro__)    #只有新式才有这个属性可以查看线性列表,经典类没有这个属性

#新式类继承顺序:F->D->B->E->C->A
#经典类继承顺序:F->D->B->A->E->C
#python3中统一都是新式类
#pyhon2中才分新式类与经典类

6 子类中调父类的方法

方法一:父类名.父类方法()

class Animal:
    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex
    def eat(self):
        print('%s eat' %self.name)
    def talk(self):
        print('%s say' %self.name)

class People(Animal):
    def __init__(self,name,age,sex,education):
        Animal.__init__(self,name,age,sex)          #调用父类方法,也可以不依赖于继承
        self.education=education
    def talk(self):
        Animal.talk(self)                           #调用父类方法,也可以不依赖于继承
        print('这是人在说话')

peo1=People('Yim',25,'male','幼儿园毕业')
print(peo1.__dict__)

方法二:super(),这种方法依赖于继承,从MRO列表当前的位置往后找

class Animal:
    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex
    def eat(self):
        print('%s eat' %self.name)
    def talk(self):
        print('%s say' %self.name)

class People(Animal):
    def __init__(self,name,age,sex,education):
        super().__init__(name,age,sex)                      #python2:super(People.self).__init__()
        self.education=education
    def talk(self):
        super().__init__(self)
        print('这是人在说话')

peo1=People('Yim',25,'male','幼儿园毕业')
print(peo1.__dict__)

7 绑定方法与非绑定方法

1、绑定方法:绑定给谁,就由谁来调用,谁来调用就把谁本身当做第一个参数传入(自动传值)

绑定到类的方法:用classmethod装饰器装饰的方法

  • 为类量身定制
  • 类.bound_method(),自动将类当做第一个参数传入
  • 其实对象也可调用,但仍将类当作第一个参数传入

绑定到对象的方法:没有被任何装饰器装饰的方法

  • 为对象量身定制
  • 对象.bound_method(),自动将对象作为第一个参数传入
  • 属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说

实例:

#配置文件settings.py
HOST='127.0.0.1'
PORT=3306 
import settings

class Mysql:
    def __init__(self,host,port):
        self.host = host
        self.port = port
    @classmethod
    def from_conf(cls):                 #绑定给类
        return  cls(settings.HOST,settings.PORT)
    def fun1(self):                     #绑定给对象
        pass

conn = Mysql.from_conf()
print(conn.host,conn.port)

#执行结果:
127.0.0.1 3306

2、非绑定方法:在类内部用staticmethod装饰器装饰的函数即非绑定方法,就是普通函数。statimethod不与类或对象绑定(应用场景),谁都可以调用,没有自动传值效果

import uuid

class Mysql:
    def __init__(self,host,port):
        self.host = host
        self.port = port
        self.id = self.create_uuid()        #调用create_uuid函数
    @staticmethod
    def create_uuid():                      #普通函数,没有自动传值效果
        return str(uuid.uuid1())

conn = Mysql('127.0.0.1',3306)
print(conn.id)

#执行结果:
ea5637e8-8199-11e7-962a-34de1a770c8f

 

8 接口与归一化设计

接口提取了一群类共同的函数,可以把接口当做一个函数的集合。让子类去实现接口中的函数

在Python中没有一个叫做interface的关键字,如果模仿接口的概念,可以借助第三方模块:http://pypi.python.org/pypi/zope.interface,也可以使用继承

实例:

class Interface:#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。
    def read(self): #定接口函数read
        pass

    def write(self): #定义接口函数write
        pass
 
class Txt(Interface): #文本,具体实现read和write
    def read(self):
        print('文本数据的读取方法')

    def write(self):
        print('文本数据的写方法')

class Sata(Interface): #磁盘,具体实现read和write
    def read(self):
        print('硬盘数据的读取方法')

    def write(self):
        print('硬盘数据的写方法')

class Process(Interface):
    def read(self):
        print('进程数据的读取方法')

    def write(self):
        print('进程数据的写方法')

9 抽象类

抽象类是一个特殊的类,只能被继承不能被实例化,在Python中需要借助模块实现。以下是一个实例:

import abc

class Interface(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def read(self):
        pass
    @abc.abstractmethod
    def write(self):
        pass

class Txt(Interface):
    def read(self):                 #必须要有read
        print('文本数据的读取方法')

    def write(self):                #必须要有write
        print('文本数据的写方法')

10 多态

是允许将父对象设置成为和一个或多个它的子对象相等的技术,比如Parent:=Child; 多态性使得能够利用同一类(基类)类型的指针来引用不同类的对象,以及根据所引用对象的不同,以不同的方式执行相同的操作

Python不直接支持多态,但可以间接实现

实例:

import abc

class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
    @abc.abstractmethod
    def talk(self):
        pass

class People(Animal): #动物的形态之一:人
    def talk(self):
        print('say hello')

class Dog(Animal): #动物的形态之二:狗
    def talk(self):
        print('say wangwang')

def func(animal):
    animal.talk()

p = People()
d = Dog()
func(p)
func(d)

#执行结果:
say hello
say wangwang

11 封装

“封装”就是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体(即类);封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口,一特定的访问权限来使用类的成员

实例:

#这种隐藏不是真正意义上的隐藏,其实这仅仅是一种变形操作,只在类定义阶段发生的
#类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式
#在子类定义的__x不会覆盖在父类定义的__x
class Foo:
    __N = 123456        #变形为_Foo__N
    def __init__(self,name):
        self.name = name
    def __f1(self):     #变形为_Foo__f1
        print('f1')
    def bar(self):
        self.__f1()     #只有在类内部才可以通过__f1的形式访问到
f = Foo('Yim')
print(Foo.__dict__)
#print(Foo.__N)          #外部无法直接访问
f.bar()

#执行结果:
{'__module__': '__main__', '_Foo__N': 123456, '__init__': <function Foo.__init__ at 0x00000000025FB8C8>, '_Foo__f1': <function Foo.__f1 at 0x00000000025FB950>, 'bar': <function Foo.bar at 0x00000000025FB9D8>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}
f1
class People:
    def __init__(self,name,age,sex):
        self.__name = name
        self.__age = age
        self.__sex = sex
    def tell_info(self):
        print('<名字:%s 年龄:%s 性别:%s>' %(self.__name,self.__age,self.__sex))
    def set_info(self,x,y,z):               #可以定制一些控制逻辑来控制使用者对数据的操作
        if not isinstance(x,str):
            raise TypeError
        if not isinstance(y,int):
            raise TypeError
        if not isinstance(z,str):
            raise TypeError
        self.__name = x
        self.__age = y
        self.__sex = z

p = People('Yim',25,'male')
p.set_info('Zim',18,'male')
p.tell_info()
#取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
#对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做
#隔离了复杂度,同时也提升了安全性

class ATM:
    def __card(self):
        print('插卡')
    def __auth(self):
        print('用户认证')
    def __input(self):
        print('输入取款金额')
    def __print_bill(self):
        print('打印账单')
    def __take_money(self):
        print('取款')

    def withdraw(self):                 #隔离复杂度
        self.__card()
        self.__auth()
        self.__input()
        self.__print_bill()
        self.__take_money()

a=ATM()
a.withdraw()

静态属性property:property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值

#计算体质指数(BMI)
#体质指数(BMI)=体重(kg)÷身高^2(m)

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)

p = People('Yim',70,1.80)
# print(p.bmi())
print(p.bmi)
posted on 2017-08-17 20:37  wangyinhu1208  阅读(200)  评论(0编辑  收藏  举报