__slots__正常情况下,当我们定义了一个类,并且根据类创建了实例后,我们可以给这些实例绑定任何属性和方法,就这是动态语言的灵活性,下面来看案例:
- 创建一个类
>>> class Student(object):
... pass
...
- 创建一个实例,并且绑定属性'name'
>>> zhangsan = Student()
>>> zhangsan.name = 'zhangsan'
>>> zhangsan.name
'zhangsan'
- 还可以绑定方法
>>> def set_age(self,age):
... self.age = age
...
>>> from types import MethodType #导入模块
>>> zhangsan.set_age = MethodType(set_age,zhangsan) #给zhangsan实例绑定方法
>>> zhangsan.set_age(22)
>>> zhangsan.age
22
- 上面的'zhangsan'实例绑定的方法,只对'zhangsan'本身生效,如果根据'Student'类再次创建实例,'新创建的实例是没有这些方法的'
>>> lisi = Student()
>>> lisi.set_age
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'set_age'
>>> lisi.set_age(33) #无法调用
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'set_age'
- 如果想让上面的方法绑定到所有根据'Student'类创建的实例,我们可以直接给'Student类'绑定方法
>>> def set_score(self,score):
... self.score = score
...
>>> Student.set_score = set_score
>>> lisi.set_score(99)
>>> lisi.score
99
>>> zhangsan.set_score(98)
>>> zhangsan.score
98
通常情况下,上面的set_score方法是可以直接定义到类中的,但是动态绑定允许我们在程序运行的过程中动态的为类添加功能,这在静态语言中很难实现
当我们需要限制实例的属性,例如,只允许对Student类创建的实例添加name和age属性,想要达到这种效果,我们可以在定义类的时候,定义一个特殊的__slots__变量,来限制实例可添加的属性:
- 可以看到在使用'__slots__'变量后,创建的实例'zhangsan'只可以绑定特点的属性
>>> class Student(object):
... __slots__ = ('name','age')
...
>>> zhangsan = Student()
>>> zhangsan.name = 'zhangsan'
>>> zhangsan.age = 22
>>> zhangsan.score = 98
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'
- 要注意的是,类中的'__slots__'变量'只对当前类的实例有效,对继承的子类是无效的'
>>> class Test_Student(Student):
... pass
...
>>> lisi = Test_Student()
>>> lisi.score = 98
>>> lisi.score
98
注意:在类中的__slots__变量只对当前类创建的实例有效,对继承的子类是无效的
在绑定属性时,如果直接把属性暴露出去,这样虽然写起来简单,但是无法检查参数,导致可以随意修改属性,例如:
>>> class Student(object):
... pass
...
>>> zhangsan = Student()
>>> zhangsan.score = 98
>>> zhangsan.score
98
可以看到,上面的zhangsan实例的score属性是可以随意定义、修改的,在实际环境中,肯定不允许这样随意进行定义、修改,为了限制score属性的范围,我们可以在类中通过添加set_score()方法来限制score属性,然后再添加一个get_score()方法来获取score,这种方式在之前的私有变量中提到过,下面来看案例:
- 在类中使用if语句进行判断,限制score属性,可以看到在根据类创建实例后,实例的'score'属性无法随意定义、修改了
>>> class Student(object):
... def get_score(self):
... return self.score
... def set_score(self,value):
... if not isinstance(value,int): #使用isinstance判断传入参数value的类型是否为int类型
... raise ValueError('score not is int') #使用raise抛出异常
... if value < 0 or value > 100:
... raise ValueError('score must between 0 — 100!!')
... self.score = value
...
>>> zhangsan = Student()
>>> zhangsan.set_score('98')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in set_score
ValueError: score not is int
>>> zhangsan.set_score(120)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 8, in set_score
ValueError: score must between 0 — 100!!
>>> zhangsan.set_score(98)
>>> zhangsan.score
98
虽然上面创建类的方式可以实现效果,但是调用方法略显复杂,没有直接使用属性那么简单
之前有说过装饰器这个概念,装饰器可以动态的给函数添加功能,而在Python中,对于类的方法,装饰器一样有效,Python内置的@property装饰器就是负责把一个方法变成属性进行调用
- '@property'的实现比较复杂,来看下面的'Student'类,想要把一个'获取属性的方法'变成属性,只需要加上'@property'即可,而在下面,还可以看到'@property.setter',这是创建的另一个装饰器,负责把一个'赋值属性的方法'变成属性,于是,我们就有拥有了一个可控的属性操作
>>> class Student(object):
... @property
... def score(self):
... return self._score
... @score.setter
... def score(self,value):
... if not isinstance(value,int):
... raise ValueError('score not is int!')
... if value < 0 or value > 100:
... raise ValueError('score must between 0 - 100!!')
... self._score = value
...
>>> zhangsan = Student()
>>> zhangsan.score = 98 #进行赋值操作,实际调用的是第二个score方法,也就是zhangsan.set_score(98)这样的
>>> zhangsan.score #同样的,进行获取操作,实际调用的是第一个score方法,也就是zhangsan.get_score()这样的
98
>>> zhangsan.score = '98' #可以看到在赋值时不符合属性要求会报错
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 8, in score
ValueError: score not is int!
>>> zhangsan.score = 120
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 10, in score
ValueError: score must between 0 - 100!!
- 上面的代码中,单独的
@property其实就是getter方法,并且是只读属性的,例如:- 下面只使用了'getter'的装饰器,可以看到,除了在创建实例的时候可以传入'self._age'的值,创建后直接修改'zhangsan'的'age'属性值会报错,这就是'只读属性' >>> class Student(object): ... def __init__(self,age): ... self._age = age ... @property ... def age(self): ... return print(self._age) ... >>> zhangsan = Student(22) >>> zhangsan.age 22 >>> zhangsan.age = 25 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: can't set attribute 'age'
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 而上面代码中的
@score.setter其实就是setter方法,也就是用来修改属性的值的,例如:- 可以看到在使用'@age.setter'后,可以随意的去修改'self._age'的值 >>> class Student(object): ... @property ... def age(self): ... return print(self._age) ... @age.setter ... def age(self,value): ... self._age = value ... >>> zhangsan = Student() >>> zhangsan.age = 22 >>> zhangsan.age 22 >>> zhangsan.age = 25 >>> zhangsan.age 25
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
注意:
要特别注意的是,
属性的方法名千万不要和实例变量重名,否则会造成无限递归,导致栈溢出报错,例如:>>> class Student(object): ... @property ... def age(self): ... return print(self._age) #这里设置为self._age,就是因为如果不加下划线,那么变量就跟方法名冲突了 ... @age.setter ... def age(self,value): ... self._age = value
- 1
- 2
- 3
- 4
- 5
- 6
- 7
其实这个上面装饰器达到的效果可以添加多个方法来实现,例如添加get_age和set_age等,但是最终调用的是方法,如zhangsan.get_age(),而使用装饰器后,可以看到调用的是属性zhangsan.age
在之前说面向对象编程时,说到过三大基本特性之一的继承特性,继承是面向对象编程的一个重要方式,因为通过继承,子类就可以继承并扩展父类的功能,而一个类是可以继承多个父类的,这种继承就叫做多重继承,也就是多继承,下面来看一个案例,理解多继承的作用:
假如我们要实现以下四种动物:
- Dog——狗
- Bat——蝙蝠
- Parrot——鹦鹉
- Ostrich——鸵鸟
首先可以根据特性进行分类,例如通过哺乳动物和鸟类动物,可以这样分:

而如果通过“能跑”和“能飞”来说,可以这样分:

如果在分的细点,就需要设置更多的层次,例如,哺乳动物中能飞能跑的,鸟类中能飞能跑的

如果还要增加“宠物类”和“非宠物类”的话,类的数量会呈指数增长,这样明显是不行的,正确的做法就是使用多重继承,通过多重继承,一个子类就可以同时获得多个父类的所有功能,例如:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#最上层的Animal类
class Animal(object):
pass
#第二层的哺乳动物和鸟类
class Mammal(Animal):
pass
class Bird(Animal):
pass
#第三层的各种动物
class Dog(Mammal):
pass
class Bat(Mammal):
pass
class Parrot(Bird):
pass
class Ostrich(Bird):
pass
- 添加跑和飞的类
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#添加跑和飞的类
class Runnable(object):
def run(self):
return print('Running...')
class Flyable(object):
def fly(self):
return print('Flying...')
#最上层的Animal类
class Animal(object):
pass
#第二层的哺乳动物和鸟类
class Mammal(Animal):
pass
class Bird(Animal):
pass
#第三层的各种动物
class Dog(Mammal):
pass
class Bat(Mammal):
pass
class Parrot(Bird):
pass
class Ostrich(Bird):
pass
多重继承的特性让动物继承多个类,例如:#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#添加跑和飞的类
class Runnable(object):
def run(self):
return print('Running...')
class Flyable(object):
def fly(self):
return print('Flying...')
#最上层的Animal类
class Animal(object):
pass
#第二层的哺乳动物和鸟类
class Mammal(Animal):
pass
class Bird(Animal):
pass
#第三层的各种动物
class Dog(Mammal,Runnable):
pass
class Bat(Mammal,Flyable):
pass
class Parrot(Bird,Flyable):
pass
class Ostrich(Bird,Runnable):
pass
多重继承这种让类继承多个类的设计,也叫做Mixln,是一种常见设置,只允许单一继承的语言,例如java是不能使用Mixln设计的
__xx__这样的变量或者函数时要知道,这种函数是有特殊用途的,例如可以限制实例添加属性的__slots__变量,以及判断字符长度的__len__()函数,除了这些,Python的类还有许多这样有特殊用途的函数,可以帮助我们定义类__str__我们先来定义一个Student类,并且打印一个实例:
>>> class Student(object):
... def __init__(self,name):
... self.name = name
...
>>> print(Student('lisi')) #打印lisi实例
<__main__.Student object at 0x0000028800FF61D0>
可以看到在打印实例时,输出了一堆<__main__.Student object at 0x0000028800FF61D0>,这样不易阅读,我们可以在类中添加一个__str__()方法,从而使输出更容易阅读,例如:
>>> class Student(object):
... def __init__(self,name):
... self.name = name
... def __str__(self):
... return 'Student object (name is : %s)' % self.name
...
>>> print(Student('lisi'))
Student object (name is : lisi)
- 通过'__str__()'方法,我们可以自定义想要输出的内容,在打印实例的时候会输出,但是只有直接使用'print'时才会输出,赋值给变量的话,则还是原来的
>>> lisi = Student('lisi')
>>> lisi
<__main__.Student object at 0x00000208C62F64A0>
在赋值变量后,输出的还是原来的不易阅读的输出,这是因为直接显示变量调用的不是__str__(),而是__repr__(),这两个方法的区别是==__str__()返回用户看到的字符串==,而==__repr__()返回程序开发者看到的字符串,即__repr__()是为了调试服务的==
解决上面的方法就是再定义一个__repr__()方法,但是通常可以写成__repr__ = __str__
>>> class Student(object):
... def __init__(self,name):
... self.name = name
... def __str__(self):
... return 'Student object (name is : %s)' % self.name
... __repr__ = __str__
...
>>> print(Student('lisi'))
Student object (name is : lisi)
>>> lisi = Student('lisi')
>>> print(lisi)
Student object (name is : lisi)
__iter__如果一个类想像字典或者元组那样被用于for循环,那么就必须要添加一个__iter__()方法,该方法会返回一个迭代对象,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出,下面来看一个案例:
- 斐波那契数列:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class Fib(object):
def __init__(self):
self.a,self.b = 0,1 #初始化定义两属性的值
def __iter__(self): #__iter__返回实例本身
return self
def __next__(self):
self.a,self.b = self.b,self.a + self.b
if self.a > 100:
raise StopIteration()
return self.a
for i in Fib():
print(i)
#输出
1
1
2
3
5
8
13
21
34
55
89
__getitem__上面的Fib实例虽然能够用于for循环,但是把实例直接当作列表来用还是不行,也就是说一些类似于下标的操作是无法执行的,例如:
>>> class Fib(object):
... def __init__(self):
... self.a,self.b = 0 , 1
... def __iter__(self):
... return self
... def __next__(self):
... self.a,self.b = self.b,self.a + self.b
... if self.a > 100:
... raise StopIteration()
... return self.a
...
>>> for i in Fib():
... print(i)
...
1
1
2
3
5
8
13
21
34
55
89
>>> Fib()[5] #报错了
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'Fib' object is not subscriptable
如果想要使创建的实例可以像列表那样使用下标的话,可以再类中添加__getitem__()方法:
>>> class Fib(object):
... def __getitem__(self,n):
... a,b = 1,1
... for i in range(n):
... a,b = b,a + b
... return a
...
>>> f = Fib()
>>> Fib()
<__main__.Fib object at 0x000002C6A39A79D0>
>>> Fib()[0] #可以使用下标获取元素
1
>>> Fib()[5]
8
>>> f[5]
8
列表还有一种切片方法,例如:
>>> list(range(100))[96:100]
[96, 97, 98, 99]
>>> Fib()[5:6]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in __getitem__
TypeError: 'slice' object cannot be interpreted as an integer
>>> f[5:6]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in __getitem__
TypeError: 'slice' object cannot be interpreted as an integer
可以看到列表的切片方法是不适用于实例的,原因是__getitem__()传入的参数可能是一个int类型的,也可能是一个slice切片对象,所以需要做判断,判断传入参数是int还是slice:
>>> class Fib(object):
... def __getitem__(self,n):
... if isinstance(n,int):
... a,b = 1, 1
... for i in range(n):
... a,b = b,a + b
... return a
... if isinstance(n,slice):
... start = n.start #这是slice带的方法
... stop = n.stop
... if start is None:
... start = 0
... a,b = 1 , 1
... L = []
... for i in range(stop):
... if i >= start:
... L.append(a)
... a,b = b,a + b
... return L
...
>>> f = Fib()
>>> f[0:5]
[1, 1, 2, 3, 5]
>>> f[5:6]
[8]
>>> f[0:5:2] #但是没有对step跳数参数做处理
[1, 1, 2, 3, 5]
>>> f[-1:] #也无法对负数做处理
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 15, in __getitem__
TypeError: 'NoneType' object cannot be interpreted as an integer
可以从上面的案例看出,如果想要正确实现一个__getitem__()方法,还需做很多事情,例如跳数和负数等
此外,如果把对象看做是字典,那么__getitem__()的参数也可能是一个可以作为键的对象,例如str,与之对应的是__setitem__()方法,把对象视为列表或者字典来对集合赋值,还有一个__delitem__()方法,用于删除指定元素,通过这些方法,可以使我们在创建类时表现的和Python自带的列表、元组、字典没什么区别
__getattr__正常情况下,当我们调用类的方法或者属性时,如果不存在,那么就会报错,例如:
>>> class Student(object):
... def __init__(self):
... self.name = 'zhangsan'
...
>>> zhangsan = Student()
>>> zhangsan.name
'zhangsan'
>>> zhangsan.score
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'
可以看到错误信息就是在说没有找到score这个属性,想要避免这种错误,除了手动定义一个score属性之外,在Python中还可以在类中添加一个__getattr__()方法,这个方法可以动态返回一个属性,下面来看案例:
>>> class Student(object):
... def __init__(self):
... self.name = 'zhangsan'
... def __getattr__(self,value):
... if value == 'score':
... return 98
...
>>> zhangsan = Student()
>>> zhangsan.name
'zhangsan'
>>> zhangsan.score
98
>>> zhangsan.abc #调用不存在的属性,并且在__getattr__方法里也没有的,就会返回None
>>> if zhangsan.abc == None:
... print("abc is None!!")
...
abc is None!!
我们可以发现,在添加__getattr__()方法后,创建的实例除了可以直接调用name,还可以调用不存在的属性score,这是因为在调用不存在的属性时,Python解释器会试图使用__getattr__(self,'score')来尝试获得属性,这样就可以返回score的值
并且还可以返回函数:
>>> class Student(object):
... def __getattr__(self,attr):
... if attr == 'age':
... return lambda:98
...
>>> zhangsan = Student()
>>> zhangsan.age #返回的是一个函数
<function Student.__getattr__.<locals>.<lambda> at 0x00000205B6373E20>
>>> zhangsan.age() #相同的调用方式需要加括号,表示调用函数
98
需要注意的是,只有在没有找到指定属性的情况下,才会调用__getattr__()方法,已经存在的属性,例如上面案例中,在类里提前定义的name属性,是不会去__getattr__()方法里去找的,会直接输出
在上面的案例可以发现,在使用__getattr__()方法后,调用除了类里、__getattr__()方法里出现的属性时,都会返回None,这是因为定义的__getattr__()方法默认返回的就是None,而返回的数据,我们可以通过raise抛出异常来修改,例如:
>>> class Student(object):
... def __getattr__(self,attr):
... if attr == 'age':
... return 98
... raise AttributeError('Student object has no attribute %s' % attr)
...
>>> zhangsan = Student()
>>> zhangsan.age
98
>>> zhangsan.aaa
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in __getattr__
AttributeError: Student object has no attribute aaa #调用不存在的属性会抛出异常
利用__getattr__()方法,我们可以把一个类的所有属性和方法的调用全部动态化处理,不需要任何其他手段,这种动态调用可以针对完全动态的情况做调用
__call__一个对象实例可以有自己的方法和属性,当我们调用实例方法的时候,我们使用instance.metod()来调用,而在Python中,是可以直接在实例本身上调用的
在创建类时,只需要添加一个__call__()方法,就可以直接对实例进行调用,例如:
>>> class Student(object):
... def __init__(self,name):
... self.name = name
... def __call__(self):
... print('name is : %s' % self.name)
...
>>> zhangsan = Student('zhangsan')
>>> zhangsan() #可以看到把直接调用了实例,后面加了括号
name is : zhangsan
可以看到,在添加__call__()方法后,可以直接调用实例本身,然后会执行__call__()方法下的代码
__call__()方法还可以定义参数,对实例进行直接调用和对一个函数直接调用是一样的,所以完全可以把对象看作成函数,把函数看作成对象,因为两者之间其实没有什么太大的区别
如果把对象看成函数,那么函数本身其实也是可以在运行期间动态创建出来的,这是因为类的实例都是在运行期间创建出来了,这样的话,对象和函数的界限就变得模糊了
那么该如何判断一个变量是对象还是函数呢,其实在更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable可调用的对象,例如函数和上面定义的带有__call__()方法的类实例
使用
callable()函数可以检查一个对象是否是可调用的,如果返回True,对象仍然可能调用失败,但是如果返回的是False,那么调用绝对会失败对于函数、方法、lambda函式、类以及有
__call__()方法的类实例,callable()函数都会返回True,语法就是callable(object)下面来看几个例子:
>>> class Student(object): ... def __init__(self,name): ... self.name = name ... def __call__(self): ... print("name is : %s " % self.name) ... >>> callable(Student) True >>> zhangsan = Student('zhangsan') >>> zhangsan() name is : zhangsan >>> callable(zhangsan) True >>> callable('aaa') False >>> callable(123) False >>> callable(None) False - 可以看到类和实例返回的都是'True',其他的字符串、数字、None返回的都是'False'
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
这个小结,写的不是很全,建议可以去看其他关于枚举类的资源
当我们需要定义常量时,有一个办法就是使用大写变量通过整数来定义,例如:
- 月份
JAN = 1
FEB = 2
MAR = 3
...
NOV = 11
DEC = 12
这样定义的好处肯定就是简单,缺点就是类型是数字,并且仍然是变量
更好的方法就是为这样的枚举类型定义一个class类类型,然后每一个常量都是类的一个唯一实例,而在Python中,提供了Enum类来实现这个功能
- 这样我们就获得了'Month'类型的枚举类,可以看到,下面直接使用'Month.Jan'来引用一个常量
>>> from enum import Enum
>>> Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
>>> Month.Jan
<Month.Jan: 1>
>>> Month.Mar
<Month.Mar: 3>
>>> Month.Dec
<Month.Dec: 12>
- 还可以利用for循环枚举它的全部成员
>>> for name,number in Month.__members__.items():
... print(name,'=>',number,',',number.value)
...
Jan => Month.Jan , 1
Feb => Month.Feb , 2
Mar => Month.Mar , 3
Apr => Month.Apr , 4
May => Month.May , 5
Jun => Month.Jun , 6
Jul => Month.Jul , 7
Aug => Month.Aug , 8
Sep => Month.Sep , 9
Oct => Month.Oct , 10
Nov => Month.Nov , 11
Dec => Month.Dec , 12
number.value中,value属性是自动赋值给成员的int常量的,默认是从1开始计算Enum派生出自定义类>>> from enum import Enum,unique
>>> @unique #@unique装饰器可以帮助检查保证没有重复值
... class Weekday(Enum):
... Sun = 0
... Mon = 1
... Tue = 2
... Wed = 3
... Thu = 4
... Fri = 5
... Sat = 6
...
>>> day_1 = Weekday.Mon
>>> day_1
>>> <Weekday.Mon: 1>
>>> print(day_1)
>>> Weekday.Mon
>>> print(Weekday.Tue)
Weekday.Tue
>>> print(Weekday['Tue'])
Weekday.Tue
>>> print(day_1 == Weekday.Mon)
True
>>> print(day_1 == Weekday(1))
True
>>> Weekday(7)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Users\RZY\AppData\Local\Programs\Python\Python310\lib\enum.py", line 385, in __call__
return cls.__new__(cls, value)
File "C:\Users\RZY\AppData\Local\Programs\Python\Python310\lib\enum.py", line 710, in __new__
raise ve_exc
ValueError: 7 is not a valid Weekday
>>> Weekday(6)
<Weekday.Sat: 6>
>>> for name,number in Weekday.__members__.items():
... print(name, '=>', number)
...
Sun => Weekday.Sun
Mon => Weekday.Mon
Tue => Weekday.Tue
Wed => Weekday.Wed
Thu => Weekday.Thu
Fri => Weekday.Fri
Sat => Weekday.Sat
- 从上面可以看出,既可以用成员名称引用枚举常量,也可以直接根据value的值获得枚举常量。
type就是Python在背后用来创建所有类的元类。
Python中一切皆对象,包括整数、字符串、函数以及类,它们全部都是对象,而且它们都是从一个类创建而来,这个类就是type,type就是Python的内建元类,当然了,也可以创建自己的元类。
动态语言和静态语言最大的区别就是函数和类的定义,静态语言是在编译时定义的,而动态语言时运行时动态创建的,例如:
- 想要定义一个'Hello'的类,可以写一个'hello.py'的模块,以下是在linux环境下
[root@centos-1 ~]# vim hello.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class Hello(object):
def __init__(self,name='zhangsan'):
self.name = name
def hello(self):
print('Hello %s' % self.name)
#保存退出
[root@centos-1 ~]# python3
Python 3.9.9 (main, May 13 2022, 15:23:56)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-16)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from hello import Hello
>>> zhangsan = Hello()
>>> zhangsan.name
'zhangsan'
>>> zhangsan.hello()
Hello zhangsan
>>> lisi = Hello('lisi')
>>> lisi.name
'lisi'
>>> lisi.hello()
Hello lisi
>>> type(Hello) #查看Hello类的类型,可以看到类型是type类型的
<class 'type'>
>>> type(zhangsan) #查看zhangsan实例的类型,可以看到是hello.Hello,前面的hello即hello.py模块,后面的Hello即模块中的Hello类
<class 'hello.Hello'>
>>> type(lisi)
<class 'hello.Hello'>
在Python中,类的定义是运行时动态创建的,而创建类的方法就是使用type()函数
type()函数既可以返回一个对象的类型,也可以创建出新的类型(类),无需使用class关键字创建,例如:
>>> def fn(self,name='zhangsan'): #定义fn函数
... print('Hello %s' % name)
...
>>> Hello = type('Hello',(object,),dict(hello=fn)) #使用type创建Hello类
>>> zhangsan = Hello()
>>> zhangsan.hello()
Hello zhangsan
>>> type(Hello)
<class 'type'>
>>> type(zhangsan)
<class '__main__.Hello'>
- 注释:
type('Hello',(object,),dict(hello=fn))传入的参数分别是:
1、'Hello' : 这是创建的类的名称
2、'(object,)' : 创建的类继承的父类,如果只有一个父类,要记得'元组在只有单元素时后面要加逗号'
3、'dict(hello=fn)' : 创建的类中,方法可以和函数绑定在一起,这里把创建的'fn'函数绑定到创建类的'hello'方法上,可以看到最终实例调用的是'hello'方法,这里使用的是键值的字典格式,这段可以之间写成字典,也可以使用dict转换一下
可以看到,通过type创建的类和直接使用class关键字创建的类是一样的,这是因为Python解释器在遇到类的定义时,其实只是扫描一下类定义的语法,然后直接调用type()函数进行创建的
除了上面使用的type()函数动态创建类以外,要控制类的创建行为,还可以使用metaclass,metaclass直译就是元类,对于metaclass来说,廖雪峰大神的解释是这样的:
正常在定义完类之后,我们就可以根据这个类去创建出实例,步骤是:定义类——>创建实例
但是如果想创建出类呢,那么就必须根据
metaclass创建出类,步骤就变成了:定义metaclass——>创建类——>创建实例,可以看做成类是metaclass创建出来的实例
metaclass是Python面向对象里最难理解的,也是最难的魔术代码,正常情况下是不会碰到需要metaclass的情况,所以这里写的是扩展,下面来简单看一下案例:
- 定义类'ListMetaclass'类,按照默认习惯,metaclass的类名总是以'Metaclass'结尾的
>>> 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): #继承父类list
... pass
...
#注释:
当我们传入关键字参数'metaclass'后,魔术就生效了,它指示Python解释器在创建'MyList'类时,要通过'ListMetaclass.__new__()'来创建,我们可以修改类的定义,例如,加上新的方法然后,返回修改后的定义
其中'__new__'方法接收到的参数依次为:
1、'cls' : 当前准备创建类的对象
2、'name' : 类的名字,也就是'MyList'
3、'bases' : 类继承的父类集合,也就是'list'
4、'attrs' : 创建类的方法集合,这里写的是' attrs['add'] = lambda self, value: self.append(value) ',其中'attrs['add']'的'add'就是方法名称,后面的lambda是方法的代码,使用lambda匿名函数,实现的其实就是往实例对象里面添加元素,实例对象是一个列表
- 下面来测试一下'MyList'是否可以调用'add'方法,可以看到调用成功,但是普通的list并没有add方法
>>> L = MyList()
>>> L.add(1)
>>> L #可以看到,实例对象是一个列表
[1]
>>> L2 = list()
>>> L2.add(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute 'add'
这是廖雪峰大神‘面向对象高级编程’的最后一个小节,这个有点难,有兴趣可以去官网看看,这里不写具体代码了
ORM全称Object Relational Mapping,即对象-关系映射,就是把关系型数据库的一行数据映射为一个对象,一个类对应一个表,这样写代码会更加简单,不需要直接操作SQL语句
要编写一个ORM框架,所有的类只能动态定义,因为只有使用者才能够根据表的结构定义出对应的类