python 面向对象编程

1. 面向对象常用术语

  • Python是一门面向对象编程语言。
  • 面向对象编程是一种程序设计思想,把对象作为程序的基本单元,一个对象包含数据和操作数据的函数。
  • 在Python中,所有数据类型都被视为对象,也可以自定义对象。自定义对象数据类型就是面向对象中的类(Class)的概念。
  • 类:用来描述具有相同属性和方法的对象的集合。类定义了集合中每个对象共有的属性和方法。
  • 对象:对象是类的实例。
  • 类变量(属性):类变量在整个实例化的对象中是公用的。类变量定义在类中,且在方法之外。类变量通常不作为实例变量使用。类变量也称作属性。
  • 实例变量:定义在方法中的变量只作用于当前实例的类。
  • 数据成员:类变量或实例变量用于处理类及其实例对象的相关数据。
  • 方法重写:如果从父类继承的方法不能满足子类的需求,就可以对其进行改写,这个过程称为方法的覆盖(Override),也称为方法的重写。
  • 实例化(Instance):创建一个类的实例、类的具体对象。
  • 方法:类中定义的函数。
  • 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
  • 面向对象的三大特性:
    • 多态(Polymorphism):对不同类的对象使用同样的操作。
    • 封装(Encapsulation):对外部世界隐藏对象的工作细节。
    • 继承(Inheritance): 即一个派生类(derivedclass)继承基类(base class)的字段和方法。

2. 类

2.1 类的定义和对象实例化

类用关键字 class 进行定义,定义一个类,通常要定义属性和方法。类在定义后,一般通过类实例化对象,然后通过对象访问类的属性和方法。

class Dog(): # 定义了一个Dog类
    name = "小花"           # 类的属性
    def cry(self):    # 类中定义的方法
        return "汪汪汪"
        
dog = Dog()     # 实例化对象
# 通过对象调用类的属性和方法。
print(f"我的小狗名字为:{dog.name},它喜欢{dog.cry()}的叫")  
我的小狗名字为:小花,它喜欢汪汪汪的叫

2.2 类的构造方法

在Python中,_init_()方法是一个特殊方法,在对象实例化时会被调用。_init_()的意思是初始化,是initialization的简写。这个方法也叫构造方法。在定义类时,若不显式地定义一个_init_()方法,则程序默认调用一个无参的_init_()方法。

class Dog():  
    name = "小花"
    def __int__(self): # 不显示定义,默认调用该构造方法   
        pass 
dog = Dog()
dog.name
'小花'
class Dog():  
    def __init__(self,name):         
        self.name = name
        print(f"构造方法带一个参数:{self.name}")
dog = Dog("小花")   
构造方法带一个参数:小花
class Dog():
    def __init__(self,name,age):      
        self.name = name
        self.age = age
        print(f"构造方法带两个参数:{self.name}-{self.age}")  
dog = Dog("小花","2岁")

构造方法带两个参数:小花-2岁

2.3 类的访问权限

类的访问权限在类内部有属性和方法,外部代码可以通过直接调用实例变量的方法操作数据,这样就隐藏了内部的复杂逻辑。

class Dog():
    def __init__(self,name,age):      
        self.name = name
        self.age = age
    def show_info(self):
        print(f"姓名:{self.name}\t 年龄:{self.age}")  
dog = Dog("小花","2岁")
print(f"修改前的年龄:{dog.age}")
dog.show_info()
dog.age = "5岁"
dog.show_info()
修改前的年龄:2岁
姓名:小花	 年龄:2岁
姓名:小花	 年龄:5岁

在类中定义的非构造方法可以调用类中构造方法实例变量的属性,调用的方式为self.实例变量属性名。可以在类的外部修改类的内部属性。如果要让内部属性不被外部访问,可以在属性名称前加两个下画线__,使实例变量变为就会变成私有变量(private),如果外部代码要获取类中的name和score,可以为类增加get_attrs和set_attrs方法,获取和修改类中的私有变量。

class Dog():
    def __init__(self,name,age):      
        self.__name = name
        self.__age = age
    def set_name(self,name):
        self.__name = name
    def set_age(self,age):
        self.__age = age
    def get_name(self):
        return self.__name
    def get_age(self):
        return self.__age
    def show_info(self):
        print(f"姓名:{self.get_name()}\t 年龄:{self.get_age()}")  
dog = Dog("小花","2岁")
dog.show_info()
dog.set_age("5岁")  
dog.show_info()
姓名:小花	 年龄:2岁
姓名:小花	 年龄:5岁

3. 继承

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NhRlKnFf-1584881814392)(attachment:image.png)]

  继承可以理解成类之间类型和子类型的关系。当我们定义一个class时,可以从某个现有的class继承,定义的新class称为子类(Subclass),而被继承的class称为基类、父类或超类(Baseclass、Superclass)。如上图:Animal和Plant类都继承object类,Dog和Cat类继承Animal类,Tree和Flower类继承Plant类。
在Python中,继承有以下特点:

  • 在继承中,基类的构造方法(_init_()方法)不会被自动调用,需要在子类的构造方法中专门调用。
  • 在调用基类的方法时需要加上基类的类名前缀,并带上self参数变量。区别于在类中调用普通函数时不需要带self参数。
  • 在Python中,首先查找对应类型的方法,如果在子类中找不到对应的方法,才到基类中逐个查找。
    注意: 继承语法class子类名(基类名)时,//基类名写在括号里,基本类是在定义类时,在元组中指明的。
class Animal(object):
    def cry(self):       
        print("动物在叫。")
        
class Dog(Animal):
    pass
class Cat(Animal):
    pass

dog = Dog()
dog.cry()    # 子类直接继承父类的公有方法,如果cry()方法为__cry()私有方法,则不能继承。

cat = Cat()
cat.cry()
动物在叫。
动物在叫。
class Animal(object):
    def __cry(self):       
        print("动物在叫。")
        
class Dog(Animal):
    pass
class Cat(Animal):
    pass

dog = Dog()
dog.__cry()    #如果cry()方法为__cry()私有方法,则不能继承。
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-20-71a8dd49d0b3> in <module>()
      9 
     10 dog = Dog()
---> 11 dog.__cry()    #如果cry()方法为__cry()私有方法,则不能继承。


AttributeError: 'Dog' object has no attribute '__cry'

4.多态

继承可以帮助我们重复使用代码。但对于上面继承中的示例,无论是Dog还是Cat,调用父类的cry()方法时显示的都是“动物在叫。”,如果想让结果显示为“汪汪汪”和“喵喵喵”则需要对从父类继承的方法进行重写。

class Animal(object):
    def cry(self):       
        print("动物在叫。")
        
class Dog(Animal):
    def cry(self):       # 子类对父类的方法进行重写
        print("汪汪汪。")
    def eat(self):       #子类新增的方法
        print("小狗只吃狗粮。")
            
class Cat(Animal): 
    def cry(self):      # 子类对父类的方法进行重写
        print("喵喵喵。")
    def eat(self):
         print("小猫只吃猫粮。")

animal = Animal()
animal.cry()
            
dog = Dog()
dog.cry()
dog.eat()

cat = Cat()
cat.cry()
cat.eat()
动物在叫。
汪汪汪。
小狗只吃狗粮。
喵喵喵。
小猫只吃猫粮。
# 为了理解多态,请观察下面的结果。
print(f"dog 的类型为Dog吗:{isinstance(dog,Dog)}")
print(f"dog 的类型为Animal吗:{isinstance(dog,Animal)}")
dog 的类型为Dog吗:True
dog 的类型为Animal吗:True

因为Dog是从Animal继承下来的,当我们创建Dog的实例dog时,我们认为dog的数据类型是Dog,但dog同时也是Animal,Dog本来就是Animal的一种。实际上,任何依赖Animal作为参数的函数或方法都可以不加修改地正常运行,原因就在于多态。如下面的例子,在定义cry_twice()方法中,传入的参数为animal类型,想要具体到子类,不需要在改为dog或者cat,只需要传入相应类型的子类对象即可。

def  cry_twice(animal):
    animal.cry()
    animal.cry()

cry_twice(Animal())
cry_twice(Dog())
cry_twice(Cat())
动物在叫。
动物在叫。
汪汪汪。
汪汪汪。
喵喵喵。
喵喵喵。

5. 封装

封装并不等同于多态。多态可以让用户对不知道类(或对象类型)的对象进行方法调用,而封装可以不用关心对象是如何构建的,直接使用即可。

class Dog():
    def __init__(self,name,age):      
        self.__name = name
        self.__age = age
    def set_name(self,name):
        self.__name = name
    def set_age(self,age):
        self.__age = age
    def get_name(self):
        return self.__name
    def get_age(self):
        return self.__age
    def show_info(self):
        print(f"姓名:{self.get_name()}\t 年龄:{self.get_age()}")  
dog = Dog("小花","2岁")
dog.show_info()
dog.set_age("5岁")  
dog.show_info()
姓名:小花	 年龄:2岁
姓名:小花	 年龄:5岁

  每个实例都拥有各自的name和age数据。我们可以通过函数访问这些数据就没有必要从外面的函数访问,可以直接在Dog类内部定义访问数据的函数,这样就把“数据”封装起来了。这些封装数据的函数和Dog类本身是相关联的,我们称之为类的方法。这样一来,我们从外部看Dog类,只需要知道创建实例需要给出的name和age,如何输出是在Dog类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,不用知道内部实现的细节。

6. 多重继承

多重继承: 一个子类就可以继承多个父类,同时获得多个父类的所有非私有功能。

class People():
    def run(self):
        print("每天都要走路。")
        
class Student():
    def study(self):
        print("每天都要学习。")
        
class Senior(People,Student):
    pass

senoir = Senior()
senoir.run()
senoir.study()
每天都要走路。
每天都要学习。

7. 获取对象信息

7.1 type()函数

type(123)
int
type(dog)
__main__.Dog
type(senoir)
__main__.Senior

7.2 isinstance()函数

isinstance(dog,Dog)
True
isinstance(senoir,Senior)
True
isinstance(senoir,Student)
True
isinstance(senoir,People)
True
isinstance(senoir,object)
True
isinstance(cat,Dog)
False

7.3 dir()函数

dir(dog)  ##获取对象所有的属性和方法
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'age',
 'name',
 'show_info']

8. 类的专有方法

8.1. _str_

class Student(object):
    def __init__(self,name):
        self.name = name
        
print(Student('小明'))
<__main__.Student object at 0x000001844A42C390>
class Student(object):
    def __init__(self,name):
        self.name = name
    def __str__(self):
        return f'学生名称:{self.name}'
print(Student('小明'))
学生名称:小明

8.2._repr_

class Student(object):
    def __init__(self,name):
        self.name = name
    def __str__(self):
        return f'学生名称:{self.name}'
stu = Student('小明')
stu
<__main__.Student at 0x18448ae3ef0>
class Student(object):
    def __init__(self,name):
        self.name = name
    def __str__(self):
        return f'学生名称:{self.name}'
    __repr__ = __str__
stu = Student('小明')
stu
学生名称:小明

8.3. _iter_

如果想将一个类用于for …in循环,类似list或tuple一样,就必须实现一个_iter_()方法。该方法返回一个迭代对象,Python的for循环会不断调用该迭代对象的_next_()方法,获得循环的下一个值,直到遇到StopIteration错误时退出循环。

class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1 # 初始化:a和b
    
    def __iter__(self):
        return self   # 实例本身就是迭代对象,故返回自己
    
    def __next__(self):
        self.a, self.b = self.b, self.a + self. b # 计算下一个月的值
        if self.a > 10000: #退出循环
            raise StopIteration();
        return self.a  # 返回下一个值
for n in Fib():
    print(n)
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765

8.4 _getitem_

Fib()[5]
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-79-14290f619470> in <module>()
----> 1 Fib()[5]


TypeError: 'Fib' object does not support indexing

上面的实例,如果要取出第n个元素,直接Fib()[n],不可行。要像list一样按照下标取出元素,则需要实现__getitem__方法

class Fib(object):
    def __getitem__(self,n):
        a, b = 1, 1
        for x in range(n):
            a, b = b, a+b
        return a 
fib = Fib()
fib[5]
8

8.5 _getattr_

Python还提供了一种机制,就是写一个_getattr_()方法,动态返回一个不存在的属性。
注: 只有在没有找到属性的情况下才调用_getattr_,已有的属性(如name)不会在__getattr__中查找。此外,如果所有调用都会返回None(如stu.abc),就是定义的__getattr__默认返回None。

class Student(object):
    def __init__(self):
        self.name = "小明"
    
    def __getattr__(self,attr):
        if attr == "score":
            return 90
        
stu = Student()
print(stu.name)
print(stu.score)
小明
90

8.6 _call_

一般通通过对象名.方法名()调用实例,但是,任何类,如果定义了一个_call_()方法,就可以直接对实例进行调用,就合函数调用差不多。

class Student(object):
    def __init__(self,name):
        self.name =name
    def __call__(self):
        print(f"学生姓名:{self.name}")
stu = Student( "小明")
stu()  # 直接使用实例进行调用
学生姓名:小明

怎么判断一个变量是对象还是函数呢? 通过callable()函数可以判断一个对象是否为“可调用”对象。

callable(stu)
True
callable(Student("小明"))
True
callable(max)
True
callable(list)
True
callable([1,2,3,4])
False
callable("小明")
False
posted @ 2020-04-09 11:07  sinlearn  阅读(262)  评论(0编辑  收藏  举报