面向对象的程序设计

  面向过程的程序设计经常用于操作系统的内核,git等,一成不变的流水线式解决一个问题,极大程度降低程序复杂性。

  面向对象的程序设计解决了程序的扩展性(类可产生各种各样的对象,对于新增技能或修改技能可使用方法直接调用),但是可控性差,因为面向对象程序一旦开始就是由对象之间交互来解决问题。

  OOD面向对象的程序设计

  先找程序中所有的对象,将对象归纳出类(并归纳出共同属性与方法和不同的属性)。

  OOP面向对象编程

  编程时先定义出类,再根据类实例化出对象。

  python3统一了类与类型的概念,python3中的类型就是类。

  编程方式:

  面向过程: 根据代码在脚本的堆叠顺序,从上到下依次执行,

  函数式编程:将相同功能的代码封装到函数中,直接调用即可,减少代码重复性,

  面向对象:对函数进行分类和封装,将同类的函数放到一个类中,使调用更简单。

类的简介

  类和对象
  类就是一个模板,模板里可以包含多个方法(即函数),方法里实现一些功能,对象则是根据模板创建的实例,通过实例对象可以执行类中的函数。

#创建类  class+类名
class foo:               #class是关键字,表示类,foo是类的名字
    def f1(self):               #类的方法1
        pass
    def f2(self):               #类的方法2
        pass

#创建对象  对象 = 类名()
bar = foo()   #创建一个bar对象 ,此对象中有类中所有的方法 ,创建对象,类名称后加括号即可 

#调用对象的方法  对象.方法名()
bar.f1()
bar.f2()
创建类和变量的方式

  类名+()就等于在执行Person.__init__(),执行完__init__()就会返回一个对象。这个对象类似一个字典,存着属于这个人本身的一些属性和方法。

  这里提一嘴特殊的类属性。

  类名.__name__# 类的名字(字符串)

  类名.__doc__# 类的文档字符串

  类名.__base__# 类的第一个父类

  类名.__bases__# 类所有父类构成的元组

  类名.__dict__# 查看类和对象的名称空间

  类名.__module__# 类定义所在的模块

  类名.__class__# 实例对应的类(仅新式类中)

  python中使用class定义类,在python3中只有新式类(默认继承object),而python2中有新式类(手写继承object)与经典类的区别,可使用__basic__查看继承关系。

  python2的经典类:

  类的一般操作形式:

class Teacher:                 #定义类
    x=1  #调用属性时不论对象还是类指向的都是同一块内存地址,属性又称静态字段
    def __init__(self,name,sex,age,money):#init不能有返回值,里面放的是独有特征,共有特征使用单独函数定义
        self.name=name                    #普通字段
        self.sex=sex
        self.age=age
        self.money=money
    def search(self):#teacher调用函数或者实例化对象的绑定方法调用的不是同一块内存地址,普通方法
        print("scord")
    def study(self):
        print("study")
print(Teacher.x)   #1
t=Teacher('jeff','male','110',10)#实例化出来的对象
print(t.name)    #jeff
print(t.age)    #110
t.study()       #study
Teacher.y=5
print(Teacher.y)#5
class 类名:
    类属性 = None
    def __init__(self,参数1,参数2):
        self.对象的属性1 = 参数1
        self.对象的属性2 = 参数2

    def 方法名(self):pass

    def 方法名2(self):pass

对象名 = 类名(1,2)  #对象就是实例,代表一个具体的东西
                  #类名() : 类名+括号就是实例化一个类,相当于调用了__init__方法
                  #括号里传参数,参数不需要传self,其他与init中的形参一一对应
                  #结果返回一个对象
对象名.对象的属性1   #查看对象的属性,直接用 对象名.属性名 即可
对象名.方法名()     #调用类中的方法,直接用 对象名.方法名() 即可
#对象增加属性
对象.新的属性名 = 1000
通用的class定义方法

  类是整个单独的名称空间,定义类也就是单独定义了他的名称空间(变量,函数和类名字)用来存储类中定义的所有名字,这些名字称为类的属性,静态属性就是直接在类中定义的变量,动态属性就是定义在类中的方法。可以使用__dict__查看类和对象的名称空间。实例化对象会先从自己的dict查找变量,找不到就去类(父类)的dict找,没有则会报错。

  类可以进行实例化,可以进行属性引用。对象只能属性引用。对象本身只能引用属性(变量name,sex,age。。。)。

t.name2='tom'#
del t.name2#
t.name='jerry'#
print(t.name)#

  实例化对象之间的交互:

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 #根据自己的攻击力,攻击敌人就减掉敌人的生命值。

class Garen:
    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 #根据自己的攻击力,攻击敌人就减掉敌人的生命值。


#对象之间的交互
 r1=Riven('芮雯雯')
 g1=Garen('草丛轮')
 print(r1.life_value)
 g1.attack(r1)
 print(r1.life_value)
德玛西亚大战瑞文

  私有属性

  特点:

  类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。

  这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。

  在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。

  注意:

  这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N。

class people:
    __name = 'jeff'
    __age = 12
    def set(self):
        print(self.__name)
        print(self.__age)
p = people()
#print(p.__name,p.__age)   #AttributeError: 'people' object has no attribute '__name'
p.set()
运行结果;
jeff
12

  封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。

#类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了
        return self.__width * self.__length * self.__high


#对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能
 r1.tell_area()#对于用户只要知道这个接口的功能就可以了
扩展性

  property属性

  把绑定方法装饰的像一个属性一样调用,被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('egon',75,1.85)
print(p1.bmi)
property属性
import math
class Circle:
    def __init__(self,radius): #圆的半径radius
        self.radius=radius

    @property
    def area(self):
        return math.pi * self.radius**2 #计算面积

    @property
    def perimeter(self):
        return 2*math.pi*self.radius #计算周长

c=Circle(10)
print(c.radius)
print(c.area) #可以像访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值
print(c.perimeter) #同上
'''
输出结果:
314.1592653589793
62.83185307179586
'''
#注意:此时的特性area和perimeter不能被赋值
c.area=3 #为特性area赋值
'''
抛出异常:
AttributeError: can't set attribute
'''
但是不能赋值

  将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则。

  对于obj.name我们想要在实际中会有需求能改他的值,实例.name='jeff'就是改self.__name。

  这就需要用到@name.setter,删除特性的方法@name.deleter。

class People:
    def __init__(self,name,SEX):
        self.name=name
        # self.__sex=SEX
        self.sex=SEX #self.sex='male'   p1.sex='male'
    @property
    def sex(self):
        return self.__sex #p1.__sex

    @sex.setter
    def sex(self,value):
        # print(self,value)
        if not isinstance(value,str):
            raise TypeError('性别必须是字符串类型')
        self.__sex=value  #p1.__sex='male'
    @sex.deleter
    def sex(self):
        del self.__sex #del p1.__sex

p1=People('cobila','male')
p1.sex='female'
print(p1.sex)
特性方法的修改和删除

  类方法

class people:
    country = 'china'
    # 类方法,用classmethod来进行修饰
    @classmethod
    def getCountry(cls):  #类方法自动将类作为cls传递进函数内
        return cls.country
    @classmethod
    def setCountry(cls, country):
        cls.country = country
p = people()
print(p.getCountry())  # 可以用过实例对象引用
print(people.getCountry())  # 可以通过类对象引用
p.setCountry('japan')
print(p.getCountry())
运行结果:
china
china
japan

  静态方法

class people:
    country = 'china'
    @staticmethod
    # 静态方法使用装饰器方式绑定,并且作用和普通函数一样并不会自动传递self
    def getCountry():
        return people.country
print(people.getCountry())
运行结果:
china

  普通方法 默认有一个self对象传进来,并且只能被对象调用——绑定到对象

  类方法 默认有一个cls传进来表示本类,并且可以被类和对象(不推荐)调用——绑定到类

  静态方法 没有默认参数,并且可以被类和对象(不推荐)调用——非绑定

面向对象三大特性

  封装

  封装顾名思义就是将东西装起来然后留出接口供用户使用,不需要知道内部发生了什么,只需要知道接口如何调用即可。在此之前所说的__init__定义的普通字段的赋值,并提供普通方法作为调用接口就是python中的封装结构。除此之外,私有属性也是封装的一种方法之一。

  好处:将变化隔离; 

  便于使用;

  提高复用性; 

  提高安全性;

  封装原则:

  将不需要对外提供的内容都隐藏起来;

   把属性都隐藏,提供公共方法对其访问。

  多重封装:

#创建类
class SQL:
    def __init__(self,name,passwd):
        self.name = name
        self.passwd = passwd
    def create(self,sql):
        print(sql)

class test:
    def __init__(self,name,obj):
        self.name = name
        self.obj = obj

    def add(self,arg):
        print(arg)

class test2:
    def __init__(self,obj):
        self.obj = obj
    def iner(slef,arg):
            print(arg)

#创建对象
c1 = SQL('fuzj','12313')
c2 = test('aaa',c1)   #把c1对象封装到c2对象里,c2对象会有c1对象的所有方法
c3 = test2(c2)          #把c2对象封装到c3对象中,c3对象会有c2对象的所有方法,同时也就有了c1对象的所有方法

#调用
c1.create("c1调用自身create方法")
c2.obj.create('c2调用c1的create方法')
c3.obj.add('c3调用c2的add方法')
c3.obj.obj.create('c3调用c1的create方法')

结果:
c1调用自身create方法
c2调用c1的create方法
c3调用c2的add方法
c3调用c1的create方法
多重封装

  继承

  对于面向对象的继承来说,其实就是将多个类共有的方法提取到父类中,子类仅需继承父类而不必一一实现每个方法。

class ParentClass1: #定义父类
    pass

class ParentClass2: #定义父类
    pass

class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass
    pass

class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类
    pass
继承

  一个类可以继承多个父类(基类,超类),这点与其他语言略有不同,当类是经典类时,多继承情况下,会按照深度优先方式查找;当类是新式类时,多继承情况下,会按照广度优先方式查找。

class D:
    def bar(self):
        print('D.bar')
class C(D):
    def bar(self):
        print('C.bar')
class B(D):
    def bar(self):
        print('B.bar')
class A(B, C):
    def bar(self):
        print('A.bar')
a = A()
# 经典类执行bar方法时
# 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错
# 所以,查找顺序:A --> B --> D --> C
# 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
a.bar()
#新式类执行bar方法时的查找顺序为A --> B --> C --> D
class Hero:#这个英雄类包含了所有英雄都有的属性和方法
    def __init__(self, nickname,
                 aggressivity,
                 life_value):
        self.nickname = nickname
        self.aggressivity = aggressivity
        self.life_value = life_value

    def attack(self, enemy):
        enemy.life_value -= self.aggressivity

class Garen(Hero):#盖伦继承了英雄类
    camp='Demacia'
    def attack(self, enemy):
        pass
    def fire(self):#盖伦独有的喷火技能
        print('%s is firing' %self.nickname)
class Riven(Hero):
    camp='Noxus'
g1=Garen('garen',18,200)
r1=Riven('rivren',18,200)
# print(g1.camp)
# print(r1.camp)
# g1.fire()
g1.attack(g1)
瑞文大战德玛之继承
class Animal:      #父类  
    def __init__(self,name,life_value,aggr):
        self.name = name
        self.life_value = life_value
        self.aggr = aggr  #攻击力
    def eat(self):
        self.life_value += 10

class Person(Animal):  #子类 
    def __init__(self,money,name,life_value,aggr):
        super().__init__(name,life_value,aggr)#super调用父类的构造方法
        self.money = money   #派生属性

    def attack(self,enemy):    #人的派生方法
        enemy.life_value -= self.aggr

class Dog(Animal): #派生子类
    def __init__(self,breed,name,life_value,aggr):
        #Animal.__init__(self,name,life_value,aggr)   #让子类执行父类的方法,就是父类名.方法名(参数),连self也得传就是super()
        super().__init__(name,life_value,aggr)  #super关键字——新式类
        #super(Dog,self).__init__(name,life_value,aggr)  #super关键字——新式类
        self.breed = breed
    def bite(self,person):   #狗的派生方法
        person.life_value -= self.aggr

    def eat(self):       # 父类方法的重写
        super().eat()
        print('dog is eating~~~ ')

ha2 = Dog('牛头梗','旺财',20000,100)
print(ha2.life_value)
ha2.eat()
print(ha2.life_value)
# super(Dog,ha2).eat()  #调用父类的
print(ha2.life_value)
人狗大战之派生
class Hero:
    def __init__(self, nickname, aggressivity, life_value):
        self.nickname = nickname
        self.aggressivity = aggressivity
        self.life_value = life_value
    def attack(self, enemy):
        print('Hero attack')
        enemy.life_value -= self.aggressivity
# print(Hero.__init__)
# print(Hero.attack)
class Garen(Hero):
    camp = 'Demacia'
    def __init__(self, nickname, aggressivity, life_value, script):
        Hero.__init__(self,nickname,aggressivity,life_value)
        # self.nickname = nickname
        # self.aggressivity = aggressivity
        # self.life_value = life_value
        self.script = script
    def attack(self, enemy):  # self=g1,enemy=r1
        # self.attack(enemy) #g1.attack()
        Hero.attack(self, enemy)
        print('from garen attack')
    def fire(self):
        print('%s is firing' % self.nickname)
# g1=Garen('garen',18,200) #Garen.__init__(g1,'garen',18,200)
g1=Garen('garen',18,200,'人在塔在') #Garen.__init__(g1,'garen',18,200)
print(g1.script)
继承更加精简了代码

  多态

  多态即多种形态,在运行时确定其状态,在编译阶段无法确定其类型,这就是多态。Python中的多态和Java以及C++中的多态有点不同,Python中的变量是弱类型的,在定义时不用指明其类型,它会根据需要在运行时确定变量的类型(个人觉得这也是多态的一种体现),并且Python本身是一种解释性语言,不进行预编译,因此它就只在运行时确定其状态,故也有人说Python是一种多态语言。

  组合

  与继承不同,组合不是什么是什么的关系,而是一种什么有什么的关系,比如学生有课程,他们是一种并行的关系而不是可以继承的关系。

class Student:
    def __init__(self,ID,name,sex):
        self.id=ID
        self.name=name
        self.sex=sex
        self.course_list=[]
class Course:
    def __init__(self,name,price,period):
        self.name=name
        self.price=price
        self.period=period

s1=Student('123123123123','cobila','female')

python_obj=Course('python',1,'7m')
linux_obj=Course('linux',1,'2m')

s1.course_list.append(python_obj)
s1.course_list.append(linux_obj)
print(s1.course_list[0].name)
print(s1.course_list[0].price)
运行结果:
python
1

  关于组合的问题看到一个哥们在博客里的人狗大战的代码,与德玛西亚大战瑞文一样,可以有一个装备类,装备有技能,也有属性,可以通过购买装备组合,也就是人与狗的实例化对象都可以拥有装备,装备加属性加技能再进行交互。

class Person:  # 定义一个人类
    '''
        这是一个游戏里人物的数据类型
    '''
    role = 'person'  # 人的角色属性都是人
    def __init__(self, name, aggressivity, life_value):
        self.name = name  # 每一个角色都有自己的昵称;
        self.aggressivity = aggressivity  # 每一个角色都有自己的攻击力;
        self.life_value = life_value  # 每一个角色都有自己的生命值;
    def attack(self,dog):
        # 人可以攻击狗,这里的狗也是一个对象。
        dog.life_value -= self.aggressivity
        print("{0}打了{1}一下,{1}剩余血量{2}".format(self.name, dog.name, dog.life_value))

class Dog:  # 定义一个狗类
    '''
        这是一个游戏里狗的数据类型
    '''
    role = 'dog'  # 狗的角色属性都是狗
    def __init__(self, name, breed, aggressivity, life_value):
        self.name = name  # 每一只狗都有自己的昵称;
        self.breed = breed  # 每一只狗都有自己的品种;
        self.aggressivity = aggressivity  # 每一只狗都有自己的攻击力;
        self.life_value = life_value  # 每一只狗都有自己的生命值;
    def bite(self,people):
        # 狗可以咬人,这里的狗也是一个对象。
        people.life_value -= self.aggressivity
        print("{0}咬了{1}一下,{1}剩余血量{2}".format(self.name,people.name,people.life_value))

class Weapon:
    '''
        这是一个游戏里武器的数据类型
    '''
    def __init__(self,name, price, aggrev, life_value):
        self.name = name    #武器名称
        self.price = price  #武器价格
        self.aggrev = aggrev    #武器伤害加成
        self.life_value = life_value    #武器血量加成

    def update(self, obj):  #obj就是要带这个装备的人
        obj.money -= self.price  # 用这个武器的人花钱买所以对应的钱要减少
        obj.aggressivity += self.aggrev  # 带上这个装备可以让人增加攻击
        obj.life_value += self.life_value  # 带上这个装备可以让人增加生命值

    def prick(self, obj):  # 这是该装备的主动技能,绞龙
        obj.life_value -= 3000  # 假设攻击力是3000
        print("{0}发动主动技:蛟龙==>{1}剩余血量{2}".format(self.name, obj.name, obj.life_value))


a = Person("苍井井",10,1000)
b = Dog("egon","狼狗",200,20000)
c = Weapon("蛟龙鞭",1000,40,2000)
a.money = 2000

#判断是否买的起武器
if a.money > c.price :
    c.update(a)
    a.weapon = c

#大战开始
while True :
    a.attack(b)
    if b.life_value <= 0 :
        print(b.name + "" + a.name + "打死了!")
        break
    a.weapon.prick(b)
    if b.life_value <= 0 :
        print(b.name + "" + a.name + "绞死了!")
        break
    b.bite(a)
    if a.life_value <= 0 :
        print(a.name+""+b.name+"咬死了!")
        break
人狗大战实例

  接口归一化设计

  父类中定义不操作,子类进行定义的衍生(子类都有相同功能,并且功能操作可能不一样)。如果子类没有定义或者也pass了,那这个方法就没有实际的意义了,为了防止这种现象的出现要采取一些措施。

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('进程数据的读取方法')



t1=Txt()
s1=Sata()
p1=Process()



t1.read()
t1.write()

s1.read()
s1.write()

p1.read()
p1.write()
可能需要的场景
class Animal:
    def run(self):
        raise AttributeError('子类必须实现这个方法')
    def speak(self):
        raise AttributeError('子类必须实现这个方法')
class People(Animal):
    def run(self):
        print('人正在走')

    # def speak(self):
    #     print('说话')

class Pig(Animal):
    def run(self):
        print('pig is walking')
    def speak(self):
        print('哼哼哼')

peo1=People()
# peo1.run()
peo1.speak()#异常咯
low主动抛异常

  这使得虽然大家的操作都不一样,但是调用的时候方法都是一样的,这就是归一化设计。

  这里存在一个问题,我们需要进行拓展,如果说,我们的子类没有进行父类的某一项普通方法的定义,根据广度优先的查找顺序,会在父类中找到此方法,但是此方法没有任何定义,自然这个操作毫无意义,所以我们可以在父类进行raise主动将异常抛出,或者使用abc模块自动抛出异常。

import abc
#抽象类:本质还是类,与普通类额外的特点的是:加了装饰器的函数,子类必须实现他们
class Animal(metaclass=abc.ABCMeta):
    tag='123123123123123'
    @abc.abstractmethod
    def run(self):
        pass
    @abc.abstractmethod
    def speak(self):
        pass
class People(Animal):
    # def run(self):  #子类没有定义父类有的方法,但是没有搜寻父类的方法直接报错
    #     pass
    def speak(self):
        pass

peo1=People()
print(peo1.tag)
运行结果:
TypeError: Can't instantiate abstract class People with abstract methods run

   注意:抽象类不能被实例化

super()函数的用法

  super在python2中的用法:

  1:super(自己的类,self).父类的函数名字

  2:super只能用于新式类

class People(object):
    def __init__(self,name,sex,age):
        self.name=name
        self.age=age
        self.sex=sex
    def walk(self):
        print('%s is walking' %self.name)
class Chinese(People):
    country='China'
    def __init__(self,name,sex,age,language='Chinese'):
        # self.name=name
        # self.sex=sex
        # self.age=age
        # People.__init__(self,name,sex,age)
        super(Chinese,self).__init__(name,sex,age)
        self.language=language
c=Chinese('egon','male',18)
print c.name,c.age,c.sex,c.language
py2super
class People:
    def __init__(self,name,sex,age):
        self.name=name
        self.age=age
        self.sex=sex
    def walk(self):
        print('%s is walking' %self.name)
class Chinese(People):
    country='China'
    def __init__(self,name,sex,age,language='Chinese'):
        # self.name=name
        # self.sex=sex
        # self.age=age
        # People.__init__(self,name,sex,age)
        super().__init__(name,sex,age)
        self.language=language
    def walk(self,x):
        super().walk()
        print('子类的x',x)
c=Chinese('egon','male',18)
# print(c.name,c.age,c.sex,c.language)
c.walk(123)
py3super

  __str__

  _str__定义在类内部,必须返回一个字符串类型,打印由这个类产生的对象时,会触发执行.

class People:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def __str__(self):
        return '<name:%s,age:%s>' %(self.name,self.age)

p1=People('egon',18)
print(p1)
str(p1) #此时print触发----->p1.__str__()
#所以打印结果为<name:egon,age:18>
__str__
原文地址:https://www.cnblogs.com/Jeffding/p/7376515.html