Kyle's Notebook

Python 类与对象总结

Word count: 6.5kReading time: 30 min
2019/08/22

Python 类与对象总结

变量的本质

在 Python 中变量的本质是一个指针(类比为可贴在任何物品上的 标签,Java 中则可以类比为盒子)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
a = 1           # 生成对象,把 a 指向该对象
a = "abc" # 把 a 指向别的对象

a = [1,2,3]
b = a
print (id(a), id(b))
print (a is b)
b.append(4) # 相当于在列表上贴上 a、b 两个标签,对 a/b 操作都是在同一个对象上完成
print (a)

# ========

a = [1,2,3,4] # not a is b 此时 a、b 可理解为贴在不同对象上的标签
b = [1,2,3,4] # a == b,表示 a、b 所指的对象中值是相等的

a = 1
b = 1 # a is b,a、b 都是从一个常量池获取,所以是同一个对象

class People:
pass

person = People()
if type(person) is People:
print("yes")

传参错误问题

向函数传入 list、dict,对象可能会被修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
def add(a, b):
a += b
return a

class Company:
def __init__(self, name, staffs=[]):
self.name = name
self.staffs = staffs

def add(self, staff_name):
self.staffs.append(staff_name)

def remove(self, staff_name):
self.staffs.remove(staff_name)

if __name__ == "__main__":
com1 = Company("com1", ["ywh1", "ywh2"])
com1.add("ywh3")
com1.remove("ywh1")
print(com1.staffs)

com2 = Company("com2")
com2.add("ywh")
print(com2.staffs)

print(Company.__init__.__defaults__)

com3 = Company("com3")
com3.add("ywh5")
print(com2.staffs)
print(com3.staffs)
print(com2.staffs is com3.staffs)

a = 1
b = 2
c = add(a, b)
print(a, b, c) # 1, 2, 3

a = [1, 2]
b = [3, 4]
c = add(a, b)
print(a, b, c) # [1, 2, 3, 4], [3, 4], [1, 2, 3, 4]

a = (1, 2)
b = (3, 4)
c = add(a, b)
print(a, b, c) # (1, 2) (3, 4) (1, 2, 3, 4)

对象和类型

Python 中的一切皆对象,包括 class、function 等,具体表现在:

  • 可赋值给变量

  • 可添加到集合

  • 可作为函数参数

  • 可作为函数返回值

其中对象的特征:身份(即对象在内存中的地址,id)、类型、值。

type、object、class

type:

  • 可以创建所有对象(类、函数、集合)的类,或返回对象的类型(type(Object) == type

  • type 也是对象(自身类的对象),且作为类时 type 继承于 object:type.__bases__ == (object, )

object:

  • object 是 type 的实例,type 是 object 的类:type(object) == type

  • object 是所有类的基类(xxx.__bases__),自定义类默认继承 object:object.__bases__ == ()

常见内置类型

  • None:全局只有一个

  • 数值:int、float、complex、bool

  • 迭代:for … in …

  • 序列:list、bytes、range、tuple、str、array

  • 映射:dict

  • 集合:set、fronzenset

  • 上下文:with

  • 其他:模块(import)、class 和 object、函数、方法、代码、object、type、elipsis、notimplemented

isinstance 与 type

使用 isinstance 与 type 都可以判断对象的类型,但更推荐使用 isinstance,会检查对象类的继承链。

1
2
3
4
5
6
7
8
9
10
11
12
13
class A:
pass

class B(A):
pass

b = B()

print(isinstance(b, B))
print(isinstance(b, A)) # b属于B类也属于A类

print(type(b) is B) # 判读的是id,要使用is
print(type(b) is A) # b不是A类,所以是false

鸭子类型(Duck Typing)

  • 变量可以指向任何类型的对象(不需要继承,也不必考虑多态);

  • 定义了相同方法(包括魔法方法)的类,可以归为同一种类型(比如 list、set 都是可迭代对象,都可以调用 extend),以相同的方式调用该方法、也可以 for 循环迭代;

  • 缺陷是作为动态语言无法在编译时发现错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Cat(object):
def say(self):
print("i am a cat")

class Dog(object):
def say(self):
print("i am a fish")

class Duck(object):
def say(self):
print("i am a duck")

animal_list = [Cat, Dog, Duck]
for animal in animal_list:
animal().say() # 都可以“叫”,都可以当它是动物来用,而不必区分是哪种动物

垃圾收集(Garbage Collection)

Python 的垃圾收集是基于引用计数标记-清除机制分代回收

1
2
3
4
5
a = object()
b = a
del a
print(b)
print(a)

其中魔法方法 __del__ 定义垃圾收集时执行的操作:

1
2
3
class A:
def __del__(self):
pass

引用计数

PyObject 存在于所有对象中,其中 ob_refcnt 即为引用计数。

  • 当一个对象有新的引用时,ob_refcnt 即会增加,而引用它的对象被删除,ob_refcnt 就会减少。

  • 当引用计数为 0 时,表示对象生命结束,内存会被回收。

这种做法简单、实时,但维护引用计数需要消耗资源,且可能存在循环引用的问题。

标记 - 清除

即先按需分配,在没有空闲内存时,从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后对内存空间清扫,把所有没标记的对象释放。

分代回收

将系统中的所有内存块根据其存活时间划分为不同的集合(即“代”),垃圾收集频率随着“代”的存活时间(使用经过垃圾回收次数度量)的增大而减小。Python 默认定义了三代对象集合,索引数越大,对象存活时间越长。

  • 假设当某些内存块M经过了 3 次垃圾收集的清洗之后还存活时,就将内存块 M 划到一个集合 A 中去,而新分配的内存都划分到集合 B 中去。

  • 当垃圾收集开始工作时,大多数情况都只对集合 B 进行垃圾回收,而对集合 A 进行垃圾回收要隔相当长一段时间后才进行,从而减少垃圾收集机制需要的内存,提高效率。

  • 在这个过程中,集合 B 中的某些内存块由于存活时间长而会被转移到集合 A 中;

  • 集合 A 中实际上也存在一些垃圾,这些垃圾会因为这种分代的机制而被延迟回收。

魔法方法(Magic Method)

  • 在 Python 中使类获得某种功能不必通过继承基类、实现方法,而是实现魔法方法(协议);

  • 魔法方法属于数据模型的一种特性,可以在任何对象中定义;

  • 魔法方法对 Python 原生的类型支持更好,应尽量使用原生;

  • 可迭代对象 __iter__ 会优先于 __getitem__ 且性能更好。

常用魔法方法及其实现特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Company(object):
def __init__(self, employee_list):
self.employee = employee_list

def __getitem__(self, item):
return self.employee[item]

def __len__(self):
return len(self.employee)

def __str__(self):
print("is Company")

def __repr__(self):
print("is Company(dev)")

obj = Company()

__getitem__:可遍历、切片

1
2
3
for i in obj:
pass
print(obj[:2])

__len__:取长度

1
len(obj)

__str__:输出类信息(__repr__于交互式环境自动调用)

1
print(obj)

另外更多魔法方法用法见:https://rszalski.github.io/magicmethods/

实现类的比较操作

和其他语言(Java、C++ 的运算符重载)类似,Python 也可以通过重写魔法方法使任意类型支持运算和比较操作,下面以比较不同图形的面积大小为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
from functools import total_ordering
from abc import abstractmethod

# 抽象基类
# 只需实现其中两种判断的魔法方法即可(自动推测出其他的比较方法)
@total_ordering
class Shape(object):

def __init__(self, radius):
self.radius = radius
self.pi = 3.14

# 定义抽象方法(抽象基类 + 抽象方法 -> 接口)
@abstractmethod
def area(self):
pass

# 小于 <
def __lt__(self, obj):
if not isinstance(obj, Shape):
raise TypeError("Obj is not Shape!")
return self.area() < obj.area()

# 等于 =
def __eq__(self, obj):
if not isinstance(obj, Shape):
raise TypeError("Obj is not Shape!")
return self.area() == obj.area()

# 长方形
class Rectangle(Shape):

def __init__(self, width, height):
self.width = width
self.height = height

def area(self):
return self.width * self.height

# 圆形
class Circle(Shape):

def __init__(self, radius):
self.radius = radius
self.pi = 3.14

def area(self):
return self.radius ** 2 * self.pi

rectangle = Rectangle(2, 2)
circle = Circle(2)
print(rectangle > circle, rectangle == circle, rectangle < circle) # (False, False, True)

加减乘除等运算也支持这样的操作,感兴趣的朋友可以试试。

实现上下文管理对象

在进行文件读写操作时常会用到 with 语句,作用是进入上下文作用域,作用域内可使用 with 语句返回的对象:

1
2
with open("/tmp/file.txt", "r+b") as f:
f.write("xxx")

退出作用域时不需要使用 f.close() 手动关闭文件对象,这在 open 类型已经内部定义好:当退出作用域时自动关闭文件对象。

实现 __enter____exit__ 方法后,可用 with 语句划分作用域,使任意类型都支持这种上下文管理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
try:
pass
except:
pass
else: # 没有抛出异常时执行
pass
finally: # 不管是否抛出异常都会执行,资源释放操作(关闭文件、关闭数据库连接等)
pass # 如果 finally 中包括 return 语句,则会以 finally 中的 return 返回(从上到下入栈、出栈执行)

# 上下文管理器协议
class Sample:

def __enter__(self):
print ("enter")
# 获取资源
return self

def __exit__(self, exc_type, exc_val, exc_tb):
# 释放资源
print ("exit")

def do_something(self):
print ("doing something")

with Sample() as sample:
sample.do_something()

又如下面建立 SSH 连接远程执行命令的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import paramiko     # paramiko 是基于 SSH2 协议的第三方模块,可建立 SSH 连接

class SSH(object):
def __init__(self, hostname, port, username, password):
self.hostname = hostname
self.port = port
self.username = username
self.password = password
self.connection = None

# 远程执行命令
def execute(self, cmd):
stdin, stdout, stderr = self.connection.exec_command(cmd)
print(stdout.read())

# 进入作用域时自动执行
def __enter__(self): # 返回的对象即为“with a as b”的 b
self.connection = paramiko.SSHClient() # 创建一个连接对象
self.connection.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.connection.connect( # 使用用户名-密码的方式登录,也可以使用密钥登录
hostname=self.hostname,
port=self.port,
username=self.username,
password=self.password
)
print("\nConnecting to {}:{}...\n".format(self.hostname, self.port))
return self

# 退出作用域时自动执行
def __exit__(self, exc_type, exc_val, exc_tb): # 三个参数分别用于处理异常、执行清理工作和执行回调函数
self.connection.close()
print("Bye.")

config = {
"hostname": "localhost", "port": 22, "username": "root", "password": "123456"
}

with SSH(**config) as ssh:
ssh.execute("ls /etc | head -5")

执行结果:

1
2
3
4
5
6
7
8
9
Connecting to 123.207.226.173:22...

DIR_COLORS
DIR_COLORS.256color
DIR_COLORS.lightbgcolor
GeoIP.conf
NetworkManager

Bye.

另外通过 contextlib 模块可实现上下文管理器的简化

1
2
3
4
5
6
7
8
9
10
import contextlib    # contextlib.contextmanager 装饰器的函数自带上下文管理器功能

@contextlib.contextmanager
def file_open(file_name):
print ("file open")
yield {}
print ("file end")

with file_open("bobby.txt") as f_opened:
print ("file processing")

继承机制(Inheritance)

抽象基类(Abstract Base Class)

  • 类似 Java 中的接口(定义子类共有的方法且必须被实现),不能实例化;

  • 当希望判定某个对象的类型,或强制某个子类必须实现某方法时,可以通过定义抽象基类让子类继承;

  • collections.abc 模块包含了内部定义的抽象基类,也可以直接导入 abc 自己实现;

  • 虽然提供了抽象基类功能,但还是应该尽可能重用鸭子类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from collections.abc import Sized    # Sized表示实现了__len__的类型

class Company(object):
def __init__(self, employee_list):
self.employee = employee_list

def __len__(self):
return len(self.employee)

com = Company(["b1", "b2"])

# 如果没有基类,就需要hasattr来判断对象是否存在某个方法
hasattr(com, "__len__")

# 继承抽象基类的类,只需要根据对象其基类即可判断是否存在某方法
isinstance(com, Sized) # Sized抽象基类肯定存在 __len__ 方法,所以可以放心调用 len

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import abc

class CacheBase(metaclass=abc.ABCMeta):

@abc.abstractmethod # 如果子类没有实现该方法,则会抛出异常
def get(self, key):
pass

@abc.abstractmethod
def set(self, key, value):
pass

class RedisCache(CacheBase):
pass

r = RedisCache()

super 函数

  • 子类继承自父类,有时不需要在子类中执行变量赋值:self.name = name,只需要执行父类初始化方法即可;

  • super 执行顺序:MRO 顺序(而不是调用父类构造方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from threading import Thread
class MyThread(Thread):
def __init__(self, name, user):
self.user = user
super().__init__(name=name)

class A:
def __init__(self):
print ("A")

class B(A):
def __init__(self):
print ("B")
super().__init__() # 获取父类,调用其初始化方法

class C(A):
def __init__(self):
print ("C")
super().__init__()

class D(B, C):
def __init__(self):
print ("D")
super(D, self).__init__()

if __name__ == "__main__":
print(D.__mro__)
d = D()

多继承设计

建议使用 Mixin 模式:可以更合理地组织代码、复用代码

  • Mixin 类表示一种功能,功能应尽量单一(表示这个父类作为功能添加到子类,而不起父类作用,类似 Java 的 Interface);

  • 不和基类关联,可以和任意基类组合,基类可以不和 Mixin 关联就能初始化成功;

  • Mixin 类不依赖于子类的实现;

  • 子类没有基础 Mixin 类也可以照常工作,只是缺少部分功能;

  • 在 Mixin 中不要使用 super 这种用法(不能按照 MRO 顺序)。

例如此处的 Airplane 继承了 Vehicle 和 PlaneMixin,但从含义上 PlaneMixin 表示混入,Airplane 实际上只是属于 Vehicle 的一种。

1
2
3
4
5
6
7
8
9
class Vehicle(object):
pass

class PlaneMixin(object):
def fly(self):
print 'I am flying'

class Airplane(Vehicle, PlaneMixin):
pass

扩展内置数据结构

通过继承和重写构造方法的方式可以对 Python 内置的数据结构进行功能扩展,如下面正整数元组的例子:

1
2
3
4
5
6
7
8
class IntTuple(tuple):
def __new__(cls, tup):
new_tup = (x for x in tup if isinstance(x, int) and x > 0)
# 把新的元组传入父类的构造方法并实例化
return super(IntTuple, cls).__new__(cls, new_tup)

tup = IntTuple([-1, 1, "asgag", "1", 99, -5.1])
print(tup)

执行结果:

1
(1, 99)

自省机制(Introspection)

自省是指对象创建后可以通过一定的机制查询和修改其内部结构(如运行时能够获得对象的类型、为对象添加方法)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from x.class_method import Date

class Person:
name = "user"

class Student(Person):
def __init__(self, scool_name):
self.scool_name = scool_name

if __name__ == "__main__":
user = Student("ywh")

print(user.__dict__) # 通过__dict__查询实例具有的属性(C实现,性能很高)
user.__dict__["school_addr"] = "北京市" # 动态修改对象
print(user.school_addr)
print(Person.__dict__)
print(user.name) # 自动向上查找
a = [1, 2]
print(dir(a))

通过名称调用方法

有时获取到方法的名称(例如客户端 get 或 post 传来的字符串数据),要根据这个名称在后端调用相应的方法,一般的做法是使用 if…else 判断。但当程序规模变大,使用 if…elif…else 的判断逻辑就会线性递增,既不方便也不美观。

使用 hasattrgetattr 的方法(也有人称之为“反射”)可以通过字符串在当前的类或模块中获取函数对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Circle(object):
def __init__(self, radius):
self.radius = radius
self.pi = 3.14

def get_value(self, key):
# getattr表示从self中取名为 "get_" + key 的方法,返回该方法的对象
if hasattr(self, "get_"+key):
return getattr(self, "get_" + key)()

def get_radius(self):
return self.radius

def get_perimeter(self):
return self.pi * self.radius * 2

def get_area(self):
return self.pi * self.radius ** 2

c1 = Circle(3)
print(c1.get_value("radius")) # 3
print(c1.get_value("area")) # 28.26
print(c1.get_value("perimeter")) # 18.84

其中 hasattr 是判断 self(即当前对象)中是否有名为 “get_” + key 的方法,getattr 则获取该方法的对象来执行。

当要从 当前模块中 获取函数对象,可以:

1
2
3
if hasattr(sys.modules[__name__], func_name):
func = getattr(sys.modules[__name__], func_name)
func()

类的属性与方法

类属性和实例属性

定义在类和实例中的变量与方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A:
aa = 1
def __init__(self, x, y):
self.x = x
self.y = y

a = A(2,3)

a.aa = 100 # 修改的是实例变量,不会影响类变量
print(a.x, a.y, a.aa)
print(A.aa)

b = A(3,5)
print(b.aa)

查找顺序:

  • 由下而上,先检查实例是否存在该属性,不存在再从类中查找;

  • 多继承时,采用 C3 算法在父类中查找。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class D:
pass

class E:
pass

class C(E):
pass

class B(D):
pass

class A(B, C):
pass

print(A.__mro__)

静态方法、类方法、对象方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class Date:
# 构造函数
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day

# 实例方法
def tomorrow(self):
self.day += 1

@staticmethod
def parse_from_string(date_str):
year, month, day = tuple(date_str.split("-"))
return Date(int(year), int(month), int(day))

# 静态方法(与普通函数没什么区别,只是封装在类中,与类交互时要指定类名)
@staticmethod
def valid_str(date_str):
year, month, day = tuple(date_str.split("-"))
return int(year)>0 and (int(month) >0 and int(month)<=12) and (int(day) >0 and int(day)<=31)

# 类方法(隐含参数为类本身,不依赖于类名)
@classmethod
def from_string(cls, date_str):
year, month, day = tuple(date_str.split("-"))
return cls(int(year), int(month), int(day))

# 实例方法
def __str__(self):
return "{year}/{month}/{day}".format(year=self.year, month=self.month, day=self.day)

if __name__ == "__main__":
new_day = Date(2018, 12, 31)
new_day.tomorrow()
print(new_day)

# 2018-12-31
date_str = "2018-12-31"
year, month, day = tuple(date_str.split("-"))
new_day = Date(int(year), int(month), int(day))
print (new_day)

# 用 staticmethod 完成初始化
new_day = Date.parse_from_string(date_str)
print(new_day)

# 用 classmethod 完成初始化
new_day = Date.from_string(date_str)
print(new_day)
print(Date.valid_str("2018-12-32"))

数据封装与私有属性

  • 以双下划线开头的私有属性不可以直接通过对象直接访问,而应该定义 get、set 方法进行有条件的读写;

  • 以单下划线开头的私有属性不能用 from module import * 导入,其他方面和公有属性访问方式一样;

  • 私有属性仍然可以通过 obj._classname__field 访问,所以不是绝对安全的;

  • 利用这个机制,可以解决类继承后私有变量重名的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
from x.class_method import Date
class User:
def __init__(self, birthday):
self.__birthday = birthday

def get_age(self):
return 2018 - self.__birthday.year # 返回年龄

if __name__ == "__main__":
user = User(Date(1990,2,1))
print(user._Student__birthday)
print(user.__birthday) # 报错
print(user.get_age())

__slots__ 属性

需要创建大量实例时,定义 __slots__ 可以限制类实例的属性(定义后不支持动态绑定属性)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Student1(object):
def __init__(self, sid, name, age):
self.sid = sid
self.name = name
self.age = age

class Student2(object):
__slots__ = ["sid", "name", "age"]

def __init__(self, sid, name, age):
self.sid = sid
self.name = name
self.age = age

s1 = Student1("1", "ywh", "22")
s2 = Student2("2", "hwy", "12")

s1.sex = "male"
print(s1.sex)
s2.sex = "female"
print(s2.sex)

执行结果:

1
2
3
4
5
male
Traceback (most recent call last):
File "E:/Projects/PythonLearning/Notes/data_model.py", line 37, in <module>
s2.sex = "female"
AttributeError: 'Student2' object has no attribute 'sex'

除此之外对比 dir(s1)dir(s2) 也可以发现可见 s2 少了 __dict__(实现动态绑定、解除属性)和 __weakref__(弱引用,引用时不增加引用计数),因此能节省大量内存。

对象属性管理与元类编程

Property

在类中实现内置的 property 方法可以动态地获取、设置、删除类/对象属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Circle(object):
def __init__(self, radius):
self.radius = radius
self.pi = 3.14

# 获取圆半径
def get_radius(self):
return self.radius

# 设置圆半径
def set_radius(self, value):
if not isinstance(value, (int, long, float)):
raise ValueError("Wrong type.")
self.radius = float(value)

# 获取圆面积
@ property
def area(self):
return self.radius ** 2 * self.pi

# 四个参数分别为get、set、del、doc,实现后可以获取、设置、删除属性
R = property(get_radius, set_radius) # 也可以作为装饰器使用

c = Circle(3.0)
print(c.R)
c.R = 4.0
print(c.R)

执行结果:

1
2
3.0
4.0

其中 property 方法也可以通过装饰器的方式实现,加入 property 装饰器的方法将转变为特性,可以使该方法支持以字段的方式访问),但默认只可读:

1
print c.area    # 50.24

要使属性可写,只需再实现 setter 装饰器:

1
2
3
4
5
class Circle(object):
...
@area.setter
def area(self, value):
pass

使用描述符做类型检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from datetime import date, datetime
class User:
def __init__(self, name, birthday):
self.name = name
self.birthday = birthday
self._age = 0

# def get_age(self):
# return datetime.now().year - self.birthday.year

@property
def age(self):
return datetime.now().year - self.birthday.year

@age.setter
def age(self, value):
self._age = value

if __name__ == "__main__":
user = User("ywh", date(year=1987, month=1, day=1))
user.age = 30
print (user._age)
print(user.age)

为了使代码更好维护,不建议直接访问成员变量,而使用 property 实现 get、set 方法,通过代码逻辑控制访问权限(使用描述符定义 getter、setter 方法,可以限制类属性可接收的类型,判断失败抛出异常):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Attr(object):
def __init__(self, val, attr):
self.val = val
self.attr = attr

# 获取属性
def __get__(self, instance, cls):
return instance.__dict__[self.val]

# 修改属性:使用描述符作类型检查
def __set__(self, instance, value):
if not isinstance(value, self.attr):
raise TypeError("Expected an {}".format(self.attr))
instance.__dict__[self.val] = value

# 析构:自动清除对象
def __delete__(self, instance):
del instance.__dict__[self.val]

class Student(object):
sid = Attr("sid", str)
name = Attr("name", str)
age = Attr("age", int)

s1 = Student()
s1.age = 5
print(s1.age)
s1.age = "6"
print(s1.age)

执行结果:

1
2
3
4
5
6
7
5
Traceback (most recent call last):
File "E:/Projects/PythonLearning/Notes/data_model.py", line 196, in <module>
s1.age = "6"
File "E:/Projects/PythonLearning/Notes/data_model.py", line 181, in __set__
raise TypeError("Expected an {}".format(self.attr))
TypeError: Expected an <type 'int'>

getattr 与 getattribute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# __getattr__, __getattribute__
from datetime import date
class User:
def __init__(self,info={}):
self.info = info

# 在__getattribute__抛出异常时调用
def __getattr__(self, item):
return self.info[item]

# 是 __getattr__ 更高级的封装,使用 obj.attr 但查找不到属性时调用,但尽量不要重写
def __getattribute__(self, item):
return "ywh"

if __name__ == "__main__":
user = User(
info={"company_name": "alibaba", "name": "ywh"}
)
print(user.test)

属性描述符

利用属性描述符做参数类型检查(类似 Django 的做法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
from datetime import date, datetime
import numbers

# 属性描述符(__get__、__set__、__delete__实现任意一或多个)
class IntField:
def __get__(self, instance, owner):
return self.value

def __set__(self, instance, value):
if not isinstance(value, numbers.Integral):
raise ValueError("int value need")
if value < 0:
raise ValueError("positive value need")
self.value = value

def __delete__(self, instance):
pass

class NonDataIntField:
# 非数据属性描述符
def __get__(self, instance, owner):
return self.value

class User:
age = IntField()
# age = NonDataIntField()

'''
如果user是某个类的实例,user.age(以及等价的 getattr(user,’age’))
首先调用 __getattribute__。如果类定义了 __getattr__ 方法,
那在 __getattribute__ 抛出 AttributeError 时就会调用到 __getattr__,
而对于描述符 (__get__) 的调用,则是发生在 __getattribute__ 内部的。
user = User(), user.age 顺序如下:
(1)如果“age”是出现在 User 或其基类的 __dict__ 中,且 age 是 data descriptor, 调用其 __get__ 方法, 否则
(2)如果“age”出现在 User 的 __dict__ 中, 直接返回 obj.__dict__[‘age’], 否则
(3)如果“age”出现在 User 或其基类的 __dict__ 中
(3.1)如果 age 是 non-data descriptor,那调用其 __get__ 方法, 否则
(3.2)返回 __dict__[‘age’]
(4)如果 User 有__getattr__ 方法,调用 __getattr__ 方法,否则
(5)抛出 AttributeError
'''

# class User:
#
# def __init__(self, name, email, birthday):
# self.name = name
# self.email = email
# self.birthday = birthday
# self._age = 0
#
# # def get_age(self):
# # return datetime.now().year - self.birthday.year
#
# @property
# def age(self):
# return datetime.now().year - self.birthday.year
#
# @age.setter
# def age(self, value):
# #检查是否是字符串类型
# self._age = value

if __name__ == "__main__":
user = User()
user.__dict__["age"] = "abc"
print (user.__dict__)
print (user.age)
# print (getattr(user, 'age'))
# user = User("ywh", date(year=1987, month=1, day=1))
# user.age = 30
# print (user._age)
# print(user.age)

__new____init__

  • __new__ 是用来控制对象的生成过程, 在对象生成之前;

  • __init__ 是用来完善对象的,如果new方法不返回对象, 则不会调用 init 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
class User:

def __new__(cls, *args, **kwargs):
print (" in new ")
return super().__new__(cls)

def __init__(self, name):
print (" in init")
pass

a = int()
if __name__ == "__main__":
user = User(name="ywh")

元类编程

  • 类的本质是对象,type 是创建类的类;

  • 元类是创建类的类:对象 <- class(对象) <- type;

  • 使用元类可以控制类的实例化过程(类定义中指定 metaclass 属性,通过 metaclass 去创建类);

  • 类的实例化过程:首先寻找 metaclass,去创建类对象、实例;

  • 一般不直接使用 type,而创建一个 MetaClass 继承 type,再在要实例化的类中指定 metaclass 属性为这个 MetaClass。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def create_class(name):
if name == "user":
class User:
def __str__(self):
return "user"
return User
elif name == "company":
class Company:
def __str__(self):
return "company"
return Company

def say(self):
return "i am user"
# return self.name


class BaseClass():
def answer(self):
return "i am baseclass"

# 使用元类控制类的实例化过程
from collections.abc import *

class MetaClass(type):
def __new__(cls, *args, **kwargs):
return super().__new__(cls, *args, **kwargs)

class User(metaclass=MetaClass):

def __init__(self, name):
self.name = name

def __str__(self):
return "user"

if __name__ == "__main__":
# MyClass = create_class("user")
# my_obj = MyClass()
# print(type(my_obj))

# type 动态创建类
User = type(
"User",
(BaseClass, ),
{"name":"user", "say":say}
)
my_obj = User(name="ywh")
print(my_obj)

实例:实现简易 ORM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import numbers

class Field:
pass

# 整型描述符
class IntField(Field):

def __init__(self, db_column, min_value=None, max_value=None):
self._value = None
self.min_value = min_value
self.max_value = max_value
self.db_column = db_column
if min_value :
if not isinstance(min_value, numbers.Integral):
raise ValueError("min_value must be int")
elif min_value < 0:
raise ValueError("min_value must be positive int")
if max_value :
if not isinstance(max_value, numbers.Integral):
raise ValueError("max_value must be int")
elif max_value < 0:
raise ValueError("max_value must be positive int")
if min_value and max_value :
if min_value > max_value:
raise ValueError("min_value must be smaller than max_value")

def __get__(self, instance, owner):
return self._value

def __set__(self, instance, value):
if not isinstance(value, numbers.Integral):
raise ValueError("int value need")
if value < self.min_value or value > self.max_value:
raise ValueError("value must between min_value and max_value")
self._value = value

# 字符串型描述符
class CharField(Field):
def __init__(self, db_column, max_length=None):
self._value = None
self.db_column = db_column
if not max_length:
raise ValueError("you must spcify max_lenth for charfiled")
self.max_length = max_length

def __get__(self, instance, owner):
return self._value

def __set__(self, instance, value):
if not isinstance(value, str):
raise ValueError("string value need")
if len(value) > self.max_length:
raise ValueError("value len excess len of max_length")
self._value = value


class ModelMetaClass(type):

def __new__(cls, name, bases, attrs, **kwargs):
if name == "BaseModel":
return super().__new__(cls, name, bases, attrs, **kwargs)
fields = {}
for key, value in attrs.items():
if isinstance(value, Field):
fields[key] = value
attrs_meta = attrs.get("Meta", None)
_meta = {}
db_table = name.lower()
if attrs_meta :
table = getattr(attrs_meta, "db_table", None)
if table :
db_table = table
_meta["db_table"] = db_table
attrs["_meta"] = _meta
attrs["fields"] = fields
del attrs["Meta"]
return super().__new__(cls, name, bases, attrs, **kwargs)


class BaseModel(metaclass=ModelMetaClass):
def __init__(self, *args, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
return super().__init__()

def save(self):
fields = []
values = []
for key, value in self.fields.items():
db_column = value.db_column
if not db_column:
db_column = key.lower()
fields.append(db_column)
value = getattr(self, key)
values.append(str(value))

sql = "insert {db_table}({fields}) values({values})".format(
db_table=self._meta["db_table"],
fields=",".join(fields),
values=",".join(values)
)

class User(BaseModel):

# 数据库表中的字段
name = CharField(db_column="name", max_length=10)
age = IntField(db_column="age", min_value=1, max_value=100)

# 表名称
class Meta:
db_table = "user"

if __name__ == "__main__":
user = User(name="ywh", age=28)
user.save()
CATALOG
  1. 1. Python 类与对象总结
    1. 1.1. 变量的本质
      1. 1.1.1. 传参错误问题
    2. 1.2. 对象和类型
      1. 1.2.1. type、object、class
      2. 1.2.2. 常见内置类型
      3. 1.2.3. isinstance 与 type
      4. 1.2.4. 鸭子类型(Duck Typing)
    3. 1.3. 垃圾收集(Garbage Collection)
      1. 1.3.1. 引用计数
      2. 1.3.2. 标记 - 清除
      3. 1.3.3. 分代回收
    4. 1.4. 魔法方法(Magic Method)
      1. 1.4.1. 实现类的比较操作
      2. 1.4.2. 实现上下文管理对象
    5. 1.5. 继承机制(Inheritance)
      1. 1.5.1. 抽象基类(Abstract Base Class)
      2. 1.5.2. super 函数
      3. 1.5.3. 多继承设计
      4. 1.5.4. 扩展内置数据结构
    6. 1.6. 自省机制(Introspection)
      1. 1.6.1. 通过名称调用方法
    7. 1.7. 类的属性与方法
      1. 1.7.1. 类属性和实例属性
      2. 1.7.2. 静态方法、类方法、对象方法
      3. 1.7.3. 数据封装与私有属性
      4. 1.7.4. __slots__ 属性
    8. 1.8. 对象属性管理与元类编程
      1. 1.8.1. Property
      2. 1.8.2. 使用描述符做类型检查
      3. 1.8.3. getattr 与 getattribute
      4. 1.8.4. 属性描述符
      5. 1.8.5. __new__ 与 __init__
      6. 1.8.6. 元类编程