资讯专栏INFORMATION COLUMN

Python学习之路8.2-对Python类的补充

liukai90 / 2557人阅读

摘要:本章主要是对上一章类的补充。对于多态的补充子类可以被看成是父类的类型,但父类不能被看成是子类的类型。仍然以类为例,动物里有哺乳动物,卵生动物,有能飞的动物和不能飞的动物,这是两种大的分类方式。一般在中,以为结尾类的都作为接口。

《Python编程:从入门到实践》笔记。
本章主要是对上一章Python类的补充。
1. 从一个类派生出所有类

上一篇文章说道Python类的定义与继承一般是如下形式:

class A:   # 或者写成class A():
    pass

class B(A):
    pass

其实,对于类A,它并不是算是一个真正意义上的基类,而是和Java类似,Python中所有的类最终都继承自object类(首字母小写,比较特殊),所以对于A的定义可以写成如下形式:

class A(object):
    pass

只是通常把object给省略了。

2. 访问限制

从上一篇中我们知道,类的属性可以被直接访问,如果需要对访问做一些限制,我们可以通过定义相应的方法。在Python中,对于一般的属性,用C++或Java的话来说,它们都是公有属性,外部可以直接访问,比如像下面的这个name属性:

class A:
    def __init__(self, name):
        self.name = name

但如果我们在这个属性前面加两个下划线,将其变成如下形式:

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

那么name就变成了一个私有属性,它只能在对象的内部被访问,如果想以如下形式访问则会报错:

# 代码:
class A:
    -- snip --
    
a = A("test")
print(a.__name)

# 结果:
AttributeError: "A" object has no attribute "__name"

那是不是真的就访问不到这个属性了呢?说不清是有幸还是不幸,Python没有所谓的真正的私有属性,Python中类的所有属性都能被访问。Python解释器只是将__name换了个名称,变成了:

self._A__name

即在前面加了一个单下划线和类名。

# 代码:
class A:
    -- snip --
    
a = A("test")
print(a._A__name)

# 结果:
test

强烈不建议这样访问属性!而且,不同版本的Python解释器会将这样的属性改成不同的名字。

3. 使用装饰器(decorator)

在上一点中说到了通过方法来访问类的属性,这种方式一般叫做get/set方法,最后在调用时调用的是类的方法,现在我们使用Python内置的@property装饰器来访问类的属性,最后在调用时是调用的属性,实际上它是将类的方法通过装饰器变为属性。以下是通过装饰器和通过get/set方法来访问属性的代码比较:

class Teacher:
    def __init__(self, name):
        self.name = name

    def get_name(self):
        return self.name

    def set_name(self, name):
        # 可以加上一些限制
        self.name = name

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

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        # 可以加上一些限制
        self._name = name

t = Teacher("Miss")
s = Student("Boy")

print(t.get_name())
print(s.name)
t.set_name("Miss Lee")
s.name = "Kevin"

从上述代码也可以看出,定义的时候,两者的代码量区别其实不大,但是在调用的时候,明显使用装饰器更方便些。

4. 类中其它类型的属性

类中除了普通的属性,以及上述的私有属性,还有前后都有双下划线的属性,例如__xxx__,它们是特殊变量,可以被直接访问,不是私有属性,所以一般不要起__name____score__这样的属性名,对于方法也是如此,不光有想__init__()这样的方法,还有很多前后都有双下划线的方法,比如__del__(),它是类的析构函数。在以后的文章中还会介绍许多这种方法。

不光有双下划线的属性,还有单下划线的比如 _name,前单下划线,它表示的意思是:虽然能被访问,但请将其看做私有属性,不要随便访问。

5. 对于多态的补充

子类可以被看成是父类的类型,但父类不能被看成是子类的类型。比如:

# 代码:
class Animal:
    pass

class Dog(Animal):
    pass

a = Animal()
d = Dog()

print(isinstance(d, Animal))
print(isinstance(a, Dog))

# 结果:
True
False

也就是说,如果我们定义了这样一个函数:

def animal_run(animal):
    animal.run()

它接收Animal及其子类的所有对象,只要该类的run()方法正确编写,Python都能在解释时正确调用相应类的run()方法,即调用方只管调用animal_run()函数,不用管类的run()方法的细节,不管是现有的类还是新扩展出的子类,只要保证run()正确实现了,那么animal_run()就是正确的。这就是著名的“开闭原则”:对扩展开放,对修改封闭。

6. 静态语言与动态语言

仍以上面的animal_run()函数为例。对于像Java这样的静态语言,传入的参数必须是Animal及其子类,否则就无法调用run()方法。而对于像Python这样的动态语言,传入的不一定要求是Animal及其子类,只要这个对象有run()方法就行了。这就是动态语言的“鸭子类型”,只要“看起来像鸭子,走起道来像鸭子”,那它就能被看做是鸭子。Python的“file-like object”就是一种“鸭子类型”,对于真正的文件对象,都有一个read()方法,用于返回文件内容。但对于其他对象,只要正确实现了read()方法,即使它不是文件对象,它也能被看做是文件。

7. 多重继承与MixIn设计

前一篇文章中的继承是单继承,但Python和C++一样,支持多重继承;Java只支持单继承,她通过接口类来实现多重继承的效果。首先需要搞清楚多重继承为什么存在。仍然以Animal类为例,动物里有哺乳动物,卵生动物,有能飞的动物和不能飞的动物,这是两种大的分类方式。如果我们要派生出一个能飞的哺乳动物(比如蝙蝠),如果按照单一继承,可以按如下方式:

也可以先从Animal继承出RunnableFlyable两个类,再继承出哺乳类和卵生类(相当于将上图的二三层换了位置),但从这种单继承可以看出,如果分类增多,类的数量将呈指数级增加。故而一般采用多重继承的方式:

class Animal:
    pass

class Mammalia(Animal):
    pass

class Flyable:
    def fly(self):
        print("Flying...")

class Bat(Mammalia, Flyable):
    pass

这样Bat类将具有MammaliaFlyable两个父类的所有属性与方法。一般在Java中,以able为结尾类的都作为接口。

在设计类的继承的时候,一般主线都是单一继承的,像上述例子中的从Animal派生出Manmalia,但如果后续的类中要混入一些额外的功能,但这功能又不是这个子类所独有的,比如上述的Flyable,那么就可以通过多重继承,从ManmaliaRunnable派生出Bat类,这就是MinIn设计,Java中采用接口来实现这种设计。

为了更好的看出继承关系,一般将RunnableFlyable类的名字改为RunnableMixInFlyableMixIn,同时,还可以定义出肉食动物CarnivorousMixIn和植食动物HerbivoresMixIn,让子类同时拥有好几个MinIn

class Dog(Mammalia, RunnableMixIn, CarnivorousMixIn):
    pass

所以在设计类时,我们应该优先考虑通过多重继承来组合多个MinIn,而不是直接考虑更多层次的继承关系。

最后,本篇较多内容是根据廖雪峰老师的博客再理解而来的,感谢廖雪峰老师!

迎大家关注我的微信公众号"代码港" & 个人网站 www.vpointer.net ~

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/41793.html

相关文章

  • Python学习之路30-接口:从协议到抽象基类

    摘要:本篇内容将从鸭子类型的动态协议,逐渐过渡到使接口更明确能验证实现是否符合规定的抽象基类。抽象基类介绍完动态实现接口后,现在开始讨论抽象基类,它属于静态显示地实现接口。标准库中的抽象基类从开始,标准库提供了抽象基类。 《流畅的Python》笔记。本篇是面向对象惯用方法的第四篇,主要讨论接口。本篇内容将从鸭子类型的动态协议,逐渐过渡到使接口更明确、能验证实现是否符合规定的抽象基类(Abst...

    LucasTwilight 评论0 收藏0
  • Python学习之路28-符合Python风格的

    摘要:本篇继续学习之路,实现更多的特殊方法以让自定义类的行为跟真正的对象一样。之所以要让向量不可变,是因为我们在计算向量的哈希值时需要用到和的哈希值,如果这两个值可变,那向量的哈希值就能随时变化,这将不是一个可散列的对象。 《流畅的Python》笔记。本篇是面向对象惯用方法的第二篇。前一篇讲的是内置对象的结构和行为,本篇则是自定义对象。本篇继续Python学习之路20,实现更多的特殊方法以让...

    Eric 评论0 收藏0
  • Python学习之路10-测试代码

    摘要:也就是说,你可以将上述代码中的看做单元测试,而将看做测试用例。在测试类中的每一个测试方法都必须以开头,否则将不会被认定是一个单元测试。 《Python编程:从入门到实践》笔记。本章主要学习如何使用Python标准库中的unittest模块对代码进行简单的测试。 1. 前言 作为初学者,并非必须为你尝试的所有项目编写测试;但参与工作量较大的项目时,你应对自己编写的函数和类的重要行为进行测...

    huangjinnan 评论0 收藏0
  • Python学习之路10-测试代码

    摘要:也就是说,你可以将上述代码中的看做单元测试,而将看做测试用例。在测试类中的每一个测试方法都必须以开头,否则将不会被认定是一个单元测试。 《Python编程:从入门到实践》笔记。本章主要学习如何使用Python标准库中的unittest模块对代码进行简单的测试。 1. 前言 作为初学者,并非必须为你尝试的所有项目编写测试;但参与工作量较大的项目时,你应对自己编写的函数和类的重要行为进行测...

    Developer 评论0 收藏0
  • Python学习之路27-象引用、可变性和垃圾回收

    摘要:函数的参数作为引用时唯一支持的参数传递模式是共享传参,它指函数的形参获得实参中各个引用的副本,即形参是实参的别名。而在上面这个例子中,类的属性实际上是形参所指向的对象所指对象,的别名。 《流畅的Python》笔记本篇是面向对象惯用方法的第一篇,一共六篇。本篇主要是一些概念性的讨论,内容有:Python中的变量,对象标识,值,别名,元组的某些特性,深浅复制,引用,函数参数,垃圾回收,de...

    Batkid 评论0 收藏0

发表评论

0条评论

liukai90

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<