美文网首页
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面向对象学习笔记-01

    学习笔记 # 0,OOP-Python面向对象 - Python的面向对象 - 面向对象编程 - 基础 -...

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

    创建类和对象 1. 创建一个类 谈起面向对象,对于大部分程序员来说都是耳熟能详的玩意(比如我这个java码农),这...

  • 读书笔记 | Python学习之旅 Day7

    Python学习之旅 读书笔记系列 Day 7 《Python编程从入门到实践》 第9章 类 知识点 面向对象编程...

  • Python 学习笔记

    Python 学习笔记 Python 基础语法 Python是一种面向对象解释型计算机编程语言,语法简洁凝练,强制...

  • Python 面向对象编程

    Python 面向对象编程(一) Python 面向对象编程(一) 虽然Python是解释性语言,但是它是面向对象...

  • python学习笔记目录

    Python学习笔记目录(倒序) Python学习-异常 Python学习-面向对象 Python学习-文件系统 ...

  • 史上最全 Python 面向对象编程

    面向对象编程和函数式编程(面向过程编程)都是程序设计的方法,不过稍有区别。 面向过程编程:学习Python中有不明...

  • Python学习笔记-Day08

    Python学习笔记 Day_08-面向对象 截止目前,我学习的编程都是面向过程的。从今天开始,我将学习一种全新思...

  • python面向对象编程(3)|方法和访问权限

    今天我们来学习python面向对象编程的三种方法和访问权限。 方法 上次我们已经说过,python面向对象编程一共...

  • Python全栈之路系列之面向对象基础

    面向对象基本介绍 Python编程方式: 面向过程编程 面向函数编程 面向对象编程 名称定义: 如果函数没有在类中...

网友评论

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

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