本系列主要学习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__
赋值一个tuple
,tuple
中的属性为可以添加的属性。
#!/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中内置的list
、tuple
相似但是还有不同的,比如通过索引查找元素,切片技术都是不行的,下面我们下一个标识就是改进这个的。
__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__
参数的意义:
- 当前准备创建的类的对象;
- 类的名字;
- 类继承的父类集合;
- 类的方法集合。
动态的修改要创建的类,在特定的情况下会使用到,例如:ORM
即对象-关系映射 ,也就是metaclass
可以设计ORM
框架,下一讲我们学习一下编写ORM
的框架的原理。