python面向对象编程

2018-10-19  本文已影响0人  像小象的云

面向对象编程(一)

1、面向过程程序设计与面向对象程序设计:

面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
面向对象的程序设计把计算机程序视为一组对象(具有相同的属性和行为)的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
从如下例子感受两种编程思想的差异:

假设我们要处理一个学生的成绩:
"""

#1、使用面向过程的程序设计:
#我们首先使用一个dict来保存学生的信息
stu1 = {"name":"Lily","score":89}
#然后定义一个函数来处理学生的成绩
def print_score(stu):
    print("%s:%d"%(stu["name"],stu["score"]))
print_score(stu1)

"""
2、使用面向对象的程序设计:我们首先思考的不是程序执行过程,而是学生应该被视为一个对象,
这个对象拥有name与score两个属性,如果要打印学生成绩,我们要先创建出这个对象,然后给
它发消息让它打印成绩
"""

class Student(object):
    def __init__(self,name,score):
        self.name = name
        self.score = score
    def print_score(self):
        print("%s:%d"%(self.name,self.score))

Lily = Student("Lily",89)
Lily.print_score()
2、在python中定义类

在python中所有的数据类型都可以视为对象。在Python中可以使用class关键字定义类,然后在类中通过之前学习过的函数来定义方法,这样就可以将对象的动态特征描述出来。在python中定义类的关键字为class,基本格式:

class Person(object):
    pass
"""
关键字class其后紧跟类名Person,然后元组(object)表示继承自什么
类(可以有多个),如果不写,默认就继承自基类object
"""
3、实例化对象:
p = Person() #实例化对象p
print(type(p)) 
#运行结果,可以看出p属于Person类
<class '__main__.Person'>
4、类中的方法

类中方法包括对象方法、类方法和静态方法,类方法的定义需要使用装饰器@classmethod,静态方法的定义需要使用装饰器@staticmethod,如下:

def foo():
    print("一般函数")
class A(object):
    def foo(self): #对象方法
        print("对象方法")
    @classmethod
    def class_foo(cls): #类方法
        print("类方法")
    @staticmethod
    def static_foo():#静态方法
        print("静态方法")
a = A()

这里先理解一下函数中的self与cls,self与cls分别是对实例与类的绑定。对于一般的函数,我们可以这么调用foo(),他的工作与任何东西(实例,类)都无关。对于对象方法,我们在类里每次定义方法的时候都需要绑定这个实例,就是foo(self),因为对象方法的调用离不开对象,我们需要把对象传给函数,调用的时候是这样a.foo()(其实是这样foo(a)),类方法是一样,只不过它传入的是类本身。对于普通方法其实和普通方法一样,不需要对谁进行绑定,只不过调用的时候需要使用a.static_foo()或者A.static_foo()

对象方法 类方法 静态方法
a=A() a.foo() a.class_foo() a.static_foo()
A 不可用 A.class_foo() A.static_foo()
class A(object):
    def foo(self): #定义对象方法
        print(id(self),"对象方法")
    @classmethod     #定义类方法
    def class_foo(cls): 
        print(id(cls),"类方法")
    @staticmethod  #定义静态方法
    def static_foo():
        print("静态方法")
a = A()  #实例化对象
print(id(a))
a.foo()  #调用对象方法
print(id(A))
A.class_foo()  #类调用类方法
a.class_foo()   #对象调用类方法
a.static_foo()  #对象调用静态方法
A.static_foo() #类调用静态方法

#执行结果  可以看出self就是对象本身,cls就是类本身
31601264
31601264 对象方法
31545232
31545232 类方法
31545232 类方法
静态方法
静态方法
5、类中的属性

(1)类中的属性包括对象属性,类字段
对象属性只对每个具体的对象有效,这个有效是这不同的对象的同一属性值可能不同,类字段归所有对象公有,值都一样。
由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。是通过定义一个特殊的init方法来实现初始化的,这里的这些属性就是对象属性。

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

注意到init方法的第一个参数永远是self,表示创建的实例本身,因此,在init方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。有了init方法,在创建实例的时候,就不能传入空的参数了,必须传入与init方法匹配的参数,但self不需要传。

类字段直接写在类中就行

class A(object):
    num = 64  #这个num就是类字段了
    pass

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

class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score
bart = Student('Bart Simpson', 59)
print(bart.score)
bart.score = 88  #随意改变成绩
print(bart.score)

#执行结果
59
88

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线,在Python中,实例的变量名如果以两个下划线开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。

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))
bart = Student('Bart Simpson', 59)
bart.print_score()
print("*********")
print(bart.__score)

#执行结果,无法访问bart.__score
Bart Simpson: 59
*********
Traceback (most recent call last):
  File "E:/Python工程/test/1、运算符.py", line 12, in <module>
    print(bart.__score)
AttributeError: 'Student' object has no attribute '__score'

python中没有真正的私有化(没有从访问权限上去限制内容的访问),私有的原理就是在私有的属性名或者方法名前加前缀'_类名'来阻止外部直接通过带两个下划线的名字去使用属性和方法,所以上的列子中还是可以通过bart._Stedent__score来访问成绩

注意:①在python中形如foo前后都有双下划线的名字是系统内部的名字,用来区别其他用户自定义的名字
②_foo这样只有前面有一个下划线的名字是一种约定,程序员用来指定私有变量的一种方式,不能使用 from model import * 的形式导入,其他访问方式一样,但是看见这种命名,你应该自觉的不去访问
③__foo这样前边有两个下划线的名字具有真正的意义,python解释器会解析成_classname__foo。

6、数据封装,继承,多态

面向对象编程的三大特点就是数据封装,继承,多态。

数据封装

面向对象编程的一个重要特点就是数据封装,在上面的Student类中,每个实例就拥有各自的name和score这些数据。既然Student实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。这些封装数据的函数是和Student类本身是关联起来的,我们称之为类的方法。这样一来,我们从外部看Student类,就只需要知道,创建实例需要给出name和score,而如何打印score,name,都是在Student类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节。

继承与多态

在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。继承的一个好处就是可使子类自动获取父类的所有属性和方法。继承的另一个好处就是多态。
要理解什么是多态,我们首先要对数据类型再作一点说明。当我们定义一个class的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样:

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

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

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

a = list() # a是list类型
b = Animal() # b是Animal类型
c = Dog() # c是Dog类型

print(isinstance(a, list))
print(isinstance(b, Animal))
print(isinstance(c, Dog))
print(isinstance(c, Animal))

#执行结果
True
True
True
True

看来a、b、c确实对应着list、Animal、Dog这3种类型。看来c不仅仅是Dog,c还是Animal!不过仔细想想,这是有道理的,因为Dog是从Animal继承下来的,当我们创建了一个Dog的实例c时,我们认为c的数据类型是Dog没错,但c同时也是Animal也没错,Dog本来就是Animal的一种!所以,在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行,Dog可以看成Animal,但Animal不可以看成Dog。

要理解多态的好处,我们还需要再编写一个函数,这个函数接受一个Animal类型的变量:

def run_twice(animal):
    animal.run()
    animal.run()
#当我们传入Animal的实例时,run_twice()就打印出:
run_twice(Animal())

#当我们传入Dog的实例时,run_twice()就打印出:
run_twice(Dog())

#当我们传入Cat的实例时,run_twice()就打印出:
run_twice(Cat())

#执行结果
Animal is running...
Animal is running...
Dog is running...
Dog is running...
Cat is running...
Cat is running...

7、使用__slots__

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

class Student(object):
    pass
s = Student()
s.name = 'Michael' # 动态给实例绑定一个属性
print(s.name)
#执行结果
Michael

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

class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
s = Student()
s.name = 'Michael' # 
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__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的,除非在子类中也定义一个。

8、getter和setter

1.什么时候需要添加对象属性的getter和setter
如果希望在通过对象.属性获取属性的值之前,再干点儿别的事情,就可以给这个属性添加getter。
如果希望在通过对象.属性给属性赋值之前,再干点儿别的事情,就可以给这个属性添加setter

2.怎么添加setter和getter
getter:
a.在属性名前加_
b.添加属性对应的getter
@property
def 属性名去掉_(self):
    函数体 --> 会对属性的值进行处理后,返回相应的结果(必须要有返回值)
    
c.使用属性的值的时候,不通过带下划线的属性名去使用,而是通过没有下划线的属性去使用

注意:对象.不带下划线的属性 --> 调用getter对应的函数

setter:
如果想要添加setter必须要先添加getter
a.添加setter
@getter名.setter
def 属性名去掉_(self, 参数):
    做别的事情
    self.属性名 = 处理后的值

"""
# 赋值时要求age的值只能在0-150之间,超过范围报错;获取age的值的时候,返回年龄值,并且返回这个年龄对应的阶段
# class Person:
#     def __init__(self):
#         self.name
#         self.age = 18

# print(p1.age)  --> (18, 成年)
# value, jieduan  = p1.age  value -> 18, jieduan -> 成年


class Number:
    def __init__(self):
        self._value = 0
        # 0-6保存
        self._week = 3
        self.type = int
        self.id = None

    # _value添加getter和setter
    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, x):
        if not -100 <= x <= 100:
            raise ValueError
        self._value = x




    # _week的getter
    @property
    def week(self):
        if self._week == 0:
            return '星期天'
        elif self._week == 1:
            return '星期一'
        elif self._week == 2:
            return '星期二'
        elif self._week == 3:
            return '星期三'
        elif self._week == 4:
            return '星期四'
        elif self._week == 5:
            return '星期五'
        elif self._week == 6:
            return '星期六'

    """
    isinstance(值, 类) --> 判断指定的值是否是指定类型(返回值是bool)
    """
    @week.setter
    def week(self, value):
        # 如果传的值不是整型数据
        if not isinstance(value, int):
            raise ValueError
        if not 0 <= value <= 6:
            raise ValueError

        self._week = value



number = Number()
number.value = 99
print(number.week)  # number.week 实质是在通过number去调用getter对应的week方法
number.week = 1  # number.week = 值  实质是通过number去调用setter对应的week方法


number.value = 100
print(number.value)
9、方法重写与运算符重载
父类方法重写
继承后子类会拥有父类的属性和方法,也可以添加属于自己的属性和方法

1.添加新的方法
直接在子类中声明新的方法,新的方法只能通过子类来使用

2.重写
a.子类继承父类的方法,在子类中去重新实现这个方法的功能 -- 完全重写
b.在子类方法中通过super().父类方法去保留父类对应的方法的功能

3.类中的函数的调用过程
类.方法(), 对象.方法()

先看当前类是否有这个方法,如果有就直接调用当前类中相应的方法;
如果没有就去当前的父类中去看有没有这个方法,如果有就调用父类的这个方法;
如果父类中也没有这个方法,就去父类的父类中找,依次类推直到找到为止。
如果找到基类object,还没有找到这个方法,程序才异常
"""
class Person:

    def __init__(self, name=''):
        self.name = name

    def eat(self, food):
        # self = super()
        print('%s在吃%s' % (self.name,  food))

    @staticmethod
    def run():
        print('人在跑步')

    @classmethod
    def get_up(cls):
        print('===========')
        print('洗漱')
        print('换衣服')



class Staff(Person):
    pass

class Student(Person):

    def study(self):
        print('%s在学习' % self.name)

    def eat(self, food):
        # super():当前类的父类的对象
        print('对象方法:',super())
        super().eat(food)
        print('喝一杯牛奶!')

    @staticmethod
    def run():
        print('学生在跑步')

    @classmethod
    def get_up(cls):
        # super() -> 获取当前类的父类
        # super().get_up() ->调用父类的get_up方法
        print('类方法', super())
        super().get_up()  # 可以保留父类get_up的功能
        print('背书包')


p1 = Person()
Person.run()
Person.get_up()
p1.name = '小红'
p1.eat('面条')


stu1 = Student()
stu1.study()
Student.run()
Student.get_up()

stu1.name = '小花'
stu1.eat('面包')
运算符重载
运算符重载: 通过实现类响应的魔法方法,来让类的对象支持相应的运算符(+, -, > ,< 等)

值1 运算符 值2 ---> 值1.魔法方法(值2)
"""

10 > 20   # int类,实现 > 对应的魔法方法 __gt__
10 < 20
['12', 2] > ['abc' , 1, 34]  # list类,实现 > 对应的魔法方法 __gt__

10 / 20   # __truediv__

20 % 10


import copy
import random

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

    #  __gt__就是 > 对应的魔法方法
    def __gt__(self, other):
        # self -> 指的是大于符号前面的值, other -> 指的是>符号后面的值
        return self.score > other.score

    # __lt__是 < 对应的魔法方法
    # 注意:gt和lt只需要实现一个就可以了
    def __lt__(self, other):
        return self.score < other.score

    def __add__(self, other):
        return self.score + other.score

    def __mul__(self, other: int):
        result = []
        for _ in range(other):
            result.append(copy.copy(self))
        return result


stu1 = Student('小哈', 23, 89)
stu2 = Student('小🌺', 19, 90)
print(stu1 > stu2)
print(stu1 < stu2)

print(stu1 + stu2)

students = stu1*10
print(students)
students[0].name = '小明'


class Person:
    def __init__(self, name='张三', age=0):
        self.name = name
        self.age = age

    def __mul__(self, other: int):
        result = []
        for _ in range(other):
            result.append(copy.copy(self))
        return result

    def __gt__(self, other):
        return self.age > other.age


    # 定制打印格式
    def __repr__(self):
        return str(self.__dict__)[1:-1]


# 同时创建10个人的对象
persons = Person()*10
# persons = 10 * Person()
# print(persons)

for p in persons:
    p.age = random.randint(15, 35)

print(persons)

# 列表元素是类的对象,使用sort对列进行排序
persons.sort()
print(persons)

print(max(persons))


class Dog:
    def __mul__(self, other):
        pass

dog1 = Dog()
dog1 * 4
上一篇下一篇

猜你喜欢

热点阅读