Python 面向对象 继承 多态 slots等特殊方法 【进阶篇 1 】

前面梳理回顾了python的入门基础知识,一共有三篇从python的下载安装运行环境配置,到如何定义变量、基本数据类型、条件语句、循环语句、python中list容器 dict容器定义及数组数据的用法、函数的基础知识。请参考:

Python 3 入门基础知识【1】数据类型 安装下载 推荐-CSDN博客

Python 3 入门基础知识 之数据容器及用法【2】 推荐-CSDN博客

Python 3 入门基础知识【3】递归函数以及参数部分-CSDN博客


这里是梳理了下python面向对象编程以及类的的内容。

目录

一、python面向对象编程

1.class类与实例化

1.1 类的定义

1.2 实例化及属性

1.3  类属性和实例属性的优先级

1.4 私有属性访问限制

1.5 定义实例方法

1.6 Python定义类方法

二、类的继承 

1.python的继承类

2.函数isinstance()

 3.多肽

4.获取对象信息

三、特殊方法 

1.__str__ 和 __repr__方法

2.__len__方法

3.python的数学运算__add__、__sub__、__mul__、__truediv__

4.__slots__方法


一、python面向对象编程

1.class类与实例化

1.1 类的定义

在Python中,通过class关键字定义一个类,比如我们需要定义一个人的类。按照 Python 的编程习惯,类名以大写字母开头。

# 第一种方式
class Person:  pass

# 第二种方式
class Person(): pass  

#第三种方式
class Person(object):  pass

类属性 

类和实例对象是有区别的,类是抽象,是模板,而实例则是根据类创建的对象,实例属性每个实例各自拥有,互相独立,而类属性有且只有一份。定义类属性可以直接在 class 中定义,比如在前面的Animal类中,加入地域的类属性:

class Animal(object):
    localtion = 'Asia'
    def __init__(self, name, age):
        self.name = name
        self.age = age

 localtion就是属于Animal这个类的类属性,此后,通过Animal()实例化的所有对象,都可以访问到localtion,并且得到唯一的结果

dog = Animal('wangwang', 1)
cat = Animal('mimi', 3)
print(dog.localtion) # ==> Asia
print(cat.localtion) # ==> Asia
# 类属性,也可以通过类名直接访问
print(Animal.localtion) # ==> Asia

 类属性也是可以动态添加和修改的,需要注意的是,类属性只有一份,改变了,所有实例可以访问到的类属性都会变更:

Animal.localtion = 'Africa'
print(cat.localtion) # ==>Africa
print(dog.localtion) # ==>Africa
1.2 实例化及属性

类的实例化

# 定义类
class Person(object):  pass 

# 类的实例化
xiaohong = Person()

给实例增加属性

xiaohong.age = 18
print(xiaohong.age)  #18
xiaohong.age = xiaohong.age + 1  #可以像变量一样运算
print(xiaohong.age)

初始化具有属性的实例

这里 __init__() 方法的第一个参数必须是 self(也可以用别的名字,但建议使用习惯用法),实例类除了第一个self,其他参数都要有,访问属性和上面一样,如果访问不存在的属性会报错。

1.3  类属性和实例属性的优先级

如果类属性和实例属性名字相同时,我们在前面类定义的基础上,在实例属性中,也初始化一个localtion的属性。

​class Animal(object):
    localtion = 'Asia'
    def __init__(self, name, age, localtion):
        self.name = name
        self.age = age
        self.localtion = localtion

我们实例化并打印看结果如何

​dog = Animal('wangwang', 1, 'GuangDong')
cat = Animal('mimi', 3, 'ChongQing')
print(dog.localtion) # ==> GuangDong
print(cat.localtion) # ==> ChongQing
print(Animal.localtion) # ==> Asia

可见,在类属性和实例属性同时存在的情况下,实例属性的优先级是要高于类属性的,在操作实例的时候,优先是操作实例的属性

当实例没有和类同名的时候,通过实例对象,依然可以访问到类属性,那通过实例,可不可以修改类属性呢?

class Animal(object):
    localtion = 'Asia'
    def __init__(self, name, age):
        self.name = name
        self.age = age

cat = Animal('mimi', 3)
print(cat.localtion) # ==> Asia

cat.localtion = 'Africa'
print(Animal.localtion) # ==> Asia

可见通过实例是无法修改类的属性的,事实上,通过实例方法修改类属性,只是给实例绑定了一个对应的实例属性:

​# 新增的实例属性
print(cat.localtion) # ==> Africa

注意,尽量不要通过实例来修改类属性,否则很容易引发意想不到的错误。

1.4 私有属性访问限制

并不是所有的属性都可以被外部访问的,这种不能被外部访问的属性称为私有属性。私有属性是以双下划线'__'开头的属性。

# 类私有属性
class Animal(object):
    __localtion = 'Asia'

# 实例私有属性
class Animal(object):
    def __init__(self, name, age, localtion):
        self.name = name
        self.age = age
        self.__localtion = localtion
print(Animal.__localtion)
print(dog.__localtion)

#提示错误
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Animal' object has no attribute '__localtion'

在外部访问私有属性将会抛出异常,提示没有这个属性。
虽然私有属性无法从外部访问,但是,从类的内部是可以访问的。私有属性是为了保护类或实例属性不被外部污染而设计的。

1.5 定义实例方法
class Person(object):

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

    def get_name(self):
        return self.__name

name是实例的私有属性,从外部是无法访问的,而get_name(self) 就是一个实例方法,在实例方法里面是可以操作私有属性的,注意,它的第一个参数是self。

p = Person('Alice')
print(p.get_name()) # ==> Alice

通过定义实例方法来操作私有属性的这种方法是推荐的,这种数据封装的形式除了能保护内部数据一致性外,还可以简化外部调用的难度。实例方法并不仅仅是为私有属性服务的,我们可以把和类的实例有关的操作都抽象成实例方法,比如:打印实例的详细信息等等。

 

1.6 Python定义类方法

如果需要需要操作类的私有属性,则应该定义类的方法。
默认的,在class中定义的全部是实例方法,实例方法第一个参数 self 是实例本身。类方法需要使用@classmethod来标记为类方法

class Animal(object):
    __localtion = 'Asia'
    def __init__(self, name, age):
        self.name = name
        self.age = age

    @classmethod
    def set_localtion(cls, localtion):
        cls.__localtion = localtion

    @classmethod
    def get_localtion(cls):
        return cls.__localtion

print(Animal.get_localtion()) # ==> Asia
Animal.set_localtion('Afica')
print(Animal.get_localtion()) # ==> Africa
  1. 类方法需要使用@classmethod来标记为类方法,否则定义的还是实例方法
  2. 类方法的第一个参数将传入类本身,通常将参数名命名为 cls,上面的 cls.__localtion 实际上相当于Animal.__localtion

 

二、类的继承 

1.python的继承类

类是抽象的,当一个类中定义了一些相同的属性,而另一个类也需要定义相同的属性,那么这个时候我们可以直接集成其他类,只需要新增自己需要的属性即可。比如老师和学生都属于人类,这个时候就定义一个Person类:

class Person(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

现在我们定义学生类,比如也有name和gender属性,另外多了一个学分,我们就可以继承Person类。

class Student(Person):
    def __init__(self, name, gender, score):
        super(Student, self).__init__(name, gender)
        self.score = score

student = Student('Alice', 'girl', 100)
print(student.name) # ==> Alice
print(student.gender) # ==> girl
print(student.score) # ==> 100
  1. class Student()定义的时候,需要在括号内写明继承的类Person
  2. 在__init__()方法,需要调用super(Student, self).__init__(name, gender),来初始化从父类继承过来的属性

2.函数isinstance()

前面我们回顾了python的基本数据类型,整数、浮点型、布尔型等,那我们如何知道一个变量是哪种数据类型呢?这个时候就要用到函数isinstance()方法了,这个方法可以判断一个变量的类型。

上面的person和student类分别实例化

p = Person('Tim', 'Male')
s = Student('Bob', 'Male', 88)

>>> isinstance(p, Person)
True # p是Person类型
>>> isinstance(p, Student)
False # p不是Student类型

>>> isinstance(s, Person)
True # s是Person类型
>>> isinstance(s, Student)
True # s是Student类型

说明在继承链上,一个父类的实例不能是子类类型,因为子类比父类多了一些属性和方法。s 也是Person类型,因为Student继承自Person,虽然它比Person多了一些属性和方法,但是,把 s 看成Person的实例也是可以的。在一条继承链上,一个实例可以看成它本身的类型,也可以看成它父类的类型。

isinstance也可以用于Python自有数据类型的判断。

​s = 'this is a string.'
n = 10
isinstance(s, int) # ==> False
isinstance(n, str) # ==> False

 3.多肽

在下面这个例子中,student和teacher类分别继承自 Person类,子类和父类分别有一个who方法,我们实例化后分别把函数内打印出来。我们可以看到打印结果,这种就被称为多态。

从定义上来讲,Student和Teacher都拥有来自父类Person继承的who()方法,以及自己定义的who()方法。但是在实际调用的时候,会首先查找自身的定义,如果自身有定义,则优先使用自己定义的函数;如果没有定义,则顺着继承链向上找:

class Boss(Person):
    def __init__(self, name, gender,company):
        super(Boss, self).__init__(name, gender)
        self.company = company

b = Boss('Bob', 'Male', 'Alibaba')
# 在Boss的定义类,没有定义who方法,所以会顺着继承链向上找到父类的who方法并且调用。
b.who() # ==> I am a Person, my name is Bob

多重继承

除了从一个父类继承外,Python允许从多个父类继承,称为多重继承。多重继承和单继承没有特别大的差异,只是在括号内加入多个需要继承的类的名字即可。

在多重继承里,父类虽然被继承了两次,但是__init__()的方法只调用一次。多重继承的目的是从两种继承树中分别选择并继承出子类,以便组合功能使用。

Python的网络服务器有TCPServer、UDPServer、UnixStreamServer、UnixDatagramServer,而服务器运行模式有 多进程ForkingMixin 和 多线程ThreadingMixin两种。如果没有多重继承,要实现下述所有可能的组合需要 4x2=8 个子类。

# 要创建多进程模式的 TCPServer:
class MyTCPServer(TCPServer, ForkingMixin)
    pass

# 要创建多线程模式的 UDPServer:
class MyUDPServer(UDPServer, ThreadingMixin):
    pass

4.获取对象信息

4.1 type()方法

我们通过isinstance()方法,可以判断一个对象是否是某个类型,通过type()函数,可以获得变量的类型。

n = 1
s = 'this is a string'
type(n) # ==> <class 'int'>  #获得变量类型 int
type(s) # ==> <class 'str'>

4.2 dir()方法 

通过dir()方法,可以获取变量的所有属性。在dir列出的属性中,有很多是以下划线开头和结尾的,这些都是特殊的方法,称为内建方法

>>> n = 1
>>> dir(n)
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', ...]
>>> s = 'this is a string'
>>> dir(s)
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', ...]

 4.3  getattr() 和 setattr( )

dir()返回的属性是字符串列表,如果已知一个属性名称,要获取或者设置对象的属性,就需要用 getattr() 和 setattr( )函数了。

​>>> getattr(p, 'name') # 获取name属性
'Alice'
>>> setattr(p, 'name', 'Adam') # 设置新的name属性
>>> s.name
'Adam'
>>> getattr(s, 'age') # 获取age属性,但是属性不存在,报错:
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Person' object has no attribute 'age'
>>> getattr(s, 'age', 20) # 获取age属性,如果属性不存在,就返回默认值20:
20

 

三、特殊方法 

python中的特殊方法有两个下划线开始和两个下划线结束。

1.__str__ 和 __repr__方法

对于Python的内建对象,比如int、dict、list等,通过str()方法,可以把这些对象转换为字符串对象输出。 通过str()打印的数据是对象的内建方法__str__返回的。

num = 12
str(num) # ==> '12'
d = {1: 1, 2: 2}
str(d) # ==> '{1: 1, 2: 2}'
l = [1,2,3,4,5]
str(l) # ==> '[1, 2, 3, 4, 5]'

对于自定义对象,通过str()方法,同样可以得到对象所对应的字符串结果,只不过结果会有些难理解

如果我们的类也想把容易理解的字符串输出的话,那么我们也需要实现类的__str__()方法。

这是因为 Python 定义了__str()__和__repr__()两种方法,__str()__用于显示给用户,而__repr__()用于显示给开发人员,当使用str()时,实际调用的是__str__()方法,而直接输入变量,调用的是__repr__()方法。

2.__len__方法

对于列表List或者元组Tuple,通过内建方法len(),可以得出列表或者元组中元素的个数。如果一个类表现得像一个list,想使用len()函数来获取元素个数时,则需要实现__len__()方法。

class Class:
    def __init__(self, students):
        self.students = students
    def __len__(self):
        return len(self.students)

students = ['Alice', 'Bob', 'Candy']
class_ = Class(students)
len(class_) # ==> 3

3.python的数学运算__add____sub____mul____truediv__

事实上,Python很多的操作都是通过内建函数来实现的,因此,只要我们的自定义类实现了相关的内建函数,我们的类对象,也可以做到加减乘除。

class Rational(object):
    def __init__(self, p, q):
        self.p = p
        self.q = q
    def __add__(self, r):
        return Rational(self.p * r.q + self.q * r.p, self.q * r.q)
    def __str__(self):
        return '{}/{}'.format(self.p, self.q)

r1 = Rational(1, 2)
r2 = Rational(2, 3)
print(r1 + r2)
7/6

Python的除法可以分为地板除和普通除法,地板除的特殊方法是__floordiv__,普通除法是__truediv__。地板除法和普通除法不一样,地板除法的结果只会向下取整数。 普通除法使用/表示,而地板除使用//表示

4.__slots__方法

由于Python是动态语言,任何实例在运行期都可以动态地添加属性,如果要限制添加的属性,就可以利用Python的一个特殊的__slots__来实现。

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

 __slots__的目的是限制当前类所能拥有的属性,避免因为外部属性的操作导致类属性越来越难以管理。

5.__call__方法

如果把一个类实例也变成一个可调用对象,可以实现一个特殊的方法__call__()。

我们把Person类变成一个可调用对象:

class Person(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

    def __call__(self, friend):
        print('My name is {}...'.format(self.name))
        print('My friend is {}...'.format(friend))
p = Person('Bob', 'Male')
p('Alice') # ==> 用函数的方式调用Person类的实例p
My name is Bob...
My friend is Alice...

到这里我们把

  1. python的面向对象编程,包括class类与实例化、类和实例化的属性以及优先级对比、私有属性的访问限制和类与实例化的方法;
  2. 类的继承、多态、isinstance()方法以及其他获取对象的信息的方法;
  3. python中的一些特殊方法:__str__、__repr__、__len__、__add__等方法。

这些知识进行了梳理,欢迎大家评论区探讨和交流,谢谢。

后续会继续总结python的模块、读写文件、网络编程、函数式编程。

  • 17
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值