美文网首页
Python学习笔记4-面向对象编程

Python学习笔记4-面向对象编程

作者: 朔野 | 来源:发表于2016-05-31 20:12 被阅读87次

    创建类和对象

    1. 创建一个类

    谈起面向对象,对于大部分程序员来说都是耳熟能详的玩意(比如我这个java码农),这个面向对象编程说白了无非就是类和对象,方法和成员变量,封装等等。
    Python作为一门面向对象的语言,肯定对于这些的支持是没问题,下面我们来说一下Python的面向对象编程的问题。首先是Python的声明一个类然后创建一个对象,这个是最基本的玩意。代码如下:

    class Student(object):  
        def __init__(self,name):#构造函数  
            print("构造函数启动")  
            self.name=name  
      
        def sayHi(self):  
            print("Hello World",self.name)  
              
    p = Student("rookie")  
    p.sayHi()
    # 输出 Hello World rookie
    

    和java其实差不多,只是注意所有方法必须显示写出self(也就是java和c++的this)

    2. 实例属性和类属性

    由于Python是动态语言,根据类创建的实例可以任意绑定属性。
    给实例绑定属性的方法是通过实例变量,或者通过self变量:

    class Student(object):
        def __init__(self, name):
            self.name = name
    
    s = Student('Bob')
    s.score = 90
    

    但如果如果Student类本身需要绑定一个属性呢?(也就是java的静态属性)可以直接在class中定义属性,这种属性是类属性,归Student类所有:

    class Student(object): 
        name = 'Student'
    
    print (Student.name)
    #输出 Student
    

    那如果用实例去访问类属性呢?

     class Student(object):
         name = 'Student'
    
     s = Student() # 创建实例s
    print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
    # 输出 Student
    print(Student.name) # 打印类的name属性
    # 输出 Student
    s.name = 'Michael' # 给实例绑定name属性
    print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
    # 输出 Michael
    print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
    # 输出 Student
    del s.name # 如果删除实例的name属性
    print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
    # 输出 Student
    

    从上面的例子可以看出,在编写程序的时候,千万不要把实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。

    3.类方法、实例方法、静态方法

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

    class people:
        country = 'china'
        
        #类方法,用classmethod来进行修饰
        @classmethod
        def getCountry(cls):
            return cls.country
    
    p = people()
    print p.getCountry()    #可以用过实例对象引用
    print people.getCountry()    #可以通过类对象引用
    

    类方法还有一个用途就是可以对类属性进行修改:

    class people:
        country = 'china'
        
        #类方法,用classmethod来进行修饰
        @classmethod
        def getCountry(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()   
    print people.getCountry()    
    

    运行结果:

    china
    china
    japan
    japan
    

    结果显示在用类方法对类属性修改之后,通过类对象和实例对象访问都发生了改变。

    实例方法:在类中最常定义的成员方法,它至少有一个参数并且必须以实例对象作为其第一个参数,一般以名为'self'的变量作为第一个参数(当然可以以其他名称的变量作为第一个参数)。在类外实例方法只能通过实例对象去调用,不能通过其他方式去调用。

    静态方法:需要通过修饰器"@staticmethod"来进行修饰,静态方法不需要多定义参数。

    class people:
        country = 'china'
        
        @staticmethod
        #静态方法
        def getCountry():
            return people.country
            
    
    print (people.getCountry() )
    

    对于类属性和实例属性,如果在类方法中引用某个属性,该属性必定是类属性,而如果在实例方法中引用某个属性(不作更改),并且存在同名的类属性,此时若实例对象有该名称的实例属性,则实例属性会屏蔽类属性,即引用的是实例属性,若实例对象没有该名称的实例属性,则引用的是类属性;如果在实例方法更改某个属性,并且存在同名的类属性,此时若实例对象有该名称的实例属性,则修改的是实例属性,若实例对象没有该名称的实例属性,则会创建一个同名称的实例属性。想要修改类属性,如果在类外,可以通过类对象修改,如果在类里面,只有在类方法中进行修改。
      从类方法和实例方法以及静态方法的定义形式就可以看出来,类方法的第一个参数是类对象cls,那么通过cls引用的必定是类对象的属性和方法;而实例方法的第一个参数是实例对象self,那么通过self引用的可能是类属性、也有可能是实例属性(这个需要具体分析),不过在存在相同名称的类属性和实例属性的情况下,实例属性优先级更高。静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类对象来引用。

    封装和访问控制

    1.访问控制

    在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。
    但是,从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的name、score属性:

    bart = Student('Bart Simpson', 98)
    print (bart.score)
    98
    bart.score = 59
    print (bart.score)
    59
    

    如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线,在Python中,实例的变量名如果以开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问.
    这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。
    但是如果外部代码要获取或修改name和score怎么办?可以给Student类增加get_name和set_score这样的方法。

    需要注意的是,在Python中,变量名类似xxx的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用namescore这样的变量名。

    有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

    那双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name
    来访问__name变量

    >>> bart._Student__name
    'Bart Simpson'
    

    2.特殊的类属性

    对于任何类C,有以下的特殊属性及方法:

         C.__name__           类C的名字(string)
         C.__doc__             类C的文档字符串
         C.__bases__          类C的所有父类构成元素(包含了以个由所有父类组成的元组)
         C.__dict__             类C的属性(包含一个字典,由类的数据属性组成) 
         C.__module__        类C定义所在的模块(类C的全名是'__main__.C',如果C位于一个导入模块mymod中,那么C.__module__ 等于 mymod)
         C.__class__           实例C对应的类
    x=C()
    x.__init__()   初始化一个实例,在实例创建之后立即调用,并且这个方法没有返回值(无return语句)
    x.__repr__()  字符串的“官方”表示方法  >>> x  <==> >>>x.__repr__()
    x.__str__()   字符串的非正式值   等同于  print xx.__new__()  一般是用来生成一个不可变实例,控制实际创建的进程
    

    继承与多态

    1. 继承

    继承给人的直接感觉是这是一种复用代码的行为。继承可以理解为它是以普通的类为基础建立专门的类对象,子类和它继承的父类是IS-A的关系。一个简单而不失经典的示例如下:

    class Animal(object):
    
        def __init__(self): 
            self.Name="Animal"
          
        def move(self,meters):
            print ("%s moved %sm." %(self.Name,meters) )
            
    class Cat(Animal): #Cat是Animal的子类
    
         def __init__(self):  #重写超类的构造方法
            self.Name="Garfield"
    
    ##     def move(self,meters): #重写超类的绑定方法
    ##        print "Garfield never moves more than 1m."
    
    class RobotCat(Animal):
    
        def __init__(self):  #重写超类的构造方法
            self.Name="Doraemon"
    
    ##     def move(self,meters): #重写超类的绑定方法
    ##        print "Doraemon is flying."
    
    obj=Animal()
    obj.move(10) #输出:Animal moved 10m.
    
    cat=Cat()
    cat.move(1) #输出:Garfield moved 1m.
    
    robot=RobotCat()
    robot.move(1000) #输出:Doraemon moved 1000m.
    

    2. 多重继承

    不同于Java,Python是支持多重类继承的。多重继承机制有时很好用,但是它容易让事情变得复杂。一个多重继承的示例如下:

    class Animal:
      
        def eat(self,food):
            print ("eat %s" %food )
            
    class Robot:
          
        def fly(self,kilometers):
            print ("flyed %skm." %kilometers )
            
    class RobotCat(Animal,Robot): #继承自多个超类
    
        def __init__(self):  
            self.Name="Doraemon"
    
    robot=RobotCat() # 一只可以吃东西的会飞行的叫哆啦A梦的机器猫
    print (robot.Name)
    
    robot.eat("cookies") #从动物继承而来的eat
    
    robot.fly(10000000) #从机器继承而来的fly
    

    如你所看到的那样,多重继承的好处显而易见,我们可以轻而易举地通过类似“组合”的方式复用代码构造一个类型。

    有个需要注意的地方,如果一个方法从多个超类继承,那么务必要小心继承的超类(或者基类)的顺序,多重继承的结果因为继承的顺序而有所不同,Python在查找给定方法或者特性时访问超类的顺序被称为MRO(Method Resolution Order,方法判定顺序)。

    3. 鸭子类型

    对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。
    对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个eat()方法就可以了:

    class Persion(object):
        def run(self):
            print('Persion eat...')
    

    这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

    Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个read()方法,返回其内容。但是,许多对象,只要有read()方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了read()方法的对象。

    高级特性

    1.使用slots

    正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。先定义class:

    class Student(object): 
        pass
    

    然后,尝试给实例绑定一个属性:

    >>> s = Student()
    >>> s.name = 'Michael' # 动态给实例绑定一个属性
    >>> print(s.name)
    Michael
    >>> def set_age(self, age): # 定义一个函数作为实例方法
    ...     self.age = age
    ...
    >>> from types import MethodType
    >>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
    >>> s.set_age(25) # 调用实例方法
    >>> s.age # 测试结果
    25
    

    但是,如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加name属性,不允许添加score属性.
    为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的slots变量,来限制该class实例能添加的属性:

    class Student(object): 
        __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
    

    然后,我们试试:

    >>> s = Student() # 创建新的实例
    >>> s.name = 'Michael' # 绑定属性'name'
    >>> s.age = 25 # 绑定属性'age'
    >>> s.score = 99 # 绑定属性'score'
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'Student' object has no attribute 'score'
    

    由于'score'没有被放到slots中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。
    使用slots要注意,slots定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:

    >>> class GraduateStudent(Student):
    ...     pass
    ...
    >>> g = GraduateStudent()
    >>> g.score = 9999
    

    除非在子类中也定义slots,这样,子类实例允许定义的属性就是自身的slots加上父类的slots

    2. 使用@property

    如果类的每个属性都要设置set和get方法是非常麻烦的,那有没有方便的办法呢?
    肯定是有了,那就是@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=10
    print (s.score)
    

    通过@property修饰的属性不是直接暴露的,而是通过getter和setter方法来实现的。可以在@score.setter修饰的set方法来规定输入范围.
    而且还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性:

    class Student(object):
    
        @property
        def birth(self):
            return self._birth
    
        @birth.setter
        def birth(self, value):
            self._birth = value
    
        @property
        def age(self):
            return 2015 - self._birth
    

    上面的birth是可读写属性,而age就是一个只读属性,因为age可以根据birth和当前时间计算出来。

    相关文章

      网友评论

          本文标题:Python学习笔记4-面向对象编程

          本文链接:https://www.haomeiwen.com/subject/tcabrttx.html