python面向对象编程进阶(代码片段)

五包辣条! 五包辣条!     2023-03-21     190

关键词:

前言

上上篇我们讲解了Python面向对象编程的基础知识,《Python基础篇:面向对象怎样炼成的》这篇我们继续来讨论面向对象编程相关的内容。

目录

可见性和属性装饰器

在很多面向对象编程语言中,对象的属性通常会被设置为私有(private)或受保护(protected)的成员,简单的说就是不允许直接访问这些属性;对象的方法通常都是公开的(public),因为公开的方法是对象能够接受的消息,也是对象暴露给外界的调用接口,这就是所谓的访问可见性。在Python中,可以通过给对象属性名添加前缀下划线的方式来说明属性的访问可见性,例如,可以用__name表示一个私有属性,_name表示一个受保护属性,代码如下所示。

class Student:

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

    def study(self, course_name):
        print(f'self.__name正在学习course_name.')


stu = Student('王大锤', 20)
stu.study('Python程序设计')
print(stu.__name)

上面代码的最后一行会引发AttributeError(属性错误)异常,异常消息为:'Student' object has no attribute '__name'。由此可见,以__开头的属性__name是私有的,在类的外面无法直接访问,但是类里面的study方法中可以通过self.__name访问该属性。

需要提醒大家的是,Python并没有从语法上严格保证私有属性的私密性,它只是给私有的属性和方法换了一个名字来阻挠对它们的访问,事实上如果你知道更换名字的规则仍然可以访问到它们,我们可以对上面的代码稍作修改就可以访问到私有的属性。

class Student:

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

    def study(self, course_name):
        print(f'self.__name正在学习course_name.')


stu = Student('王大锤', 20)
stu.study('Python程序设计')
print(stu._Student__name, stu._Student__age)

Python中有一句名言:“We are all consenting adults here”(大家都是成年人)。Python语言的设计者认为程序员要为自己的行为负责,而不是由Python语言本身来严格限制访问可见性,而大多数的程序员都认为开放比封闭要好,把对象的属性私有化并不是编程语言必须的东西,所以Python并没有从语法上做出最严格的限定。

Python中可以通过property装饰器为“私有”属性提供读取和修改的方法,之前我们提到过,装饰器通常会放在类、函数或方法的声明之前,通过一个@符号表示将装饰器应用于类、函数或方法。

class Student:

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

    # 属性访问器(getter方法) - 获取__name属性
    @property
    def name(self):
        return self.__name
    
    # 属性修改器(setter方法) - 修改__name属性
    @name.setter
    def name(self, name):
        # 如果name参数不为空就赋值给对象的__name属性
        # 否则将__name属性赋值为'无名氏',有两种写法
        # self.__name = name if name else '无名氏'
        self.__name = name or '无名氏'
    
    @property
    def age(self):
        return self.__age


stu = Student('王大锤', 20)
print(stu.name, stu.age)    # 王大锤 20
stu.name = ''
print(stu.name)    # 无名氏
# stu.age = 30     # AttributeError: can't set attribute

在实际项目开发中,我们并不经常使用私有属性,属性装饰器的使用也比较少,所以上面的知识点大家简单了解一下就可以了。

动态属性

Python是一门动态语言,维基百科对动态语言的解释是:“在运行时可以改变其结构的语言,例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。动态语言非常灵活,目前流行的Python和JavaScript都是动态语言,除此之外如PHP、Ruby等也都属于动态语言,而C、C++等语言则不属于动态语言”。

在Python中,我们可以动态为对象添加属性,这是Python作为动态类型语言的一项特权,代码如下所示。需要提醒大家的是,对象的方法其实本质上也是对象的属性,如果给对象发送一个无法接收的消息,引发的异常仍然是AttributeError

class Student:

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


stu = Student('王大锤', 20)
# 为Student对象动态添加sex属性
stu.sex = '男'

如果不希望在使用对象时动态的为对象添加属性,可以使用Python的__slots__魔法。对于Student类来说,可以在类中指定__slots__ = ('name', 'age'),这样Student类的对象只能有nameage属性,如果想动态添加其他属性将会引发异常,代码如下所示。

class Student:
    __slots__ = ('name', 'age')

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


stu = Student('王大锤', 20)
# AttributeError: 'Student' object has no attribute 'sex'
stu.sex = '男'

静态方法和类方法

之前我们在类中定义的方法都是对象方法,换句话说这些方法都是对象可以接收的消息。除了对象方法之外,类中还可以有静态方法和类方法,这两类方法是发给类的消息,二者并没有实质性的区别。在面向对象的世界里,一切皆为对象,我们定义的每一个类其实也是一个对象,而静态方法和类方法就是发送给类对象的消息。那么,什么样的消息会直接发送给类对象呢?

举一个例子,定义一个三角形类,通过传入三条边的长度来构造三角形,并提供计算周长和面积的方法。计算周长和面积肯定是三角形对象的方法,这一点毫无疑问。但是在创建三角形对象时,传入的三条边长未必能构造出三角形,为此我们可以先写一个方法来验证给定的三条边长是否可以构成三角形,这种方法很显然就不是对象方法,因为在调用这个方法时三角形对象还没有创建出来。我们可以把这类方法设计为静态方法或类方法,也就是说这类方法不是发送给三角形对象的消息,而是发送给三角形类的消息,代码如下所示。

class Triangle(object):
    """三角形类"""

    def __init__(self, a, b, c):
        """初始化方法"""
        self.a = a
        self.b = b
        self.c = c

    @staticmethod
    def is_valid(a, b, c):
        """判断三条边长能否构成三角形(静态方法)"""
        return a + b > c and b + c > a and a + c > b

    # @classmethod
    # def is_valid(cls, a, b, c):
    #     """判断三条边长能否构成三角形(类方法)"""
    #     return a + b > c and b + c > a and a + c > b

    def perimeter(self):
        """计算周长"""
        return self.a + self.b + self.c

    def area(self):
        """计算面积"""
        p = self.perimeter() / 2
        return (p * (p - self.a) * (p - self.b) * (p - self.c)) ** 0.5

上面的代码使用staticmethod装饰器声明了is_valid方法是Triangle类的静态方法,如果要声明类方法,可以使用classmethod装饰器。可以直接使用类名.方法名的方式来调用静态方法和类方法,二者的区别在于,类方法的第一个参数是类对象本身,而静态方法则没有这个参数。简单的总结一下,对象方法、类方法、静态方法都可以通过类名.方法名的方式来调用,区别在于方法的第一个参数到底是普通对象还是类对象,还是没有接受消息的对象。静态方法通常也可以直接写成一个独立的函数,因为它并没有跟特定的对象绑定。

继承和多态

面向对象的编程语言支持在已有类的基础上创建新类,从而减少重复代码的编写。提供继承信息的类叫做父类(超类、基类),得到继承信息的类叫做子类(派生类、衍生类)。例如,我们定义一个学生类和一个老师类,我们会发现他们有大量的重复代码,而这些重复代码都是老师和学生作为人的公共属性和行为,所以在这种情况下,我们应该先定义人类,再通过继承,从人类派生出老师类和学生类,代码如下所示。

class Person:
    """人类"""

    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def eat(self):
        print(f'self.name正在吃饭.')
    
    def sleep(self):
        print(f'self.name正在睡觉.')


class Student(Person):
    """学生类"""
    
    def __init__(self, name, age):
        # super(Student, self).__init__(name, age)
        super().__init__(name, age)
    
    def study(self, course_name):
        print(f'self.name正在学习course_name.')


class Teacher(Person):
    """老师类"""

    def __init__(self, name, age, title):
        # super(Teacher, self).__init__(name, age)
        super().__init__(name, age)
        self.title = title
    
    def teach(self, course_name):
        print(f'self.nameself.title正在讲授course_name.')



stu1 = Student('白元芳', 21)
stu2 = Student('狄仁杰', 22)
teacher = Teacher('武则天', 35, '副教授')
stu1.eat()
stu2.sleep()
teacher.teach('Python程序设计')
stu1.study('Python程序设计')

继承的语法是在定义类的时候,在类名后的圆括号中指定当前类的父类。如果定义一个类的时候没有指定它的父类是谁,那么默认的父类是object类。object类是Python中的顶级类,这也就意味着所有的类都是它的子类,要么直接继承它,要么间接继承它。Python语言允许多重继承,也就是说一个类可以有一个或多个父类,关于多重继承的问题我们在后面会有更为详细的讨论。在子类的初始化方法中,我们可以通过super().__init__()来调用父类初始化方法,super函数是Python内置函数中专门为获取当前对象的父类对象而设计的。从上面的代码可以看出,子类除了可以通过继承得到父类提供的属性和方法外,还可以定义自己特有的属性和方法,所以子类比父类拥有的更多的能力。在实际开发中,我们经常会用子类对象去替换掉一个父类对象,这是面向对象编程中一个常见的行为,也叫做“里氏替换原则”(Liskov Substitution Principle)。

子类继承父类的方法后,还可以对方法进行重写(重新实现该方法),不同的子类可以对父类的同一个方法给出不同的实现版本,这样的方法在程序运行时就会表现出多态行为(调用相同的方法,做了不同的事情)。多态是面向对象编程中最精髓的部分,当然也是对初学者来说最难以理解和灵活运用的部分,我们会在之后专门的例子来讲解多态这个知识点。

总结

Python是动态语言,Python中的对象可以动态的添加属性。在面向对象的世界中,一切皆为对象,我们定义的类也是对象,所以类也可以接收消息,对应的方法是类方法或静态方法。通过继承,我们可以从已有的类创建新类,实现对已有类代码的复用。

python面向对象编程进阶(代码片段)

前言上上篇我们讲解了Python面向对象编程的基础知识,《Python基础篇:面向对象怎样炼成的》这篇我们继续来讨论面向对象编程相关的内容。目录前言可见性和属性装饰器动态属性静态方法和类方法继承和多态总结可见... 查看详情

进阶学python:python面向对象基础!(代码片段)

什么是编程范式?在了解面向对象编程之前,我们需要了解三大编程范式以及其之间的区别和利弊即:面向过程编程、函数式编程、面向对象编程。编程:是程序员用特定的语法+数据结构+算法组成的代码来... 查看详情

python面向对象进阶(代码片段)

...好的使用面向对象编程思想进行程序开发,我们还需要对Python中的面向对象编程进行更为深入的了解。@property装饰器之前我们讨论过Python中属性和方法访问权限的问题,虽然我们不建议将属性设置为私有的,但是如果直接将属性... 查看详情

面向对象编程进阶(代码片段)

python内置函数isinstance(obj,class)  判断一个对象是否是已知的类型,类似type()isinstance语法:isinstance(object,classinfo)object是实例/对象classinfo可以是直接或间接类名,基本类型或者由他们组成的元组返回值如果对象的类型与classinfo相... 查看详情

进阶学python:python面向对象系列之魔法方法!(代码片段)

本篇主要介绍面向对象编程中类的魔法属性,这些魔法属性均有其特殊的功能,即执行某些操作时,会自动触发这些魔法方法的执行,了解这些魔法方法可以更好的了解面向对象编程的实现原理;在学习魔法... 查看详情

python面向对象编程进阶(代码片段)

前言上上篇我们讲解了Python面向对象编程的基础知识,《Python基础篇:面向对象怎样炼成的》这篇我们继续来讨论面向对象编程相关的内容。目录前言可见性和属性装饰器动态属性静态方法和类方法继承和多态总结可见... 查看详情

python面向对象编程进阶(代码片段)

前言上上篇我们讲解了Python面向对象编程的基础知识,《Python基础篇:面向对象怎样炼成的》这篇我们继续来讨论面向对象编程相关的内容。目录前言可见性和属性装饰器动态属性静态方法和类方法继承和多态总结可见... 查看详情

面向对象进阶(代码片段)

...中,类就是一组用来描述如何生成一个对象的代码段。在Python中这一点仍然成立:>>>classObjectCreator(object):…pass…>>>my_object=ObjectCreator()>>>printmy_object<__main__.ObjectCreatorobjectat0 查看详情

面向对象编程进阶版(代码片段)

isinstance(obj,cls)和issubclass(sub,super)isinstance(obj,cls)检查obj是否是类cls的实例化对象issubclass(sub,super)检查sub类是否是super类的派生类#isinstance(obj,cls)检查obj是否是类cls的实例化对象classFoo(object):passclassFoo1(Foo):passobj= 查看详情

面向对象(进阶)(代码片段)

一.关于上篇面向对象(初识)总结:1.面向对象是一种编程方式,此编程方式的实现是基于对类和对象的使用2.类:是一个模板,模板中包装了多个"函数"供使用(可以讲多个函数中公用的变量封装到对象中)3.对象,根据模板创建的实例(即:... 查看详情

python中面向对象初识到进阶(代码片段)

面向对象初识到进阶#面向对象结构:#class类名:#def__init__(self,参数1,参数2):#self.对象的属性1=参数1#self.对象的属性2=参数2##def方法名(self):pass##def方法名2(self):pass##对象名=类名(1,2)#对象就是实例,代表一个具体的东西##类名():类名+... 查看详情

python全栈--9.1--面向对象进阶-super类对象成员--类属性-私有属性查找源码类对象步骤类特殊成员isinstanceissubclass异常处理(代码片段)

...向对象三大特性:封装、继承和多态 本篇将详细介绍Python类的成员 查看详情

python进阶指南(编程轻松进阶):十六面向对象编程和继承(代码片段)

原文:http://inventwithpython.com/beyond/chapter16.html定义一个函数,并从几个地方调用它,可以省去复制和粘贴源代码的麻烦。不复制代码是一个很好的实践,因为如果你需要修改它(无论是为了修复一个错误还是添... 查看详情

面向对象进阶(代码片段)

...的设计领域所采用,并在Lisp和面向对象方面取得了成绩。2python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可 查看详情

面向对象进阶(代码片段)

...计领域所采用,并在Lisp和面向对象方面取得了成绩。 2python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物 查看详情

python------面向对象进阶反射详解(重点)(代码片段)

一.反射  通过字符串映射或者修改程序运行时的状态,属性,或者方法。1.getattr(object,name,default=None)2.hasattr(object,name)3.setattr(x,y,v)4.delattr(x,y)1classDog(object):2def__init__(self,name):3self.name=name45defeat(sel 查看详情

python之面向对象(代码片段)

一、三大编程范式python是一门面向对象的编程语言。不同的编程样式有不同的风格,不同的风格有他不同的牛逼之处。三大编程范式:面向过程编程函数式编程面向对象编程二、面向对象设计与面向对象编程面向对象设计(Object... 查看详情

python100天学习笔记day9面向对象进阶(代码片段)

...使用面向对象编程思想进行程序开发,我们还需要对Python中的面向对象编程进行更为深入的了解。@property装饰器之前我们讨论过Python中属性和方法访问权限的问题,虽然我们不建议将属性设置为私有的,但是如果... 查看详情