学习笔记:Python3 面向对象

时间:2019-08-06
本文章向大家介绍学习笔记:Python3 面向对象,主要包括学习笔记:Python3 面向对象使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

仅为个人查阅使用,如有错误还请指正。

面向对象编程就是一种程序设计思想。把对象当作程序的基本单元,一个对象包含了数据和操作数据的函数

这种设计思想是从自然界中来的。因为在自然界中,类(Class)实例(Instance)的概念是很自然的。Class是一种抽象概念,比如我们定义一个运动员类:Class-Player,是指运动员这个概念,而实例(Instance)则是一个个具体的Player,比如:Jordan,Durant他们是具体的。

有编程经验的都知道,面向对象的三个特点:封装继承多态

  • 类和实例

    类是抽象的模板。比如Player类。实例是一个个具体的对象,每个对象都拥有相同的方法。

    • 定义类

      通过关键字class,后面紧跟类名(首字母大写),最后是(object)。可以表明他是承继哪个类,如果没有合适的,就选择object这个类,因为它是所以类的祖宗

      class Player(object):
          pass
    • 创建实例

      跟函数调用差不多,这里是类名()的操作。

      # 创建实例1
      player1 = Player()
      # 创建实例2
      player2 = Player()
    • 绑定属性

      通过创建__init__.py的方法,self代表实例本身。

      class Player(object):
      
          def __init__(self, name, age):
              self.name = name
              self.age = age

      获取属性的方式可以通过对象.属性

      # 创建实例1
      player1 = Player("Jordan", 45)
      # 获取属性
      name = player1.name
      age = player1.age
    • 封装

      现在,我们知道实例可以拥有这些属性。可以直接在类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。而该函数就是类的方法。

      实现一个打印姓名年龄的方法

      class Player(object):
      
          def __init__(self, name, age):
              self.name = name
              self.age = age
      
          def print_name_age(self):
              print("%s, %d"%(self.name, self.age))
      
      player1 = Player("Jack", "30")
      print(player1.print_name_age())

      从一个调用者来看,我只要在创建实例的时候输入nameage。并且调用该方法,获得我想要的。具体类里面怎么实现的,我不需要去管。

  • 访问限制

    在前面的类和实例中,我们了解到在类的内部,可以有属性和方法。并且外部代码还可以自由修改一个实例的属性。这样其实是不安全的,我们应该要避免这样的操作。也就是要把这变量从公有变到私有。

    • 属性私有化

      在Python中,使用两个下划线__就可以表示私有属性。继续改我们的Player这个类。

      class Player(object):
      
          def __init__(self, name, age):
              self.__name = name
              self.__age = age
      
          def print_name_age(self):
              return "%s, %d" % (self.__name, self.__age)
      
      
      player1 = Player("Jack", 30)
      print(player1.__name)   # ERROR 

      属性被私有化之后,就不能在获取了。

    • 获取,修改私有化属性

      其实很简单,就是通过getset方法。现在给类中添加get_nameget_age方法,以及set_nameset_age方法。继续改我们的Player类。

      class Player(object):
      
          def __init__(self, name, age):
              self.__name = name
              self.__age = age
      
          def get_name(self):
              return self.__name
      
          def get_age(self):
              return self.__age
      
          def set_age(self, age):
              self.__age = age
      
          def print_name_age(self):
              return "%s, %d" % (self.__name, self.__age)
      
      
      player1 = Player("Jack", 30)
      player1.set_age(100)
      print(player1.get_name())       # Jack
      print(player1.get_age())        # 100

      很明显,实现了改功能。本来可以不加方法,就可以直接修改,获取,现在加了方法有点画蛇添足。其实不是,加方法的目的是为了对参数做检查,避免传入无效的参数。

      实例

       def set_age(self, age):
              if 0 <= age <= 100:
                  self.__age = age
              else:
                  raise ValueError('bad score')

      当然,在实际开发项目中,你会看到一个下划线开头的实例属性,_name。访问是可以访问,但请你把它视为私有。

      最后,如果不写get方法能不能调用,当然可以,因为Python解释器把__name属性变成了_Player__name。但是建议你忘记。不推荐

  • 继承和多态

    • 继承

      在类的定义就讲过,所有的类都可以继承object,同样也可以继承我们自己定义的类。

      比如说,我定义一个Animal动物类,该动物有一个run()方法。

      如果我再编写一个狗类和猫类,我就可以去继承这个动物类。

      实例

      class Animal(object):
      
          def run(self):
              return "animal is running"
      
      class Dog(Animal):
          pass
      
      class Cat(Animal):
          pass

      继承的好处:子类可以获得父类的全部功能,也就是说狗类和猫类已经拥有run()方法了。

      基于上面的实例,我们去调用。

      dog = Dog()
      print(dog.run())
      
      cat = Cat()
      print(cat.run())
      
      # output
      animal is running
      animal is running

      为了符合逻辑性,我们继续改代码

      class Animal(object):
      
          def run(self):
              return "animal is running"
      
      class Dog(Animal):
      
          def run(self):
              return "dog is running"
      
      class Cat(Animal):
      
          def run(self):
              return "cat is running"
      
      dog = Dog()
      print(dog.run())
      
      cat = Cat()
      print(cat.run())
      
      # output:
      dog is running
      cat is running

      这样就很明确了,是谁在跑。

      当子类和父类都存在相同的run()方法时。子类的已经把父类的给覆盖了。

    • 多态

      对于初学者来说,多态还是有点难理解的,只能不断的通过程序来强化。

      class Animal(object):
      
          def run(self):
              return "animal is running"
      
      class Dog(Animal):
      
          pass
      
      class Cat(Animal):
      
          def run(self):
              return "cat is running"
      
      
      dog = Dog()
      print(isinstance(dog, Dog))
      print(isinstance(dog, Animal))
      
      # output:
      True
      True

      从上面的代码可以看出,dog属于Dog类型,又属于Animal类型。同理,cat也是这样的。

      当我们传入一个对象时,如果该对象有run()方法,就执行。没有的话,就去调用父类的run()方法。这就是多态。

      这就是动态语言,动态语言调用实例方法,不检查类型,只要方法存在,参数正确,就可以调用。

      继续研究

      import json
      
      f = open('/path/to/file.json', 'r')
      print(json.load(f))
      
      class Students(object):
          def __init__(self, strlist):
              self.strlist = strlist
      
          def read(self):
              return(self.strlist)
      
      s = Students('["Tim", "Bob", "Alice"]')
      
      print(json.load(s))

      因为f对象具有read()方法,而s对象也有read()方法。

      因此就可以认为,任何对象,只要有read()方法,就称为File-like Object,都可以传给json.load()

  • 获取对象信息

    判断对象类型:isinstance()函数,获取对象类型:type()

    获取对象属性与方法:dir()函数。

  • 实例属性和类属性

    前面我们提到,可以使用__init__.py方法进行实例属性绑定。

    同样,类也可以有属性。且这个属性归类所有。

    实例

    class Player(object):
    
        country = "chinese"

    当我们定义一个类属性后,这个属性虽然归类所有,但类的所以实例都可以访问到。

    测试以上的说法

    p = Player()          # 创建实例p
    
    print(p.country)       # 打印country属性,因为实例并没有country属性,所以会继续查找类属性
    
    print(Player.country) # 打印类属性
    
    p.country = "small Japan" # 给实例绑定country属性
    print(p.country)          # 由于实例属性的优先级高于类属性,因此屏蔽了类的属性
    
    print(Player.country)     # 但是类属性并未消失。还是可以访问
    
    del p.country             # 删除实例绑定的属性之后
    
    print(p.country)          # 再去调用,就只能委屈得到类属性了。

    注意:实际编写程序的时候,千万不要实例属性类属性使用相同名字

  • 类方法

    前面说到实例有属性和方法,那类也有属性,同样也有方法。

    实例

    class Person(object):
    
        count = 0
    
        @classmethod
        def how_many(cls):
            return cls.count
    
        def __init__(self, name):
            self.name = name
            Person.count = Person.count + 1
    
    print(Person.how_many())
    p1 = Person('Bob')
    print(Person.how_many()) 

    可以看到,通过标记一个@classmethod,该方法将绑定到Person类上,而非类的实例。类方法的第一个参数将传入类本身,通常将参数名命名为cls,上面的cls.count实际上相当于Person.count

    因为是在类上调用,而非实例上调用,因此类方法无法获得任何实例变量,只能获取类的引用

  • __slots__

    因为可以动态绑定,所以我们需要限制,该方法就是用来限制实例的属性。例如如下程序:

    class Student(object):    
      __slots__ = ('name', 'age')
    
    s = Student()
    s.name = "Jack"
    s.age = 20
    s.score = 90      # AttributeError

    上面程序可以看出:用tuple定义允许绑定属性名称,由于score没有被放到__slots__中,所以不能绑定score属性。如果有Student有子类,slots定义的属性对子类是没有作用的。

  • @property

    我们在前面提到了访问限制。关于变量的关系。我们使用了getset的方法。看上去有点复杂,没有直接用属性这么方便。

    那Python中有没有既能检查参数,又可以用类似属性这样简单的方式来访问类的变量呢?是存在的。

    好比装饰器可以给函数动态加上功能,对于类方法,装饰器一样起到作用。

    Python内置的@property装饰器就是负责把一个方法变成属性调用的。例如如下程序

    class Student(object):
    
        @property
        def score(self):
            return self._score
    
        @score.setter
        def score(self, value):
            if not isinstance(value, int):
                raise ValueError('score must be an integer!')
            if value < 0 or value > 100:
                raise ValueError('score must between 0 ~ 100!')
            self._score = value
    
    s = Student()
    s.score = 60
    print(s.score)

    @property的作用就是把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作。再看代码,s.score = 60实际转化为s.set_score(60)s.score实际转化为s.get_score()。所以本质其实还是通过getter和setter方法来实现的。

  • 枚举类

    在某些情况下,一个类的对象是有限且固定的,比如季节类,它只有4个对象;行星类,目前只要8个对象;月份类,它有12个对象,这种实例有限且固定的类,被称为枚举类

    • 定义枚举类

      通过使用Enum()函数来创建。第一个参数是枚举类的类名,第二个参数是一个元组格式的枚举值。

      import enum
      
      Season = enum.Enum('Season', ('SPRING', 'SUMMER', 'FALL', 'WINTER'))
    • 访问枚举值

      每个成员都有 name、value 两个属性。

      # 直接访问指定枚举
      print(Season.SPRING)
      # 访问枚举成员的变量名
      print(Season.SPRING.name)
      # 访问枚举成员的值
      print(Season.SPRING.value)
      
      # output:
      Season.SPRING
      SPRING
      1

      此外,还提高了一个__members__ 属性,该属性返回一个 dict 字典。

      for name, member in Season.__members__.items():    
          print(name, '=>', member, ',', member.value)
      
      # output:
      SPRING => Season.SPRING , 1
      SUMMER => Season.SUMMER , 2
      FALL => Season.FALL , 3
      WINTER => Season.WINTER , 4
  • type()

    前面有提到,type()函数可以查看变量的类型,但如果想使用type()直接查看某个类的类型呢?请看以下代码:

    class Role:
        pass
    
    r = Role()
    # 查看变量r的类型
    print(type(r))        # <class '__main__.Role'>
    # 查看Role类本身的类型
    print(type(Role))     # <class 'type'>

    从上面的输出结果可以卡的看到,Role类本身的类型是 type。这句话有点拗口,怎样理解 Role 类的类型是 type?

    从 Python 解释器的角度来看,当程序使用 class 定义 Role 类时,也可理解为定义了一个特殊的对象(type 类的对象),并将该对象赋值给 Role 变量。因此,程序使用 class 定义的所有类都是 type 类的实例。

    实际上 Python 完全允许使用 type() 函数(相当于 type 类的构造器函数)来创建 type 对象,又由于 type 类的实例就是类,因此 Python 可以使用 type() 函数来动态创建类。例如如下程序:

    def fn(self):    
      print('fn函数')
    
    # 使用type()定义Dog类
    Dog = type('Dog', (object,), dict(walk=fn, age=6))
    # 创建Dog对象
    d = Dog()
    # 分别查看d、Dog的类型
    print(type(d))
    print(type(Dog))
    d.walk()
    print(Dog.age)
    
    #output:
    <class '__main__.Dog'>
    <class 'type'>
    fn函数
    6

    使用 type() 定义类时可指定三个参数:

    1、参数一:创建的类名。

    2、参数二:该类继承的父类集合。由于 Python 支持多继承,因此此处使用元组指定它的多个父类。即使实际只有一个父类,也需要使用元组语法(必须要多一个逗号)。

    3、参数三:该字典对象为该类绑定的类变量和方法。其中字典的 key 就是类变量或方法名,如果字典的 value 是普通值,那就代表类变量;如果字典的 value 是函数,则代表方法。

    由此可见,第 5 行代码定义了一个 Dog 类,该类继承了 object 类,还为该类定义了一个 walk() 方法和一个 age 类变量。

  • 元类

    理解元类不难,看完以下整个过程,如果你还不明白,那你别学了。

    前面讲了 type() 函数,其实它时适用于动态创建相对简单的类,如果要创建复杂的类,则需要通过 MetaClass(元类)的方式。

    元类可以简单的理解为,就是创建类的类。

    • 定义元类

      需令其继承与 type 类,且默认的命名习惯是,让类名以 MetaClass 结尾。不仅如此,元类中需要定义并实现 __new__() 方法(一定要有返回值)。因为元类在创建类时,该 __new__() 方法将会被调用,用来生成新建的类。

      # 定义Item元类,继承type
      class ItemMetaClass(type):
          # cls代表动态修改的类
          # name代表动态修改的类名
          # bases代表被动态修改的类的所有父类
          # attr代表被动态修改的类的所有属性、方法组成的字典
          def __new__(cls, name, bases, attrs):
              # 动态为该类添加一个cal_price方法
              attrs['cal_price'] = lambda self: self.price * self.discount
              return type.__new__(cls, name, bases, attrs)

      上面程序中,在重写该方法时为目标类动态添加了一个 cal_price 方法。

    • 使用元类创建类

      # 定义Book类
      class Book(metaclass=ItemMetaClass):
          __slots__ = ('name', 'price', '_discount')
          def __init__(self, name, price):
              self.name = name
              self.price = price
          @property
          def discount(self):
              return self._discount
          @discount.setter
          def discount(self, discount):
              self._discount = discount
      
      # 定义cellPhone类
      class CellPhone(metaclass=ItemMetaClass):
          __slots__ = ('price', '_discount' )
          def __init__(self, price):
              self.price = price
          @property
          def discount(self):
              return self._discount
          @discount.setter
          def discount(self, discount):
              self._discount = discount

      上面程序定义了 Book 和 CellPhone 两个类,在定义这两个类时都指定了元类信息,因此当 Python 解释器在创建这两个类时,ItemMetaClass 的 __new__ 方法就会被调用,用于修改这两个类。

      所以定义的这两个类,依然是有 cal_price() 方法。如下代码进行检测。

      b = Book("Python基础教程", 89)
      b.discount = 0.75
      # 创建Book对象的cal_price()方法
      print(b.cal_price())
      cp = CellPhone(2399)
      cp.discount = 0.85
      # 创建CellPhone对象的cal_price()方法
      print(cp.cal_price())
      
      # output:
      66.75
      2039.1499999999999

      从上面的运行结果来看,通过使用元类可以动态修改程序中的一批类,对它们集中进行某种修改。这个功能在开发一些基础性框架时非常有用,程序可以通过使用元类为某一批需要具有通用功能的类添加方法。

原文地址:https://www.cnblogs.com/lowkeyao/p/11311822.html