1. __solts__
正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性
。
from types import MethodType
class Student:
def __init__(self):
pass
def set_num(self, newnum):
self.num = newnum
def set_score(self, score):
self.score = score
# 为当前实例绑定属性num、实例方法set_num
# 注意, 仅仅是为当前实例
s1 = Student()
s1.num = 101
print(s1.num)
s1.set_num = MethodType(set_num, s1)
s1.set_num(102)
print(s1.num)
# 为了给所有实例都绑定属性和方法,可以直接在类上绑定
Student.name = "Zhang" # 在类上绑定属性name
s2 = Student()
s2.name = "Wang"
print(s2.name)
# 在类上绑定方法set_score
Student.set_score = set_score
s3 = Student()
s4 = Student()
s3.set_score(99)
print(s3.score)
s4.set_score(98)
print(s4.score)
通常情况下,上面的set_score方法可以直接定义在class中,但动态绑定允许我们在程序运行的过程中动态给class加上功能,这在静态语言中很难实现。
__slots__的使用
但是,如果我们想要限制实例的属性怎么办?
比如,只允许对Student实例添加name和age属性。
为了达到限制的目的,python允许在定义class的时候,定义一个特殊的__slots__变量
,来限制该class实例能添加的属性
。接下来看下面的两个示例:demo1和demo2
。
# demo1, Student类实例只能动态绑定num和name属性, 否则报错
class Student:
__slots__ = ('num', 'name')
def __init__(self):
pass
# 此时, 为实例s绑定属性num和name, 这是没有任何问题的;
# 但是为s绑定属性age, 此时就报错了, 因为age没有被放到__slots__里面去;
s = Student()
s.num = 101
s.name = 'Zhang'
# s.age = 100 # error
# demo2, 测试__slots__的继承性
class Student:
__slots__ = ('num', 'name')
def __init__(self):
pass
class SonStudent(Student):
# __slots__ = ()
pass
# 但是注意, __slots__定义的属性仅仅对当前类实例起作用, 对继承的子类是不起作用的;
# 除非在子类中也定义__slots__, 这样, 子类实例允许定义的属性就是自身的__slots__加上父类的__slots__;
# 如果开启SonStudent中的__slots__, 那么运行将报错, 因为子类继承了父类的__slots__, 是不允许动态添加age属性的;
# 如果注释掉SonStudent中的__slots__, 那么运行将是OK的;
SonStudent().age = 100
2. @property
在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是没办法对属性值进行验证,导致可以把成绩胡乱的写。
class Student:
def __init__(self):
pass
def get_score(self):
return self.score
def set_score(self, value):
if not isinstance(value, int):
raise ValueError("the score must be an integer!")
if value < 0 or value > 100:
raise ValueError("the score must between 0 ! 100!")
self.score = value
Student().set_score(60) # ok
Student().set_score(1000) # error
但是,上面的调用方法又略显复杂,没有直接用属性这么直接简单。
有没有既能检查参数,又可以用类似属性这样简单的方式来访问类的变量呢?对于追求完美的python程序员来说,这是必须要做到的!
我们知道,装饰器(decorator)可以给函数动态添加功能吗。对于类的方法,装饰器一样起作用。python内置的@property装饰器
就是负责把一个方法变成属性
调用的。
class Student:
# 要特别注意: 属性的方法名不要和实例变量重名.如果将下面的_score换成score, 那么将报错.
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError("the score must be an integer!")
if value < 0 or value > 100:
raise ValueError("the score must between 0 ! 100!")
self._score = value
# 注意: 我们debug跟踪, 发现执行s.score=89时将会跳到@score.setter所修饰的函数里去;
# 执行print(s.score)时将会跳到@property所修饰的函数里去;
# 同时通过debug可知, 在实例化Student后, 实例化的对象已经有了一个score属性了;
s = Student()
s.score = 89 # 执行@score.setter修饰的方法
# print(s.score) # 执行@score修饰的方法
s.score = 9999 # error
3. 定制类
看到类似__slots__这种形如__xxx__的变量或者函数名就要注意,这些在python中是有特殊用途的。
__slots__我们已经知道怎么用了,len()方法我们也知道是为了能让class作用于len()函数。
除此之外,python的class中还有许多这样有特殊用途的函数,可以帮助我们定制类。
我们只需要在我们的类当中去重写它即可(重新实现它)
。
(1)
如果一个类想被用于for … in循环,类似list或tuple那样,就必须实现一个__iter__()
方法,该方法返回一个迭代对象
;然后,python的for循环就会不断调用该迭代对象的__next__()
方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
(2)
要表现得像list那样按照下标取出元素(如list[0]
),需要实现__getitem__
()方法。
(3)
一个对象实例可以有自己的属性和方法,当我们调用实例方法时,我们用instance.method()来调用。能不能直接在实例本身上调用呢?
在python中,答案是肯定的。任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用
。
(4)
正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。要避免这个错误,除了可以加上该属性外;python还有另一个机制,那就是写一个__getattr__()方法,动态返回一个属性
。
# demo
# 此demo中我们可以认识__len__,__str__,__repr__,__iter,__next__,__getitem__等特殊函数.
# 定义一个Student类, 而后定义一个Vector类, Vector类中定义一个list, 而list中就用Student来填充,
#
class Student:
def __init__(self, num, name):
self.num = num
self.name = name
def __len__(self):
return 2
def __str__(self):
return 'Student(%d,%s)' % (self.num, self.name)
__repr__ = __str__
def __call__(self, *args, **kwargs):
print('>>>>>>>>call myself.')
def __getattr__(self, item):
if item == 'score':
return 100
class Vector:
def __init__(self, list, size):
self.list = list
self.size = size
self.index = 0
def __iter__(self):
return self
def __next__(self):
seq = self.index
if self.index >= self.size:
raise StopIteration()
self.index += 1
return self.list[seq]
def __getitem__(self, item):
if item < len(self.list):
return self.list[item]
s = Student(101, 'Zhang')
print(len(s)) # __len__
print(s) # __str__
Student(101, 'Wang')() # __call__
print(s.score) # __getattr__
print('----------------------------')
ls = [
Student(101, 'Zhang'),
Student(102, 'Wang'),
Student(103, 'Li'),
Student(104, 'Zhao')
]
v = Vector(ls, len(ls))
for stu in v:
print(stu)
print('----------------------------')
print(v[2])
print(v[3])
更多请参考官网
,此处抛砖引玉:
https://docs.python.org/3/reference/datamodel.html#special-method-names
4. 枚举类
当我们需要定义常量时,一个办法是用大写变量通过整数来定义,例如:
BIG = 100
MIDDLE = 60
SMALL = 10
好处是简单,缺点是类型是int,并且仍然是变量。
更好的方法是为这样的枚举类型定义一个class类型,然后每个常量都是class的一个唯一实例。python提供了Enum类来实现这个功能
,如下所示:
from enum import Enum
Size = Enum('Size', ('BIG', 'SMALL', 'MIDDLE'))
# value属性则是自动赋给成员的int常量,默认从1开始计数.
for name, member in Size.__members__.items():
print(name, '=>', member, ',', member.value)
如果需要更精确地控制枚举类型,可以从Enum派生出自定义类
,如下所示:
from enum import Enum, unique
# @unique装饰器可以帮助我们检查保证没有重复值
@unique
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
# 可用以下方法进行访问
print(Weekday.Mon)
print(Weekday['Tue'])
print(Weekday.Tue.value)
print(Weekday(1))