美文网首页python3学习
Python3(8) Python 面向对象高级编程

Python3(8) Python 面向对象高级编程

作者: 猿来如痴 | 来源:发表于2018-01-26 19:53 被阅读248次

    本系列主要学习Python的基本使用和语法知识,后续可能会围绕着AI学习展开。
    Python3 (1) Python语言的简介
    Python3 (2) Python语法基础
    Python3 (3) Python函数
    Python3 (4) Python高级特性
    Python3(5) Python 函数式编程
    Python3(6) Python 模块
    Python3(7) Python 面向对象编程
    Python3(8) Python 面向对象高级编程
    Python面向对象编程有他自己强大的一面,如“鸭子类型”,这是动态语言独有的特性,上一篇的面向对象基础讲了python与其它语言面向对象的共同之处,这一篇主要讲Python中特有的面向对象特性。重继承、定制类、元类等,这一篇应该算是Python学习当中的中级核心篇。

    动态的添加类的属性方法

    面向对象中,对象是类的实例,集类的属性和方法于一体,由于python是动态语言,在运行时才创建类,所以python 有动态添加类属性与对象属性的能力。这一下就将静态语言比下去了,如下:

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    from types import MethodType
    
    class Person(object):
        def __init__(self,name):
            self._name = name
            
        @property
        def name(self):
            return self._name
    #动态添加类的属性
    Person.age = 16
    
    p = Person("张三")
    #动态添加对象的属性
    p.sex = '男'
    print(p.name,p.sex,p.age)
    
    p1 = Person('李四')
    print(p1.name,p.age)
    #对象的属性只适用于当前对象
    # print(p1.name,p1.sex,p.age)
    
    def set_name(self,value):
        self._name = value
    #动态添加对象的方法
    p.set_name = MethodType(set_name,p)
    p.set_name('王五')
    print(p.name)
    
    def set_age(self,value):
        self.age = value
    #动态的添加类的方法
    Person.set_age = set_age
    p.set_age(12)
    p1.set_age(13)
    print(p.name,p.age)
    print(p1.name,p1.age)
    

    输出结果:

    张三 男 16
    李四 16
    王五
    王五 12
    李四 13
    
    • 动态添加类的属性、方法,每个对象都可以用
    • 动态添加对象的属性、方法,只有当前对象可以用
    • 暴露出动态语言一个缺点,可以肆意的添加方法,属性,对于严格安全性要求严格的业务明显不行,所以有一个特殊的标识出现了__slots__来限定添加的属性。

    __slots__ 的用法

    在类中,用__slots__赋值一个tupletuple中的属性为可以添加的属性。

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    
    class Person(object):
        __slots__ = ('name', 'age')  # 用tuple定义允许绑定的属性名称
    
    
    p = Person()
    p.name = "张三"
    p.age = 16
    p.sex = '男'
    
    print(p.name,p.age,p.sex)
    

    输出结果:

    Traceback (most recent call last):
      File "F:/python/HelloWord/def_func.py", line 12, in <module>
        p.sex = '男'
    AttributeError: 'Person' object attribute 'sex' is read-only
    

    上面的错误信息就是不允许添加sex 属性。

    • __solts__ 在子类中,如果使用了就是子类+父类的限制,如果没有使用父类的限制对子类没用。
    class Person(object):
        __slots__ = ('name', 'age')  # 用tuple定义允许绑定的属性名称
    
    class Child(Person):
        # __slots__ = ('weight')
        pass
    
    c = Child()
    c.name = "李四"
    c.sex = "男"
    print(c.name,c.sex)
    

    输出结果:

    李四 男
    

    如果__slots__放开就会报错,只支持name、age、weight操作。

    @property 的用法

    @property的作用就是为变量生成一个装饰器,用于把一个方法变成属性调用的。为了限制属性的赋值,解决属性赋值的随意性。

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    class Person(object):
        @property
        def name(self):
            return self._name
    
        @name.setter
        def name(self,value):
            if not isinstance(value,str):
                raise ValueError('name must be an str')
            self._name= value
    
    p = Person()
    # p.name = '123'
    # p.name = 123
    print(p.name)
    

    输出结果:

    Traceback (most recent call last):
      File "F:/python/HelloWord/def_func.py", line 16, in <module>
        p.name = 123
      File "F:/python/HelloWord/def_func.py", line 11, in name
        raise ValueError('name must be an str')
    ValueError: name must be an str
    

    如果将整数赋值给name 会抛出我们定义的异常,这样就达到限制name必须str类型的作用。使我们的属性可控起来。@property用起来立马感觉代码优美多了,减低了出错的可能。

    多重继承

    python与C++一样,支持多重继承,其实与java 中接口与继承一起用的效果差不多。Python 中称接口的设计为 MixIn

    MixIn

    MixIn 的设计与接口一样,通过组合搭配来构成一个新的完整的功能,而不是层层继承来实现。我们来分析一下Python 自带的TCPServer和UDPServer这两类网络服务。

    #一个多进程模式的TCP服务
    class MyTCPServer(TCPServer, ForkingMixIn):
        pass
    #一个多线程模式的UDP服务
    class MyUDPServer(UDPServer, ThreadingMixIn):
        pass
    #一个协程模型
    class MyTCPServer(TCPServer, CoroutineMixIn):
        pass
    

    Python中一些功能性的类命名xxxMixIn,所以我们在书写的时候也采用这种方式,主类采用当继承的方式,一些功能性用多继承,功能性的类命名采用xxxMixIn的形式。

    定制类

    我们通过Python中内置的一些特殊标识去定制一些类

    __str__ 和__repr__

    __str__用于定义打印的类本身的内容,用户反馈,__repr__也是打印类本身的内容,开发者反馈。也就是通过命令运行时的显示。

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    class Person(object):
        def __init__(self,name):
            self.name = name
        def __str__(self):
            return 'Person object (name: %s)' % self.name
        __repr__ = __str__
    
    p = Person("张三")
    print(p)
    

    输出结果:

    Person object (name: 张三)
    

    我们演示了__str__的使用,但是__repr__需要命令执行,所以可以自己试试。

    __iter__

    __iter__ 用于将类变成一个迭代对象。一般要与__next__一起使用,接下来我们定义一个可迭代的类

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    class Fib(object):
        def __init__(self):
            self.a, self.b = 0, 1 # 初始化两个计数器a,b
    
        def __iter__(self):
            return self # 实例本身就是迭代对象,故返回自己
    
        def __next__(self):
            self.a, self.b = self.b, self.a + self.b # 计算下一个值
            if self.a > 50: # 退出循环的条件
                raise StopIteration()
            return self.a # 返回下一个值
    
    f = Fib()
    for n in f:
        print(n)
    

    输出结果:

    1
    1
    2
    3
    5
    8
    13
    21
    34
    

    输出的是1~50之间的斐波那契数列,这样我们定制了一个可以循环的类,与Python中内置的listtuple相似但是还有不同的,比如通过索引查找元素,切片技术都是不行的,下面我们下一个标识就是改进这个的。

    __getitem__

    __getitem__ 的作用就是设置下标对应的元素的,也可以实现切片 要进行判断,我们一步到位:

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    class Fib(object):
        def __init__(self):
            self.a, self.b = 0, 1 # 初始化两个计数器a,b
    
        def __iter__(self):
            return self # 实例本身就是迭代对象,故返回自己
    
        def __next__(self):
            self.a, self.b = self.b, self.a + self.b # 计算下一个值
            if self.a > 50: # 退出循环的条件
                raise StopIteration()
            return self.a # 返回下一个值
    
        def __getitem__(self, n):
            if isinstance(n, int):  # n是索引
                a, b = 1, 1
                for x in range(n):
                    a, b = b, a + b
                return a
            if isinstance(n, slice):  # n是切片
                start = n.start
                stop = n.stop
                if start is None:
                    start = 0
                a, b = 1, 1
                L = []
                for x in range(stop):
                    if x >= start:
                        L.append(a)
                    a, b = b, a + b
                return L
    f = Fib()
    print(f[4])
    print(f[0:5])
    

    输出结果:

    5
    [1, 1, 2, 3, 5]
    

    这个就使我们定义的Fib有了list的部分功能。这就是鸭子类型的应用,我们可以定义我们需要的任何类,包括实现系统内置的一些类,简单一句就是可以 “以假乱真”。

    __getattr__

    __getattr__的作用是调用类的方法或属性时,如果没有的话会走__getattr__的方法。如果没有__getattr__方法就会报错。所以__getattr__非常好用,我们可以根据这个特性设计好多灵活的东西,比如灵活的拼接调用链接:

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    
    class Chain(object):
        def __init__(self, path = ''):
            self._path = path
    
        def __getattr__(self, path):
            return Chain('%s/%s' % (self._path, path))
    
        def __str__(self):
            return self._path
    
        def users(self, username):
            return Chain(self._path + '/' + username)
    
    print(Chain('post:/').aaa.bbb.user.xxx)
    
    print(Chain('post:/').aaa.bbb.users('zhangsan').xxx)
    

    输出结果:

    post://aaa/bbb/user/xxx
    post://aaa/bbb/zhangsan/xxx
    

    上面实现了一个动态的生成一个连接,我们采用的链试调用,第一次我们创建的Chain()的实例,_self的值被赋值为post:/,第二次调用Chain().aaa,aaa不存在,所以进了__getattr__方法,通过拼接_self的值变成post://aaa,......调用Chain().users()的时候,users()方法被调用,所以把对应的name拼接上,最后一个动态的链接就生成了。
    刚开始我也有点闷逼,主要是没有理解__getattr__的作用,还有就是链试调用也没看明白导致的。最后通过断点和打印每一步的数据看懂了。终于从牛角尖里出来了。

    __call__

    __call__的作用是把一个对象变成一个可调用的函数,其实函数和对象没有什么界限,因为在python中一切都是对象。换句话说如果我们类中定义了__call__函数,他的实例就是可以被直接调用的。

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    
    class Person(object):
        def __init__(self,name):
            self._name = name
    
        def __call__(self, *args, **kwargs):
            return 'My name is %s.' % self._name
    
    p = Person("yalarc")
    print(p())
    

    输出结果:

    My name is yalarc.
    

    我们直接将对象p当做函数来使用。其实还有一个函数来判断一个对象是否可以被直接调用callable()
    ···
    print(callable(p))
    ···
    输出结果:

    True
    

    枚举类

    枚举跟java一样,也是规范代码的写法。保证代码更加清晰,规范

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    from enum import Enum, unique
    
    # @unique装饰器可以帮助我们检查保证没有重复值
    # 枚举的定义
    @unique
    class Gender(Enum):
        Male = 0
        Female = 1
    
    class Student(object):
        def __init__(self, name, gender):
            self.name = name
            self.gender = gender
    
    # 测试:
    bart = Student('Bart', Gender.Male)
    if bart.gender == Gender.Male:
        print('测试通过!')
    else:
        print('测试失败!')
    

    输出结果:

    测试通过!
    

    以上就是枚举的定义和使用,规范了int值的具体含义,不会出现歧义。

    type() 的高级用法

    首先我们再次强调Python是动态语言,强大之处不是编译时生成类,而是在运行时。然后再来看type()之前用他来判断类的类型,现在我们学习他的高级用法,既然他放回的是类型,所以我们可以用它动态的创建类。这个就非常牛逼了,没有在java等静态语言中听说过的用法。

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    
    
    class Hello(object):
        def hello(self, name='world'):
            print('Hello, %s.' % name)
    
    
    h = Hello()
    h.hello("you")
    print(type(Hello))
    print(type(h))
    print('--------------------------')
    
    def fn(self, name='yalarc'):  # 先定义函数
        print('My name is, %s.' % name)
    
    Person = type('person', (object,), dict(name=fn))  # 创建Hello class
    
    p = Person()
    p.name()
    print(type(Person))
    print(type(p))
    

    输出结果:

    Hello, you.
    <class 'type'>
    <class '__main__.Hello'>
    --------------------------
    My name is, yalarc.
    <class 'type'>
    <class '__main__.person'>
    

    看到没Person类就是动态的通过type()创建的。type()的三个参数:

    • class的名称;
    • 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
    • class的方法名称与函数绑定,这里我们把函数fn绑定到方法名name上。

    元类(metaclass)

    metaclass的作用是控制类的创建行为。因为既然能创建类,我们不能让它没有规矩的创建,我们应该通过metaclass来做一些修改。我们要给类定规则,所以要先定义 metaclass,然后创建类。

    • 先定义metaclass,就可以创建类,最后创建实例
    • metaclass允许你创建类或者修改类,相当于类是 metaclass创建出来的实例
    • xxxMetaclass我们一般通过 xxx+Metaclass来命名 metaclass

    接下来我们给list添加一个 add方法:

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    
    # metaclass是类的模板,所以必须从`type`类型派生:
    class ListMetaclass(type):
        def __new__(cls, name, bases, attrs):
            attrs['add'] = lambda self, value: self.append(value)
            return type.__new__(cls, name, bases, attrs)
    # 类
    class MyList(list, metaclass=ListMetaclass):
        pass
    
    L = MyList([1,2,3])
    print(L)
    L.add(0)
    print(L)
    

    输出结果:

    [1, 2, 3]
    [1, 2, 3, 0]
    

    MyList就是我们定义的具有add方法的类,我们来看看metaclass的定义,是不是metaclass主要看有没有__new__标识的函数。__new__参数的意义:

      1. 当前准备创建的类的对象;
      1. 类的名字;
      1. 类继承的父类集合;
      1. 类的方法集合。

    动态的修改要创建的类,在特定的情况下会使用到,例如:ORM 即对象-关系映射 ,也就是metaclass可以设计ORM框架,下一讲我们学习一下编写ORM的框架的原理。

    参考

    https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143186738532805c392f2cc09446caf3236c34e3f980f000

    相关文章

      网友评论

        本文标题:Python3(8) Python 面向对象高级编程

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