赞
踩
Python允许在定义class的时候,定义一个特殊的__slots__
变量,来限制该class实例能添加的属性:
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称,只允许对Student实例添加name和age属性。
就相当于java中的类中预定义属性。这样添加别的属性就会报错。
>>> 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' # 不允许添加新的属性
当然,这种限制对继承的子类不起作用。很好理解,本身子类就是拓宽使用方向的,因此限制的操作肯定不会起效。
为了对防止属性被任意修改,可以通过在方法里加入限制的方式进行修改。
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
该调用方法略显复杂,没有直接用属性这么直接简单。
Python内置的@property
装饰器就是负责把一个方法变成属性调用。
用法:
class Student(object):
@property
def score(self): # 对想要修饰的属性加一个@property装饰器,此处相当于把执行score=property(score)
return self._score
@score.setter # 此时,@property本身又创建了另一个装饰器@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
加完@property装饰器,则默认该属性为只读属性,只有在设置setter方法之后,才会将该属性设置为可写属性。
这些装饰器都是写在类内部的,相当于给把属性变成了函数,同时通过多态修饰它。而由于属性已经成为了函数,在函数内部的调用应当加下划线。
注意:属性的方法名不要和实例变量重名
例如:
class Student(object):
# 方法名称和实例变量均为birth:
@property
def birth(self):
return self.birth
这种就是错误的。
这是因为调用s.birth
时,首先转换为方法调用,在执行return self.birth
时,又视为访问self
的属性,于是又转换为方法调用,造成无限递归,最终导致栈溢出报错RecursionError
一个子类可以有多个父类,因此可以拥有多个父类的功能。而,java只有单继承,因此,java需要通过接口弥补缺陷。
class Animal(object): pass # 大类: class Mammal(Animal): pass class Bird(Animal): pass # 大类 class Runnable(object): def run(self): print('Running...') class Flyable(object): def fly(self): print('Flying...') #可以继承多个父类,狗狗既是动物又能跑 class Dog(Mammal, Runnable): pass
如果继承的多个父类中拥有相同的方法,则按照从继承所有父类的子类到object的原则,对所有的类进行拓扑排序,优先调用离子类最近的方法。如果在拓扑排序中同级,则按最左原则判断拓扑远近。
例如:class B(A1, A2, A3), 则视为A1最近,拓扑顺序为:B,A1,A2,A3
是一些python设定好的用来实现特殊功能的函数。
用来设定print()函数打印实例时的返回值。
>>> 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)
设定控制台打印变量的返回值
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name=%s)' % self.name
__repr__ = __str__
可以让类实现for in循环。
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 # 返回下一个值
实现一个next函数和一个iter对象。
一定要设置StopIteration()错误,因为这个是退出迭代的条件。
>>> for n in Fib():
... print(n)
...
1
1
2
3
5
...
46368
75025 # 到设定的迭代条件之后退出
for in 循环只是按循序取元素,如果想要按照下标取元素,类似element[5],还是要靠getitem方法。
作用:使实例可以靠f[n]的方式来访问。
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
>>> f = Fib()
>>> f[0]
1
>>> f[1]
1
>>> f[2]
2
>>> f[3]
3
>>> f[10]
89
>>> f[100]
573147844013817084101
注:getitem方法的实质是给[]运算符赋予意义。但是,[]运算符所接受的对象除了int型的下标,还可能是slice型的切片参数变量。因此,完整版的getitem函数定义还是需要判断接受的数据类型,对其分别处理。
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是切片,返回切出来的iterator 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
当然,还可以加上对步长、对复数的处理。这样才是完整的对[]运算符的定义。
与之对应的是__setitem__()
方法,把对象视作list或dict来对集合赋值。最后,还有一个__delitem__()
方法,用于删除某个元素。
总之,通过上面的方法,我们自己定义的类表现得和Python自带的list、tuple、dict没什么区别,这完全归功于动态语言的“鸭子类型”,不需要强制继承某个接口。
作用:正常来说,访问一个实例没有定义的属性或者函数,会返回none。getarr()函数的作用是当不存在这个属性或函数的时候,返回一个默认值。
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr): # 注意,参数是一个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
注:只要加上了getattr方法,所有的不存在的值都不会抛出异常而是会返回none,因此需要进行处理。
这是因为,getattr方法不止会处理定义过在getattr()中默认值的属性,而是会处理所有没有在实例中找到的属性,并返回none。因此,如果想让属性不存在时抛出错误,我们需要进行设置:
class Student(object):
def __getattr__(self, attr):
if attr=='age':
return lambda: 25
raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
实例本身也可以当作一个函数被调用。因为不管是实例还是函数,其本质都是指向一段代码的变量。call()函数就是完成这样的功能。
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s.' % self.name)
call()函数中定义了,当函数其变量本身调用时的效果。
>>> s = Student('Michael')
>>> s() # self参数不要传入
My name is Michael.
class Chain(object): def __init__(self, path=''): self.__path = path def __getattr__(self, path): return Chain('%s/%s' % (self.__path, path)) def __call__(self, path): return Chain('%s/%s' % (self.__path, path)) def __str__(self): return self.__path __repr__ = __str__ print(Chain().users('michael').repos) # /users/michael/repos
Step 1:
Chain() #实例化
Step 2:
Chain().users
由于没有给实例传入初始化对应属性的具体信息,从而自动调用__getattr__()函数,从而有:
Chain().users = Chain(’\users’) #这是重建实例
Step 3:
Chain().users(‘michael’)
Chain().users(‘michael’) = Chain(’\users’)(‘michael’) # 这是对实例直接调用,相当于调用普通函数一样
关键就在这步,上面的朋友没有说明晰(并不是说你们不懂),这一步返回的是Chain(’\users\michael’),再一次重建实例,覆盖掉Chain(’\users’),
#记 renew = Chain(’\users\michael’), 此时新实例的属性renew.__path = \users\michael;
Step 4:
Chain().users(‘michael’).repos
这一步是查询renew实例的属性repos,由于没有这一属性,就会执行__getattr__()函数,再一次返回新的实例Chain(’\users\michael\repos’)并且覆盖点之前的实例,
这里记 trinew =Chain(’\users\michael\repos’),不要忘了,一单定义了一个新的实例,就会执行__init__方法;
Step 5:
print(Chain().users(‘michael’).repos) = print(trinew)
由于我们定义了__str__()方法,那么打印的时候就会调用此方法,据此方法的定义,打印回来的是trinew的__path属性,即——\users\michael\repos 。至此,我们也把所有定义的有特殊用的方法都用上了,完毕。
把一类成员从1开始编号。比如,在month中,从一月编号到十二月。
用法:导入Enum(从字面也可以理解,Enum即使数字化)模块后,使用其中的enum方法。
enum(枚举类名,(该枚举类的需要依次编号的元素))
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
这样,枚举类的值就不能改变了。
其实,一个枚举类就是所有元素的mappingproxy(即不可变键值对)的集合。只不过是自动编号,而且可以通过__members__方法对其进行一些处理。
可以利用@unique来检查是否有重复值
from enum import Enum, unique
@unique
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
这样如果在枚举的时候,会自动检测里面的数据是否重复。也就是,所有元素的编号必须不同。
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的
type()函数的作用:
查看类型:
>>> from hello import Hello
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'> # Hello是class,也就是一种类型
>>> print(type(h))
<class 'hello.Hello'> # 对象h是来自hello模块的Hello类型
创建类:
>>> def fn(self, name='world'): # 先定义函数
... print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class,利用dictj
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class '__main__.Hello'>
metaclass是一个class创建时的基石。meta-这个词根,对应着汉语中的”元“。因此,metaclass可以翻译成基类或者是元类。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。