5.python面向对象

2020/02/21 posted in  基础

匿名函数

顾名思义就是函数没有名字,使用lambda关键字定义的函数就是匿名函数,简化代码,增加运行效率。 特点: 匿名函数只适合做一下简单的操作,返回值不需要加上return。

#普通函数
def func(a,b,c):
    return a+b+c
print(func(1,3,4))
8
#匿名函数
result=(lambda a,b,c:a+b+c)(1,3,4)
# 匿名函数也是函数
print(result)
8
# 对字典列表排序的时候还可以使用匿名函数
my_list={'age':23,'name':'张三'}
def get_value(item):
    return item['age']

get_value(my_list)

# my_list.sort(key=get_value,reverse=True)print(my_list)
23

面向对象的编程

面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。

面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。

面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。

在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。

我们以一个例子来说明面向过程和面向对象在程序流程上的不同之处。

假设我们要处理学生的成绩表,为了表示一个学生的成绩,面向过程的程序可以用一个dict表示:

std1 = { 'name': 'Curry', 'score': 98 }
std2 = { 'name': 'James', 'score': 81 }

而处理学生成绩可以通过函数实现,比如打印学生的成绩:

def print_score(std):
    print('%s: %s' % (std['name'], std['score']))

如果采用面向对象的程序设计思想,我们首选思考的不是程序的执行流程,而是Student这种数据类型应该被视为一个对象,这个对象拥有name和score这两个属性(Property)。如果要打印一个学生的成绩,首先必须创建出这 个学生对应的对象,然后,给对象发一个print_score消息,让对象自己把自己的数据打印出来。

class Student(object):
    def init (self, name, score): 
        self.name = name
        self.score = score

    def print_score(self):
        print('%s: %s' % (self.name, self.score))

给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法(Method)。面向对象的程序写出来就像这样:

bart = Student('Bart Simpson', 59) 
lisa = Student('Lisa Simpson', 87) 
bart.print_score() 
lisa.print_score()

面向对象的设计思想是从自然界中来的,因为在自然界中,(Class)和实例(Instance)的概念是很自然的。

Class是一种抽象概念,比如我们定义的Class——Student,是指学生这个概念,而实例(Instance则是一个个具 体的Student,比如,Bart Simpson和Lisa Simpson是两个具体的Student。

所以,面向对象的设计思想是抽象出Class,根据Class创建Instance。

面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。

类和对象

面向对象编程的2个非常重要的概念:类和对象

对象是面向对象编程的核心,在使用对象的过程中,为了将具有共同特征和行为的一组对象抽象定义,提出了另外一个新的概念——

就相当于制造飞机时的图纸,用它来进行创建的飞机就相当于对象

补充:

  • 需求:做一道西红柿炒鸡蛋

方法1:按步骤一步一步制作(面向过程)——[将每个过程打包成函数,需要时依次调用]

方法2:制作一台机器人,告诉机器人炒菜(面向对象)——[打包成类与对象]

作为母版制作出很多对象

人以类聚物以群分。 具有相似内部状态和运动规律的实体的集合(或统称为抽象)。 具有相同属性行为事物的统称类是抽象的,在使用的时候通常会找到这个类的一个具体的存在,使用这个具体的存在。一个类可以找到多个对象

对象

某一个具体事物的存在 ,在现实世界中可以是看得见摸得着的。 可以是直接使用的

类和对象之间的关系

定义类和创建对象

定义一个类,格式如下:

class 类名:

方法列表

# class Hero: # 经典类(旧式类)定义形式
# class Hero():

class Hero(object): # 新式类定义形式
    def info(self): 
        print("hero")

说明:

  • 定义类时有2种形式:新式类和经典类,上面代码中的Hero为新式类,前两行注释部分则为经典类;

  • object 是Python 里所有类的最顶级父类; 类名 的命名规则按照"大驼峰命名法";

  • info 是一个实例方法,第一个参数一般是self,表示实例对象本身,当然了可以将self换为其它的名字,其作用是一个变量 这个变量指向了实例对象.

  • python中,可以根据已经定义的类去创建出一个或多个对象。

创建对象的格式为

对象名1 = 类名()

对象名2 = 类名()

对象名3 = 类名()

class Hero(object): # 新式类定义形式
    '''info 是一个实例方法,类对象可以调用实例方法,实例方法的第一个参数一定是self'''
    def info(self):
        '''当对象调用实例方法时,Python会自动将对象本身的引用做为参数, 传递到实例方法的第一个参数self里'''
        print(self)
        print("self各不同,对象是出处。")

# Hero这个类 实例化了一个对象
hero = Hero()

# 对象调用实例方法info(),执行info()里的代码
# . 表示选择属性或者方法
hero.info()
print(hero) # 打印对象,则默认打印对象在内存的地址,结果等同于info里的print(self)
<__main__.Hero object at 0x0000000005413EF0>
self各不同,对象是出处。
<__main__.Hero object at 0x0000000005413EF0>

补充:类与对象

#类的使用
class 成绩单():
    @classmethod#类的方法(函数)同类的属性(变量)结合
    #类的属性作为类方法的参数
    def 录入成绩单(cls):
        cls.学生姓名=input('请输入学生姓名:')
        cls.语文_成绩=int(input('请输入语文成绩:'))
        cls.数学_成绩=int(input('请输入数学成绩:'))
    @classmethod
    def 打印成绩单(cls):
        print(cls.学生姓名+'的成绩如下:')
        print('语文成绩:'+str(cls.语文_成绩))
        print('语文成绩:'+str(cls.语文_成绩))
    
成绩单.录入成绩单()
成绩单.打印成绩单()
请输入学生姓名:Ryan
请输入语文成绩:100
请输入数学成绩:99
Ryan的成绩如下:
语文成绩:100
语文成绩:100
#实例化
class 成绩单():
    #①
    def 录入成绩单(self):#②
        self.学生姓名=input('请输入学生姓名:')
        self.语文_成绩=int(input('请输入语文成绩:'))
        self.数学_成绩=int(input('请输入数学成绩:'))
#③
    def 打印成绩单(self):
        print(self.学生姓名+'的成绩如下:')
        print('语文成绩:'+str(self.语文_成绩))
        print('语文成绩:'+str(self.语文_成绩))

#④
成绩单1=成绩单()#实例化得到成绩单1
#调用实例化变量
成绩单1.录入成绩单()
成绩单1.打印成绩单()
请输入学生姓名:Ryan
请输入语文成绩:34
请输入数学成绩:53
Ryan的成绩如下:
语文成绩:34
语文成绩:34

对比分析

  • ①是空着的,意思是这里不再需要@classmethod的声明

  • 第②处,把cls替换成了 self.

  • 实例化后再使用的格式,需要先赋值然后再调用(第③处)在第④步骤, 我们需要用实例名=类(的方式(实例名其实就是任取一个变量名),为类创建一个实例, 然后再使用实例名函数0的方式调用对应的方法。

对象的属性和方法

添加和获取对象的属性

class Hero(object):
    """定义了一个英雄类,可以移动和攻击""" 
    def move(self):
        """实例方法"""
        print("正在前往事发地点...")
# 实例化了一个英雄对象

hero = Hero()
# 给对象添加属性,以及对应的属性值
hero.name = "德玛西亚" # 姓名
hero.hp = 2600 # 生 命 值
# 通过.成员选择运算符,获取对象的属性值
print("英雄 %s 的生命值 :%d" % (hero.name, hero.hp))

# 通过.成员选择运算符,获取对象的实例方法
hero.move()

英雄 德玛西亚 的生命值 :2600
正在前往事发地点...

通过self获取对象属性

 class Hero(object):
    """定义了一个英雄类,可以移动和攻击"""
    def move(self):
        """实例方法"""
        print("正在前往事发地点...")

    def info(self):
        """在类的实例方法中,通过self获取该对象的属性"""
        print("英雄 %s 的生命值 :%d" % (self.name, self.hp))

# 实例化了一个英雄对象
 hero = Hero()

# 给对象添加属性,以及对应的属性值
hero.name = "德玛西亚" # 姓名
hero.hp = 2600 # 生 命 值

# 通过.成员选择运算符,获取对象的实例方法

hero.info() # 只需要调用实例方法info(),即可获取英雄的属性
hero.move()

英雄 德玛西亚 的生命值 :2600
正在前往事发地点...

init魔法方法

init方法

class Hero(object):
    """定义了一个英雄类,可以移动和攻击"""
#Python 的类里提供的,两个下划线开始,两个下划线结束的方法,就是魔法方法, init ()就是一个魔法方法, 通常用来做属性初始化 或 赋值 操作。

# 如果类面没有写__init__方法,Python会自动创建,但是不执行任何操作,

# 如果为了能够在完成自己想要的功能,可以自己定义__init__方法,

# 所以一个类里无论自己是否编写 __init__方法 一定有__init__方法。

    def __init__(self):
        """ 方法,用来做变量初始化 或 赋值 操作,在类实例化对象的时候,会被自动调用""" 
        self.name = "hero" # 姓 名
        self.hp = 2600 # 生命值
    def move(self):
        """实例方法"""
        print("正在前往事发地点...")


# 实例化了一个英雄对象,并自动调用 init ()方法
hero = Hero()

# 通过.成员选择运算符,获取对象的实例方法
hero.info() # 只需要调用实例方法info(),即可获取英雄的属性
hero.move()

总结

  • __init__()方法,在创建一个对象时默认被调用,不需要手动调用
  • __init__(self)中的self参数,不需要开发者传递,python解释器会自动把当前的对象引用传递过去。

有参数的init()方法

class Hero(object):
    """定义了一个英雄类,可以移动和攻击"""
    def __init__(self, name, hp):
        """ init () 方法,用来做变量初始化 或 赋值操作"""
# 英雄名
        self.name = name
# 生命值:
        self.hp = hp

    def move(self):
        """实例方法"""
        print("%s 正在前往事发地点..." % self.name)
    def info(self):
        print("英雄 %s 的生命值 :%d" % (self.name, self.hp))



#实例化英雄对象时,参数会传递到对象的 init()方法里
blind = Hero('瞎哥',2600) 
gailun = Hero("盖伦",4200)

#print(gailun)

#print(blind)

  

# 不同对象的属性值的单独保存
print(id(blind.name)) 
print(id(gailun.name))

# 同一个类的不同对象,实例方法共享
print(id(blind.move())) 
print(id(gailun.move()))


87554072
87555744
瞎哥 正在前往事发地点...
2004853904
盖伦 正在前往事发地点...
2004853904

注意:

  • 通过一个类,可以创建多个对象,就好比 通过一个模具创建多个实体一样
  • __init__(self)中,默认有1个参数名字为self,如果在创建对象时传递了2个实参,那么init (self)中出了self作为第一个形参外还需要2个形参,例如 init (self,x,y)
  • 在类内部获取 属性 和 实例方法,通过self获取;
  • 在类外部获取 属性 和 实例方法,通过对象名获取。
  • 如果一个类有多个对象,每个对象的属性是各自保存的,都有各自独立的地址;
  • 但是实例方法是所有对象共享的,只占用一份内存空间。类会通过self来判断是哪个对象调用了实例方法。

继承

  • 在程序中,继承描述的是多个类之间的所属关系。
  • 如果一个类A里面的属性和方法可以复用,则可以通过继承的方式,传递到类B里。
  • 那么类A就是基类,也叫做父类;类B就是派生类,也叫做子类
# 父 类
class A(object):
    def __init__(self):
        self.num = 10
        
    def print_num(self):
        print(self.num + 10)

# 子 类
class B(A): 
    pass

b = B()
print(b.num) 
b.print_num()

10
20

单继承

子类只能继承一个父类

#定义一个Person类
class Person(object): 
    def __init__(self):
# 属 性
        self.name = "女娲"

    # 实例方法
    def make_person(self):
        print(" <%s> 造了一个人..." % self.name)

# 定义Teacher类,继承了 Person,则Teacher是子类,Person是父类。
class Teacher(Person):
# 子类可以继承父类所有的属性和方法,哪怕子类没有自己的属性和方法,也可以使用父类的属性和方法。
    pass

panda = Teacher() 
    # 创建子类实例对象
print(panda.name) 
    # 子类对象可以直接使用父类的属性
panda.make_person() 
    # 子类对象可以直接使用父类的方法
    

女娲
 <女娲> 造了一个人...

总结

  • 虽然子类没有定义 init 方法初始化属性,也没有定义实例方法,但是父类有。所以只要创建子类的对象,就默认执行了那个继承过来的 init 方法
  • 子类在继承的时候,在定义类时,小括号()中为父类的名字
  • 父类的属性、方法,会被继承给子类

多继承

子类继承多个父类

class Women(object): 
    def __init__(self):
        self.name = "女娲" # 实例变量,属性

    def make_person(self):          # 实例方法,方法
        print(" <%s> 造了一个人..." % self.name)
    
    def move(self):
        print("移动..")

class Man(object):
    def __init__(self):
        self.name = "亚当"

    def make_person(self):
        print("<%s> 造了一个人..." % self.name)

    def run(self):
        print("跑..")

class Person(Women, Man): # 多继承,继承了多个父类
    pass

ls = Person() 
print(ls.name) 
ls.make_person()

# 子类的魔法属性 mro 决定了属性和方法的查找顺序
print(Person. mro)

女娲
 <女娲> 造了一个人...
<built-in method mro of type object at 0x0000000004376698>

结论:

  • 多继承可以继承多个父类,也继承了所有父类的属性和方法

  • 注意:如果多个父类中有同名的 属性和方法,则默认使用第一个父类的属性和方法(根据类的魔法属性mro 的顺序来查找)

  • 多个父类中,不重名的属性和方法,不会有任何影响。

重写父类方法

子类继承父类,父类的方法满足不了子类的需要可以对父类的方法进行重写

**重写的特点: **

  1. 继承关系,
  2. 方法名相同
class Person(object): 
    def run(self):
        print("跑起来了")

class Student(Person):
    def __init__(self, name, age): 
        self.name = name
        self.age = age

# 因为父类的方法满足不了子类的需要,对其进行重写
    def run(self):
        print("%s跑起来了" % self.name)

stu = Student("王五", 10)

# 调用方法的时候先从本类去找,如果本来没有再去父类去找,会遵循mro的特点

stu.run()

王五跑起来了

属性方法

类属性和实例属性

类属性就是类对象所拥有的属性,它被所有类对象的实例对象所共有,在内存中只存在一个副本,这个和C++中类的静态成员变量有点类似。对于公有的类属性,在类外可以通过类对象和实例对象访问

class People(object):
    name = 'Tom'# 公有的类属性
    __age = 12# 私有的类属性

p = People() 
print(p.name)   # 正 确
print(People.name)  # 正 确
# print(p.__age)    # 错误,不能在类外通过实例对象访问私有的类属性
# print(People.__age) # 错误,不能在类外通过类对象访问私有的类属性实例属性(对象属性)

class People(object):
    address = '山东'  # 类属性
    def __init__(self):
        self.name = 'xiaowang'  # 实例属性
        self.age = 20   # 实例属性

p = People()
p.age = 12  # 实例属性
print(p.address)    # 正 确
print(p.name)       # 正 确
print(p.age)    # 正 确
print(People.address)   # 正 确
#print(People.name)     # 错 误
#print(People.age)  # 错 误

#通过实例(对象)去修改类属性
class People(object):
    country = 'china' #类属性

    
print(People.country) 
p = People() 
print(p.country) 

p.country = 'japan'
print(p.country)    # 实例属性会屏蔽掉同名的类属性
print(People.country)

del p.country # 删除实例属性
print(p.country)

Tom
Tom
山东
xiaowang
12
山东
china
china
japan
china
china

总结
如果需要在类外修改类属性,必须通过类对象去引用然后进行修改。如果通过实例对象去引用,会产生一个同名的实例属性,这种方式修改的是实例属性,不会影响到类属性,并且之后如果通过实例对象去引用该名称的属性,实例属性会强制屏蔽掉类属性,即引用的是实例属性,除非删除了该实例属性。

静态方法和类方法

1.类方法

是类对象所拥有的方法,需要用修饰器@classmethod来标识其为类方法,对于类方法,第一个参数必须是类对象, 一般以cls作为第一个参数(当然可以用其他名称的变量作为其第一个参数,但是大部分人都习惯以'cls'作为第一个参数的名字,就最好用'cls'了),能够通过实例对象和类对象去访问。

class People(object): 
    country = 'china'
#类方法,用classmethod来进行修饰
    @classmethod
    def get_country(cls): 
        return cls.country

p = People()
print(p.get_country())  #可以用过实例对象引用print(People.get_country())  #可以通过类对象引用类方法还有一个用途就是可以对类属性进行修改:

class People(object): 
    country = 'china'
#类方法,用classmethod来进行修饰
    @classmethod
    def get_country(cls): 
        return cls.country

    @classmethod
    def set_country(cls,country): 
        cls.country = country

p = People()
print(p.get_country())  #可以用过实例对象访问print(People.get_country())  #可以通过类访问

p.set_country('japan')

print(p.get_country()) 
print(People.get_country())
#结果显示在用类方法对类属性修改之后,通过类对象和实例对象访问都发生了改变

china
china
japan
japan

2. 静态方法

需要通过修饰器@staticmethod来进行修饰,静态方法不需要多定义参数,可以通过对象和类来访问。

class People(object): 
    country = 'china'
    @staticmethod #静态方法
    def get_country():
        return People.country

p = People()
# 通过对象访问静态方法
p.get_country()
# 通过类访问静态方法
print(People.get_country())

china

总结
从类方法和实例方法以及静态方法的定义形式就可以看出来,类方法的第一个参数是类对象cls,那么通过cls引用的 必定是类对象的属性和方法; 实例方法的第一个参数是实例对象self,那么通过self引用的可能是类属性、也有可能是实例属性(这个需要具体分析),不过在存在相同名称的类属性和实例属性的情况下,实例属性优先级更高。 静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类实例对象来引用

多态

  • 多态, 不同的子类对象调用相同的父类方法,产生不同的执行结果,可以增加代码的外部调用灵活度
  • 多态以继承和重写父类方法为前提
  • 多态是调用方法的技巧,不会影响到类的内部设计
class Animal(object): 
    def run(self):
        print('Animal is running...') 

        
class Dog(object):
    def run(self):
        print('Dog is running...') 
    
    
class Cat(object):
    def run(self):
        print('Cat is running...')

# 定义一个方法
def run_twice(animal): 
    animal.run() 
    animal.run()

dog = Dog() 
cat = Cat()

run_twice(dog) 
run_twice(cat)

Dog is running...
Dog is running...
Cat is running...
Cat is running...