Python核心丨面向对象(上)

面向对象


对象

生物课上的“界门纲目科属种”的概念,就是将生物分化为不同的类型,生活中我们也习惯对身边事物进行分类:

  • 猫和狗都是动物;
  • 直线和圆都是平面几何的图形;
  • 《哈利波特》和《冰与火之歌》都是小说

同一类事物有着相似的特性:

  • 动物会动;
  • 平面图形有面积和周长;
  • 小说有作者

Python面向对象的应用代码

class Document():
    def __init__(self, title, author, context):
        print('init function called')
        self.title = title
        self.author = author
        self.__context = context # __开头的属性是私有属性

    def get_context_length(self):
        return len(self.__context)

    def intercept_context(self, length):
        self.__context = self.__context[:length]

harry_potter_book = Document('Harry Potter', 'J. K. Rowling', '... Forever Do not believe any thing is capable of thinking independently ...')

print(harry_potter_book.title)
print(harry_potter_book.author)
print(harry_potter_book.get_context_length())

harry_potter_book.intercept_context(10)

print(harry_potter_book.get_context_length())

print(harry_potter_book.__context)

########## 输出 ##########

init function called
Harry Potter
J. K. Rowling
77
10

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-5-b4d048d75003> in <module>()
     22 print(harry_potter_book.get_context_length())
     23 
---> 24 print(harry_potter_book.__context)

AttributeError: 'Document' object has no attribute '__context'

基本概念

  • 类:一群有着相似的事物的集合,对应Python的class
  • 对象:集合中的一个事物,对应由class生成的某一个object,例代码中的harry_potter_book
  • 属性:对象的某个静态特征,比如代码中的title、author和__context
  • 函数:对象的某个动态能力,如代码中的intercept_context()函数

注:以上是为了方便理解的说法,说法并不是非常严谨

类,一群有着相同属性和函数的对象的集合

通过上边的代码可以看到,class Document定义了Document类,它有三个函数。

其中,init表示构造函数,即一个对象生成时会被自动调用的函数。

当harry_potter_book = Document(…)这行代码被执行时,'init function called’字符串会被打印出来。

而另外两个函数则为类的普通函数,调用它们来对对象的属性做一些事情。

class Document还有三个属性,title、author和__context分别表示标题、作者和内容,通过构造函数传入。

注:一个属性以__开头,就默认这个属性是私有属性。

私有属性,指不希望在类的函数之外的地方被访问和修改的属性。

深度解析

在工程实践中,可能会遇到的一些问题:

  • 如何在一个类中定义一些常量,每个对象都可以方便访问这些常量而不用重新塑造?

  • 如果一个函数不涉及到访问修改这个类的属性,而放到类外边有点不恰当,怎么做才能更优雅呢?

class Document():
    
    WELCOME_STR = 'Welcome! The context for this book is {}.'
    
    def __init__(self, title, author, context):
        print('init function called')
        self.title = title
        self.author = author
        self.__context = context
    
    # 类函数
    @classmethod
    def create_empty_book(cls, title, author):
        return cls(title=title, author=author, context='nothing')
    
    # 成员函数
    def get_context_length(self):
        return len(self.__context)
    
    # 静态函数
    @staticmethod
    def get_welcome(context):
        return Document.WELCOME_STR.format(context)


empty_book = Document.create_empty_book('What Every Man Thinks About Apart from Sex', 'Professor Sheridan Simove')


print(empty_book.get_context_length())
print(empty_book.get_welcome('indeed nothing'))

########## 输出 ##########

init function called
7
Welcome! The context for this book is indeed nothing.

第一个问题:

在Python的类里,只需要和函数并列地声明并赋值,就可以实现这一点。

例如代码中的WELCOME_STR(用全大写表示常量),可以在类中使用self.WELCOME_STR,或者在类外使用Entity.WELCOME_STR,来表达这个字符串。

第二个问题:

类函数、成员函数产生的影响是动态的,能够访问或者修改对象的属性;静态函数与类没有什么关联,最明显的特征便是,静态函数的第一个参数没有任何特殊性。

一般来说,静态函数可以用来做一些简单独立的任务,既方便测试,也能优化代码结构。通过在函数前一行加上@staticmethod(装饰器)来表示。

而类函数的第一参数一般为cls,表示必须传进一个类进来。类函数需要装饰器@classmethod来声明。

成员函数则是最正常的类的函数,不需要任何装饰器声明,第一个参数self代表当前对象的引用,可以通过此函数,来实现想要的查询/修改类的属性等功能。

继承

一个类拥有另一个类的特性,也拥有不同于另一个类的独特特征。

第一个类叫做子类,另一个类叫做父类,特征就是类的属性和函数。

class Entity():
    def __init__(self, object_type):
        print('parent class init called')
        self.object_type = object_type
    
    def get_context_length(self):
        raise Exception('get_context_length not implemented')
    
    def print_title(self):
        print(self.title)

class Document(Entity):
    def __init__(self, title, author, context):
        print('Document class init called')
        Entity.__init__(self, 'document')
        self.title = title
        self.author = author
        self.__context = context
    
    def get_context_length(self):
        return len(self.__context)
    
class Video(Entity):
    def __init__(self, title, author, video_length):
        print('Video class init called')
        Entity.__init__(self, 'video')
        self.title = title
        self.author = author
        self.__video_length = video_length
    
    def get_context_length(self):
        return self.__video_length

harry_potter_book = Document('Harry Potter(Book)', 'J. K. Rowling', '... Forever Do not believe any thing is capable of thinking independently ...')
harry_potter_movie = Video('Harry Potter(Movie)', 'J. K. Rowling', 120)

print(harry_potter_book.object_type)
print(harry_potter_movie.object_type)

harry_potter_book.print_title()
harry_potter_movie.print_title()

print(harry_potter_book.get_context_length())
print(harry_potter_movie.get_context_length())

########## 输出 ##########

Document class init called
parent class init called
Video class init called
parent class init called
document
video
Harry Potter(Book)
Harry Potter(Movie)
77
120

Document和Video有相似的地方,都有相应的标题、作者和内容等属性。

可以从中抽象出一个叫做Entity的类,来做为它俩的父类。

需要注意的是构造函数。每个类都有构造函数,继承类在生成对象的时候,是不会自动调用父类的构造函数,因此必须在init()函数中显式调用父类的构造函数。

其次需要注意父类get_context_length()函数。如果使用Entity直接生成对象,调用这个函数就会raise error终端程序的执行。

这是一种很好的写法叫做函数重写,可以使子类必须重写一遍get_context_length()函数,来覆盖掉原有函数。

最后可以看到print_tilte()函数,这个函数定义在父类中,但是子类的对象可以毫无阻力地使用它来打印title。

体现了继承的优势:减少重复的代码,降低系统的熵值(复杂度)。

抽象函数和抽象类
from abc import ABCMeta, abstractmethod

class Entity(metaclass=ABCMeta):
    @abstractmethod
    def get_title(self):
        pass

    @abstractmethod
    def set_title(self, title):
        pass

class Document(Entity):
    def get_title(self):
        return self.title
    
    def set_title(self, title):
        self.title = title

document = Document()
document.set_title('Harry Potter')
print(document.get_title())

entity = Entity()

########## 输出 ##########

Harry Potter

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-7-266b2aa47bad> in <module>()
     21 print(document.get_title())
     22 
---> 23 entity = Entity()
     24 entity.set_title('Test')

TypeError: Can't instantiate abstract class Entity with abstract methods get_title, set_title

从这段代码中,可以看出Entity本身是没有什么用的,只需拿来定义Document和Video的一些基本元素就够了。

不过一不小心生成Entity的对象怎么办,为了防止这样的手误,就可以使用抽象类。

抽象类是一种特殊的类,它生下来就是作为父类存在的,一旦对象化就会报错。

抽样函数定义在抽象类中,子类必须重写该函数才能使用。抽象函数使用@abstractmethod来表示。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值