美文网首页
面向对象编程(1)

面向对象编程(1)

作者: lvyz0207 | 来源:发表于2019-11-03 22:12 被阅读0次

    1.如何在一个类中定义一些常量,每个对象都可以方便访问这些常量而不用重新构造?
    2.如果一个函数不涉及到访问修改这个类的属性,而放到类外面有点不恰当,怎么做才能更优雅呢?
    3.既然类是一群相似的对象的集合,那么可不可以是一群相似的类的集合呢?

    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 的 class。
    对象:集合中的一个事物,这里对应由 class 生成的某一个 object,比如代码中的 harry_potter_book。
    属性:对象的某个静态特征,比如上述代码中的 title、author 和 __context。
    函数:对象的某个动态能力,比如上述代码中的 intercept_context () 函数。
    

    第一个问题,在 Python的类里,你只需要和函数并列地声明并赋值,就可以实现这一点,例如这段代码中的WELCOME_STR。一种很常规的做法,是用全大写来表示常量,因此我们可以在类中使用 self.WELCOME_STR,或者在类外使用Entity.WELCOME_STR ,来表达这个字符串。

    而针对第二个问题,我们提出了类函数、成员函数和静态函数三个概念。它们其实很好理解,前两者产生的影响是动态的,能够访问或者修改对象的属性;而静态函数则与类没有什么关联,最明显的特征便是,静态函数的第一个参数没有任何特殊性。

    具体来看这几种函数。一般而言,静态函数可以用来做一些简单独立的任务,既方便测试,也能优化代码结构。静态函数还可以通过在函数前一行加上 @staticmethod 来表示,代码中也有相应的示例。这其实使用了装饰器的概念,我们会在后面的章节中详细讲解。

    类函数的第一个参数一般为 cls,表示必须传一个类进来类函数最常用的功能是实现不同的 init 构造函数,比如上文代码中,我们使用 create_empty_book 类函数,来创造新的书籍对象,其 context 一定为 'nothing'。这样的代码,就比你直接构造要清晰一些。类似的,类函数需要装饰器@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直接生成对象,调用 get_context_length() 函数,就会 raise error 中断程序的执行。这其实是一种很好的写法,叫做函数重写,可以使子类必须重新写一遍 get_context_length() 函数,来覆盖掉原有函数。

    最后需要注意到 print_title() 函数,这个函数定义在父类中,但是子类的对象可以毫无阻力地使用它来打印 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来表示。

    我们可以看到,代码中entity = Entity()直接报错,只有通过 Document 继承 Entity 才能正常使用。

    构造函数的执行顺序很好确定,即子类 -> 父类 -> 爷类 ->… 的链式关系。不过,多重继承的时候呢?下面这个叫做菱形继承,BC 继承了 A,然后 D 继承了 BC,创造一个D的对象。那么,构造函数调用顺序又是怎样的呢?

     --->B---
    A-      -->D
     --->C---
    

    解答

    class A():
        def __init__(self):
            print('enter A')
            print('leave A')
    
    class B(A):
        def __init__(self):
            print('enter B')
            super().__init__()
            print('leave B')
    
    class C(A):
        def __init__(self):
            print('enter C')
            super().__init__()
            print('leave C')
    
    class D(B, C):
        def __init__(self):
            print('enter D')
            super().__init__()
            print('leave D')
    
    D()
    
    enter D
    enter B
    enter C
    enter A
    leave A
    leave C
    leave B
    leave D
    

    相关文章

      网友评论

          本文标题:面向对象编程(1)

          本文链接:https://www.haomeiwen.com/subject/leglbctx.html