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

本系列主要学习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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,561评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,218评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,162评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,470评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,550评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,806评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,951评论 3 407
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,712评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,166评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,510评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,643评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,306评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,930评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,745评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,983评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,351评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,509评论 2 348

推荐阅读更多精彩内容