一、给class绑定方法:
为了给所有实例都绑定方法,可以给class绑定方法:
class Student:
def set_score(self,score):
self.score = score
#Student.set_score = set_score
s = Student()
s.set_score(25)
print(s.score)
相当于score这个属性是通过调用def set_core创造并赋值的
二、限制实例的属性 __slots__
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
然后,我们试试:
>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'
由于'score'
没有被放到__slots__
中,所以不能绑定score
属性,试图绑定score
将得到AttributeError
的错误。
使用__slots__
要注意,__slots__
定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:
>>> class GraduateStudent(Student):
... pass
...
>>> g = GraduateStudent()
>>> g.score = 9999
除非在子类中也定义__slots__
,这样,子类实例允许定义的属性就是自身的__slots__
加上父类的__slots__
。
PS:
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
class Student(object):
__slots__ = ('name', 'age')
def get_score(self,score):
self.get_score = score
s = Student()
s.get_score(20)#会报错。所以说在slots下面就算定义一个功能来get另外的属性也不可以
三、检查并限制参数的范围
通过一个set_score()
方法来设置成绩,再通过一个get_score()
来获取成绩,这样,在set_score()
方法里,就可以检查参数:
class Student(object):
def get_score(self):
return self._score
def set_score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
s = Student()
>>> s.set_score(60) # ok!
>>> s.get_score()
60
>>> s.set_score(9999)
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!
用raise报错比用return更好
四、给方法加装饰器(把方法变成属性)
Python内置的@property
装饰器就是负责把一个方法变成属性调用的:
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!
getter方法(只读):加@property
setter方法(修改):加@score.setter
只定义getter方法,不定义setter方法就是一个只读属性:
class Student(object):
@property
def birth(self):
return self._birth
@birth.setter
def birth(self, value):
self._birth = value
@property
def age(self):
return 2015 - self._birth
上面的birth
是可读写属性,而age
就是一个只读属性
@property把birth_set变成了birth,把方法变成了属性(不能随便更改的属性)
原来:
s.birth_set(10)
s.birth_get
现在:
s.birth=10
a.birth
@property和xx.setter必须连着写:
class Screen:
@property
def width(self):
return self._width
def height(self):
return self._height
def resolution(self):
return self.resolution
@width.setter
def width(self,value):
self.width = value
@height.setter
def height(self,value):
self.width = value
#这样就是错的,应该改成:
class Screen:
@property
def width(self):
return self._width
@width.setter
def width(self,value):
self.width = value
@property
def height(self):
return self._height
@height.setter
def height(self,value):
self.width = value
@property
def resolution(self):
return self._resolution
#定义了三个属性,其中resolution是只读的
为什么要用函数赋予给实例属性而不直接 实例名.属性 = 巴拉巴拉?
因为可以在函数里面加上if等限制,对参数进行限制,而不是随便什么参数都可以输进去
五、多重继承 MixIn
MixIn的目的就是给一个类增加多个功能
class Animal(object):
pass
# 大类:
class Mammal(Animal):
pass
class Bird(Animal):
pass
class RunnableMixIn(object):
def run(self):
print('Running...')
class FlyableMixIn(object):
def fly(self):
print('Flying...')
class Dog(Mammal, RunnableMixIn):
pass
class Bat(Mammal, FlyableMixIn):
pass
六、定制类
6.1__str__
class Student(object):
... def __init__(self, name):
... self.name = name
... def __str__(self):
... return 'Student object (name: %s)' % self.name
...
>>> print(Student('Michael'))
Student object (name: Michael)
如果不加__str__打印出来的就是<__main__.巴拉巴拉>
直接敲变量不用print
,打印出来的实例还是不好看:
>>> s = Student('Michael')
>>> s
<__main__.Student object at 0x109afb310>
这是因为直接显示变量调用的不是__str__()
,而是__repr__()
,两者的区别是__str__()
返回用户看到的字符串,而__repr__()
返回程序开发者看到的字符串,也就是说,__repr__()
是为调试服务的。
怎么办?
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name=%s)' % self.name
__repr__ = __str__
6.2 __iter__
如果一个类想被用于for ... in
循环,类似list或tuple那样,就必须实现一个__iter__()
方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()
方法拿到循环的下一个值,直到遇到StopIteration
错误时退出循环。
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 > 100000: # 退出循环的条件
raise StopIteration()
return self.a # 返回下一个值
for n in Fib():
... print(n)
...
1
1
2
3
5
...
46368
75025
6.3 __getitem__
Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,比如,取第5个元素:
>>> Fib()[5]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'Fib' object does not support indexing
要表现得像list那样按照下标取出元素,需要实现__getitem__()
方法:
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
像list那样拥有切片功能:
class Fib(object):
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
现在试试Fib的切片:
>>> f = Fib()
>>> f[0:5]
[1, 1, 2, 3, 5]
>>> f[:10]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
6.4 __getattr__
正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。
要避免这个错误,除了可以加上一个score
属性外,Python还有另一个机制,那就是写一个__getattr__()
方法,动态返回一个属性。修改如下:
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr=='score':
return 99
当调用不存在的属性时,比如score
,Python解释器会试图调用__getattr__(self, 'score')
来尝试获得属性,这样,我们就有机会返回score
的值:
>>> s = Student()
>>> s.name
'Michael'
>>> s.score
99
返回函数也是完全可以的:
class Student(object):
def __getattr__(self, attr):
if attr=='age':
return lambda: 25
只是调用方式要变为:
>>> s.age()
25
6.5 __call__
任何类,只需要定义一个__call__()
方法,就可以直接对实例进行调用。请看示例:
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s.' % self.name)
调用方式如下:
>>> s = Student('Michael')
>>> s() # self参数不要传入
My name is Michael.
( 不然按照正常来说应该用s.__call__() )
怎么判断一个变量是对象还是函数?其实,更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable
对象,比如函数和我们上面定义的带有__call__()
的类实例:
>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('str')
False
通过callable()
函数,我们就可以判断一个对象是否是“可调用”对象。
七 枚举:没看懂
八 动态创建类:type()
可以通过type()
函数创建出Hello
类,而无需通过class Hello(object)...
的定义:
>>> def fn(self, name='world'): # 先定义函数
... print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class '__main__.Hello'>
要创建一个class对象,type()
函数依次传入3个参数:
- class的名称;
- 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
- class的方法名称与函数绑定,这里我们把函数
fn
绑定到方法名hello
上。
正常情况下,我们都用class Xxx...
来定义类,但是,type()
函数也允许我们动态创建出类来,也就是说,动态语言本身支持运行期动态创建类,这和静态语言有非常大的不同,要在静态语言运行期创建类,必须构造源代码字符串再调用编译器,或者借助一些工具生成字节码实现,本质上都是动态编译,会非常复杂。