Python入门教程[4]-面向对象编程

王 茂南 2018年2月24日06:27:27
评论
1 8307字阅读27分41秒
摘要这一篇文章会介绍面向对象的思想在python中的应用,面向对象在python的实际应用中还是用得很广泛的,如果之前没有接触过面向对象,可以先看一下,然后在以后的学习中在慢慢理解其中的思想。

了解面向对象的编程

讲之前推荐一篇文章,面向对象圣经。可以把这一篇学完之后再回过去看一下,讲得还是很好的。下面简单讲一下面向对象编程的思想(这里的知识点也是常看常新)。

以面向过程思想设计程序时,程序是一条条指令的顺序执行,当指令变得多起来时,它们被分隔成我们先前实验中讲解过的函数。

而面向对象思想则是对象视为程序的组成单元,程序的执行通过调用对象提供的接口完成。面向对象的四个核心概念:抽象、封装、继承、多态

下面讲一下这四个概念在 Python 中的应用。不完全按照这四个核心概念来讲。

参考资料

 

抽象,封装

我们先看一个简单的类的例子

#创建类
class Foo(object):
    def __init__(self,name):
        self.name = name
    def Bar(self):
        print('Bar')
    def Hello(self):
        print('i am %s' %self.name)

# 根据类Foo创建对象obj
obj = Foo('Dog')
obj.Bar() #执行Bar方法
>> 'Bar'
obj.Hello() #执行Hello方法
>> 'i am Dog'
print(dir(obj))
>> #这个可以看这个对象的方法

上面这个类中,object是Python中所有对象的祖先,它是所有类的基类。

类需要一个初始化方法__init__是Python的初始化方法,注意前后各有两个下划线 _,self指代当前的对象。所以在类的实例化的时候,需要Foo('Dog'),之后在类中就是要self来间接调用被封装的内容。

为了看出类有什么用处,我们来看一个练习:游戏人生程序

1、创建三个游戏人物,分别是:

苍井井,女,18,初始战斗力1000
东尼木木,男,20,初始战斗力1800
波多多,女,19,初始战斗力2500

2、游戏场景,分别:

草丛战斗,消耗200战斗力
自我修炼,增长100战斗力
多人游戏,消耗500战斗力

可以看到这个例子中需要创建一个类,初始化的时候需要名字,性别,年龄和战斗力。然后这个类里面有三个方法,分别是草丛战斗,自我修炼和多人游戏,可以参考下面的代码。

class practice_02(object):
    def __init__(self,name,gender,age,combat_effectiveness):
        self.name = name
        self.gender = gender
        self.age = age
        self.combat_effectiveness = combat_effectiveness
    @property
    def print_message(self):
        print('%s,%s,%s,战斗力为%s' %(self.name,self.gender,self.age,self.combat_effectiveness))
    def grass_battle(self):
        self.combat_effectiveness = self.combat_effectiveness-200
    def self_practice(self):
        self.combat_effectiveness = self.combat_effectiveness+100
    def multiplayer_game(self):
        self.combat_effectiveness = self.combat_effectiveness-500

player = practice_02('仓井井','女','18',1000)
player.print_message
player.grass_battle()#参加一次草丛战斗
player.print_message
player.self_practice()#参加一次自我修炼
player.print_message
player.multiplayer_game()#参加一次多人游戏
player.print_message

继承

继承,面向对象中的继承和现实生活中的继承相同,即:子可以继承父的内容。

例如:

  • 猫可以:喵喵叫、吃、喝、拉、撒

  • 狗可以:汪汪叫、吃、喝、拉、撒

如果我们要分别为猫和狗创建一个类,那么就需要为实现他们所有的功能,如下所示:

class 猫:
    def 喵喵叫(self):
        print '喵喵叫'
    def 吃(self):
        # do something
    def 喝(self):
        # do something
    def 拉(self):
        # do something
    def 撒(self):
        # do something

class 狗:
    def 汪汪叫(self):
        print '喵喵叫'
    def 吃(self):
        # do something
    def 喝(self):
        # do something
    def 拉(self):
        # do something
    def 撒(self):
        # do something

上述代码不难看出,吃、喝、拉、撒是猫和狗都具有的功能,而我们却分别的猫和狗的类中编写了两次。所以有了继承的概念,我们从猫和狗身上提取共同的特点,来进行编写,如下所示:

class Animal(object):
    def eat(self):
        print('%s 吃' %self.name)
    def drink(self):
        print('%s 喝' %self.name)
    def shit(self):
        print('%s 拉' %self.name)
    def pee(self):
        print('%s 撒' %self.name)

class Cat(Animal):
    def __init__(self,name):
        self.name = name
    def cry(self):
        print('喵喵叫')

cat = Cat('小猫猫')
cat.eat()
cat.cry()
cat.pee()

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

静态变量

静态变量是可以直接从类访问的,不需要实例化对象就可以访问。还是以上面动物的例子为例,假如说那些动物都来自动物园('zoo'),我们可以在Animal中加一个静态变量,一般声明在__init__前面。

class Animal(object):
    owner = 'zoo'
    def eat(self):
        print('%s 吃' %self.name)
    def drink(self):
        print('%s 喝' %self.name)
    def shit(self):
        print('%s 拉' %self.name)
    def pee(self):
        print('%s 撒' %self.name)

class Cat(Animal):
    def __init__(self,name):
        self.name = name
    def cry(self):
        print('喵喵叫')

cat = Cat('小猫猫')
print(cat.owner)
>> 'zoo'
print(Animal.owner) #不需要实例化,可以直接访问
>> 'zoo'

类方法

类方法和静态变量类似,它也可以通过类名直接访问,类方法用@classmethd装饰,类方法中可以访问类的静态变量。

class Animal(object):
    owner = 'zoo'
    def owner_name(cls):
        return cls.owner
    def eat(self):
        print('%s 吃' %self.name)
    def drink(self):
        print('%s 喝' %self.name)
    def shit(self):
        print('%s 拉' %self.name)
    def pee(self):
        print('%s 撒' %self.name)

class Cat(Animal):
    def __init__(self,name):
        self.name = name
    def cry(self):
        print('喵喵叫')

cat = Cat('小猫猫')
print(cat.owner_name())
>> 'zoo'
print(Animal.owner_name(Animal))
>> 'zoo'

注意类方法的第一个参数传入的是类对象,而不是实例对象,所以是 cls

我们再来看一个例子

class Foo(object):
    def __init__(self,name):
        self.name = name
    def ord_func(self):
        #定义普通方法
        print('普通方法')
    @classmethod
    def class_func(cls):
        #定义类方法
        print('类方法')
    @staticmethod
    def static_func():
        #定义静态方法
        print('静态方法')

f = Foo('江苏')
f.ord_func()
>> '普通方法'
#调用类方法
Foo.class_func()
>> '类方法'
#调用静态方法
Foo.static_func()
>> '静态方法'

property

在Python中,property可以将方法变成一个属性来使用,借助property可以实行Python风格的getter/setter,即可以通过property获得和修改对象的某一个属性。

我们就看下面的例子里理解吧。

class goods(object):
    def __init__(self):
        #原价
        self.original_price = 100
        #折扣
        self.discount = 0.8

    @property
    def price(self):
        #实际价格=原价*折扣
        new_price = self.original_price*self.discount
        return new_price

    @price.setter
    def price(self,value):
        self.original_price = value
    @price.deleter
    def price(self,value):
        del self.original_price

obj = goods()
print(obj.price) #查看商品的价格
>> 80
obj.price = 200 #设置商品的价格
print(obj.price) #查看现在商品的价格
>> 160
#del obj.price 删除商品价格

初始化函数__init__

当出现继承的情况的时候, 一定要注意初始化函数的使用. 第一种情况是, 子类没有定义初始化函数, 父类的初始化函数会被调用.

在下面的例子中, 我们在父类中定义了初始化函数, 但是在子类Child中没有定义. 于是在初始化子类的时候, 我们会调用父类的初始化函数, 所以在子类中需要传入参数.

  1. #定义父类:Parent
  2. class Parent(object):
  3.     def __init__(self, name):
  4.         self.name = name
  5.         print("create an instance of:", self.__class__.__name__)
  6.         print("name attribute is:", self.name)
  7. #定义子类Child ,继承父类Parent       
  8. class Child(Parent):
  9.     pass
  10. c = Child(name="init Child")
  11. print(c.name)
  12. """
  13. create an instance of: Child
  14. name attribute is: init Child
  15. init Child
  16. """

第二种情况是, 子类定义了自己的初始化函数, 而在子类中没有显示调用父类的初始化函数, 则父类的属性不会被初始化. 在下面代码中, 因为我们在子类中定义了初始化函数, 所以不会调用父类中的初始化函数, 所以没有name属性.,

  1. #定义父类:Parent
  2. class Parent(object):
  3.     def __init__(self, name):
  4.         self.name = name
  5.         print("create an instance of:", self.__class__.__name__)
  6.         print("name attribute is:", self.name)
  7. #定义子类Child ,继承父类Parent       
  8. class Child(Parent):
  9.     #子类中没有显示调用父类的初始化函数
  10.     def __init__(self):
  11.         print("call __init__ from Child class")
  12. c = Child()
  13. """
  14. all __init__ from Child class
  15. """
  16. print(c.name) # 此时会报错

第三种情况是, 如果子类定义了自己的初始化函数, 显示调用父类, 子类和父类的属性都会被初始化.

  1. class Parent(object):
  2.     def __init__(self, name):
  3.         self.name = name
  4. class Child(Parent):
  5.     def __init__(self):
  6.         super(Child,self).__init__(name='123')   #要将子类Child和self传递进去
  7. c = Child()
  8. print(c.name)
  9. """
  10. 123
  11. """

上面我们通过super的方式来传入参数.

 

Super的详细说明

super主要来调用父类方法. 在子类中, 一般会定义与父类相同的属性(数据属性, 方法), 从而来实现子类特有的行为.

例如下面子类重写了父类的函数, 但是又同时想调用父类的函数, 这个时候需要通过父类名来进行调用. 需要将self显式的传递进去.

  1. class Parent(object):
  2.     Value = "Hi, Parent value"
  3.     def fun(self):
  4.         print("This is from Parent")
  5. class Child(Parent):
  6.     Value = "Hi, Child  value"
  7.     def fun(self):
  8.         print("This is from Child")
  9.         Parent.fun(self)   #调用父类Parent的fun函数方法
  10. c = Child()
  11. c.fun()
  12. """
  13. This is from Child
  14. This is from Parent
  15. """

这种方式有一个不好的地方就是, 需要经父类名硬编码到子类中, 为了解决这个问题, 可以使用Python中的super关键字:

  1. class Parent(object):
  2.     Value = "Hi, Parent value"
  3.     def fun(self):
  4.         print("This is from Parent")
  5. class Child(Parent):
  6.     Value = "Hi, Child  value"
  7.     def fun(self):
  8.         print("This is from Child")
  9.         #Parent.fun(self)
  10.         super(Child,self).fun()  #相当于用super的方法与上一调用父类的语句置换
  11. c = Child()
  12. c.fun()
  13. """
  14. This is from Child
  15. This is from Parent
  16. """

我们再看一下与初始化函数结合进行使用的例子. 我们使用super之后调用父类中的初始化函数, 此时value的值是父类的值. 接着我们重新赋值, 打印的值就是子类的值.

  1. class Parent(object):
  2.     def __init__(self):
  3.         self.value = "Hi, Parent value"
  4. class Child(Parent):
  5.     def __init__(self):
  6.         super(Child, self).__init__()
  7.         print(self.value) # 此时是父类中自定义的值
  8.         self.value = "Hi, Child  value"
  9.         print(self.value) # 此时是子类中的值
  10. c = Child()
  11. """
  12. Hi, Parent value
  13. Hi, Child  value
  14. """

 

__call__方法

关于call的详细说明, 可以查看链接(下面是一些我的简单理解)Python call详解

关于__call__方法, 不得不先提到一个概念, 就是可调用对象(callable). 我们平时自定义的函数, 内置函数和类都属于可调用对象.

但凡是可以把一对括号()应用到某个对象身上都可称之为可调用对象, 判断对象是否为可调用对象可以用函数callable.

call方法可以把类实例当做函数调用。__call__在那些类的实例经常改变状态的时候会非常有效。我们可以看下面的一个例子. 使用call去改变实例里面的参数.

  1. class X(object):
  2.     def __init__(self, a, b):
  3.         self.a = a
  4.         self.b = b
  5.     def __call__(self, a, b):
  6.         print('call')
  7.         self.a = a
  8.         self.b = b

在实例初始化的时候, 会初始化a和b. 接着我们可以像使用函数一样修改a和b的值.

  1. x = X(2,3)
  2. print(x.a, x.b)
  3. # >> 2 3
  4. x(4,5) # 像函数一样调用
  5. print(x.a, x.b)
  6. # >> call
  7. # >> 4 5

 

类的特殊成员__doc__

这里我们讲一个类的特殊成员,__doc__,这个是输出类的描述信息,什么意思呢,可以看下面的例子。

class describe(object):
    """可以显示类的说明信息
    """
    def __init__(self,name):
        self.name = name
    def speak_name(self):
        return self.name

des = describe('dog')
des.__doc__
>> '可以显示类的说明信息\n'

所以说我们可以使用__doc__来看一下类的说明的文档。

 

类的特殊成员__repr__

repr是用来指定输出的格式的。

我们可以看一下下面的这个例子,如果直接打印类,打印出来的是地址,这个时候我们可以使用repr来定义输出的格式。

  1. In [1]: class Test(object):
  2.    ...:     def __init__(self, value='hello, world!'):
  3.    ...:         self.data = value
  4.    ...:
  5. In [2]: t = Test()
  6. In [3]: t
  7. Out[3]: <__main__.Test at 0x108844e48>
  8. In [4]: class TestNew(object):
  9.    ...:     def __init__(self, value='hello, world!'):
  10.    ...:         self.data = value
  11.    ...:     def __repr__(self):
  12.    ...:         return 'This is __repr__:{0}'.format(self.data)
  13.    ...:
  14. In [5]: tn = TestNew()
  15. In [6]: tn
  16. Out[6]: This is __repr__:hello, world!

类的特殊成员还有__init__,__dict__(返回类对象中的所有成员)等等,详细的可以去看说明文档。

 

更多入门教程链接

关于更多入门教程, 可以通过下面的链接查看.

  • 微信公众号
  • 关注微信公众号
  • weinxin
  • QQ群
  • 我们的QQ群号
  • weinxin
王 茂南
  • 本文由 发表于 2018年2月24日06:27:27
  • 转载请务必保留本文链接:https://mathpretty.com/8952.html
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: