欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Python学习笔记-类和实例(面向对象)

程序员文章站 2022-07-09 23:47:08
...

一.类的定义

类的命名规则按照驼峰原则:既单词首字母大写.

Python3采用的是新式类,故原则上对应该继承于object

类的定义有两种以下语法:

class语法:

class St(object):
    pass

使用元类type:

St=type('St',(object,),{})

第一个参数为类名

第二个参数时继承的父类,这里需要注意元组单个元素的语法

第三个参数是包含属性,方法的对应关系字典

二.实例化

实例是由类按照初始化规则(__init__函数)创建出来的具体对象,实例本身可以共享类的全部属性和方法,也可以创建实例自身的属性和方法,但实例的属性,方法会优先覆盖掉相同的类属性和方法

s1=St()

三.属性

  • 在归属上可以分为类属性,实例属性.
  • 从性质上可以分为私有属性和非私有属性.

由类创造的属性,既可以被该类调用和修改,同时所有该类的实例也都可以对该属性进行引用和修改,但若实例新增了和类属性同名的属性,则对于该实例而言,该类属性被暂时覆盖,若该实例删除了该同名属性的话,该实例同样可以调用类的该属性.

class St(object):
    name='Lia'

s1=St()
print(s1.name)#由于该实例无name属性,引用类属性
s1.name='Mark'#实例属性覆盖类属性
print(St.name)#类属性
print(s1.name)#实例属性
del s1.name#删除实例属性
print(s1.name)#引用类属性
---------------------------------------
#结果输出:
Lia
Lia
Mark
Lia

实例属性时时当前实例私有的,仅可以被当前实例调用,实例属性可以在由类创建实例时初始化而来,也可以在无其他限制条件的前提下,由实例对象本身进行添加

class St(object):
    def __init__(self,name):
        self.name=name
s2=St('Lia')
s2.age=18
s2.weight=55
print(s2.name,s2.age,s2.weight)
---------------------------------------
#输出结果:
Lia 18 55 

为了实例属性安全性与规范性的考虑,可以在类初始化时,用'self.__name'的形式将实例属性私有化,私有化的实例属性只能在类的作用域内直接访问

私有化属性被Python内部机制加密,其实用'_类名.__属性名'的形式仍然可以访问,但不建议这样使用

class St(object):
    def __init__(self,name):
        self.__name=name
    def get_name(self):
        return self.__name
    def set_name(self,value):
        self.__name=value
s2=St('Lia')
print(s2.get_name())
print(s2._St__name)
print(s2.name)
---------------------------------------
#输出结果:
Lia
Lia
Traceback (most recent call last):
  File "D:/Pycharm/TEST/0410.py", line 10, in <module>
    print(s2.name)
AttributeError: 'St' object has no attribute 'name'

在创建类时,可以使用'__slots__'对实例属性进行限定.该限制仅对当前类有效,对继承的子类无效,但若继承的子类也定义该属性的话,则该子类__slot__限定的范围为子类的+父类的

class St(object):
    __slots__=('name','age')

s=St()
s.name='Lia'
print(s.name)
s.age=18
print(s.age)
s.weight=100
---------------------------------------#输出结果:
Lia
18
Traceback (most recent call last):
  File "D:/Pycharm/TEST/0410.py", line 12, in <module>
    s.weight=100
AttributeError: 'St' object has no attribute 'weight'

四.方法

在Python的类和实例对象中,有以下三种类型的方法:

  • 类方法@classmethod
  • 实例方法
  • 静态方法@staticmethod

1.类方法

关键词: @classmethod, cls

类方法是类自身定义的方法,在定义的方法前使用语法@calssmethod,并且方法的第一个参数必须为cls.

类方法可以被类自身调用,也可被实例调用.

class St(object):
    @classmethod
    def printout(cls,text):
        print(text)

s=St()
s.printout('实例在调用类方法')
St.printout('类在调用方法')
---------------------------------------
#输出结果:
实例在调用类方法
类在调用方法 

一般情况下,类方法在类中进行修改和添加,但也可以使用动态的方式来增加或修改类方法.

class St(object):
    @classmethod
    def printout(cls,text):
        print(text)

def in_put(cls,text):
    print('输出的是:',text)
def printout(cls,text):
    print('打印结果是:',text)

St.in_put=in_put
St.printout=printout##覆盖原方法
s=St()
s.in_put('动态增加的方法')
s.printout('输出方法被覆盖了')
---------------------------------------
#输出结果:
输出的是: 动态增加的方法
打印结果是: 输出方法被覆盖了

2.实例方法

实例方式是在类中使用self关键字创建的,实例方法只能通过实例调用,正常情况下类是不能调用实例方法的

同样实例方法也可以动态添加,但分两种情况,一是动态给类增加实例方法,则所有由该类创建的实例都能调用;另一种是只为当前实例动态增加方法,该方法只有当前实例有效,其他实例无法调用.

import types

class St(object):
    def out(self,text):
        print('输出打印:',text)

def get_in(self,text):
    print('录入:',text)
def private(self,text):
    print('private:',text)

St.get_in=get_in

s1=St()
s2=St()
s1.out('s1')
s2.out('s2')
s1.get_in('s1')
s2.get_in('s2')
---------------------------------------
#输出结果:
输出打印: s1
输出打印: s2
录入: s1
录入: s2

只为实例s1调整方法,s2无法调用:

import types
s1.private=types.MethodType(private,s1)

s1.private('这是专属的')
s2.private('我也想试试')
-----------------------------------------
#输出结果:
private: 这是专属的##s1的结果
Traceback (most recent call last):##s2报错
  File "D:/Pycharm/TEST/0410.py", line 20, in <module>
    s2.private('我也想试试')
AttributeError: 'St' object has no attribute 'private'

3.静态方法

静态方法在类中创建时使用@staticmethod语法糖,静态方法类和实例都可以调用,但动态增加的静态方法只有类才能调用

class St(object):
    @staticmethod
    def out(text):
        print('输出打印:',text)

def pub(text):
    print('动态增加的静态方法:',text)

s=St()
St.out('类在调用')
s.out('实例在调用')

St.pub=pub

St.pub('类')
s.pub('实例')
---------------------------------------
#结果输出:
输出打印: 类在调用
输出打印: 实例在调用
动态增加的静态方法: 类
Traceback (most recent call last):
  File "D:/Pycharm/TEST/0410.py", line 19, in <module>
    s.pub('实例')
TypeError: pub() takes 1 positional argument but 2 were given

五.属性方法信息的获取

使用内置函数和魔术方法获取类的属性和方法信息

  1. dir:获取属性和方法
  2. vars:仅获取属性
  3. 常用魔术方法:
  • 类名.__name__: 类的名称
  • 类名.__doc__:帮助文档
  • 类名.__calss__:对应的类
  • 类名.__module__:模块名
  • 类名.__base__:第一个继续的父类
  • 类名.__bases__:所有继承的父类列表
  • 类名.__dict__:同vars

判断类和实例的类型和继承关系等

1.内置type函数

2.使用isinstance函数结合types模块使用

import types

class St():
    def out(self):
        pass

s=St()

print(isinstance(3,(int)))#指定判断
print(isinstance('abc',(list,str)))#范围判断
print(isinstance(s,St))
print(isinstance(s.out,types.MethodType))
---------------------------------------
#输出结果:
True
True
True
True

3.使用内置函数hasattr, getattr, setattr, delattr

class St():
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def out(self):
        pass

s=St('Lia',18)

print(hasattr(s,'name'))
print(hasattr(s,'weight'))
print(getattr(s,'age'))
print(getattr(s,'hight','180cm'))#有则返回,无则返回默认值404,避免报错
setattr(s,'hight',165)
print(getattr(s,'hight'))
delattr(s,'hight')
print(hasattr(s,'hight'))
---------------------------------------
##输出结果:
True
False
18
180cm
165
False

六.类装饰器property

在类的数据封装过程中,为了数据的安全性和规范性,把数据私有化后,就必须去定义额外的方法类访问和修改数据,这样就无法再使用.属性的便捷方法进行数据访问,而此时,就可以使用property 类装饰器,既能保证数据的安全规范,又能保留简介的使用语法.

class St(object):
    def __init__(self,name,score):
        self.__name=name
        self.__score=score
    def get_name(self):
        return self.__name
    def set_name(self,value):
        self.__name=value
    @property
    def score(self):
        return self.__score
    @score.setter
    def score(self,value):
        self.__score=value
---------------------------------------
#输出结果:
Lia
Mark
95
100

注意:如果数据没有被私有化的话,要注意区分property的函数名称与属性名称是否相同,若相同的话,会导致递归错误,所以在没有私有化数据时,一般在使用装修器时选择修改掉数据名(只要保持一致的话,可以随意改,Python内置机制会很神奇的处理).

因名称相同导致反复调用从而引发递归错误:

class St(object):
    def __init__(self,score):
        self.score=score
    @property
    def score(self):
        return self.score
    @score.setter
    def score(self,value):
        self.score=value
s=St(60)
print(s.score)
---------------------------------------#输出结果:
Traceback (most recent call last):
  File "D:/Pycharm/TEST/0410.py", line 21, in <module>
    s=St(60)
  File "D:/Pycharm/TEST/0410.py", line 14, in __init__
    self.score=score
  File "D:/Pycharm/TEST/0410.py", line 20, in score
    self.score=value
  File "D:/Pycharm/TEST/0410.py", line 20, in score
    self.score=value
  File "D:/Pycharm/TEST/0410.py", line 20, in score
    self.score=value
  [Previous line repeated 493 more times]
RecursionError: maximum recursion depth exceeded

只要调整数据名称即可:

class St(object):
    def __init__(self,score):
        self.score=score
    @property
    def score(self):
        return self.whatever#其实是self.score,但为代码可读性,一般使用self._score
    @score.setter
    def score(self,value):
        self.whatever=value#只需要保持一致即可

s=St(60)
print(s.score)
s.score=100
print(s.score)
---------------------------------------
#输出结果:
60
100

使用@property后,回自动生成setter, deleter

class St(object):
    def __init__(self,score):
        self.score=score
    @property
    def score(self):
        return self.whatever
    @score.setter
    def score(self,value):
        self.whatever=value
    @score.deleter
    def score(self):
        del self.whatever

同时也可以直接使用内置函数property:

class St(object):
    def __init__(self,age):
        self.age=18
    def get(self):
        return self.age
    def set(self,value):
        self.age=value
    def pop(self):
        del self.age
    xage=property(fget=get,fset=set,fdel=pop)##fget,fset,fdel为固定参数,相等于对及格方法进行了封装,以后直接调用xage即可完成对应的方法操作


s=St(18)
print(s.xage)
s.xage=100
print(s.xage)
---------------------------------------
#输出结果:
18
100

七.继承

Python中类不仅可以单继承,还可以多继承.一旦子类继承了父类,则子类获得了父类所有的属性和方法,但子类也可以选择重写部分代码,以覆盖父类的属性和方法.

class A(object):
    name='Lia'
    def out(self):
        print(8888888)

class B(A):
    name = 'Mark'
    pass

b=B()
print(b.name)
b.out()
=======================================
#输出结果:
Mark
8888888

如果使用多重继承的话,继承顺序遵循MRO原则,使用''类名.__mro__'',可以查看继承顺序

class A(object):
    name='Lia'
    def out(self):
        print(8888888)
class B(A):
    pass
class C(A):
    pass
class D(B,C):
    pass
print(D.__mro__)
=======================================
#输出结果:
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

返回的元组中,第一个代表该类本身,第二个代表第一优先的继承父类,依次类推

有时子类覆盖了父类的方法,但仍需要使用父类方法时,可以使用super(这是一个类,不是一个函数)

super语法如下:

super(__class__,ins).func(*args,**kwargs)

__calss__:指定需要继承指定的类的父类

ins:代表实例对象,一般都是self

func:是要继承的方法,同时传递该方法需要的参数(不需要再传递cls或self形参,直接传递必要的其他参数)

在python3中,也可以省略掉super中的两个参数的,既代表继承当前对象的所有父类的方法.(如果存在多重继承)

super().func(*args,**kwargs)

实例如下:

class Animal(object):
    def __init__(self, name):
        self.name = name
    def greet(self,value):
        print('Hello, I am %s.' % self.name,value)


class Dog(Animal):
    def greet(self):
        super(Dog, self).greet('first')##指定继承Dog的父类的方法
        super().greet('second')#使用不同的语法,连续两词调用父类方法,不填写super的参数,则默认为当前类和实例
        print('WangWang...')

d=Dog('WangCai')
d.greet()

输出结果:

Hello, I am WangCai. first
Hello, I am WangCai. second
WangWang...

需要注意的super的__class__参数是指定要继承哪个类的父类!以下是例子:

class Base(object):
    def __init__(self):
        print("enter Base")
        print("leave Base")

class A(Base):
    def __init__(self):
        print("enter A")
        super(Base, self).__init__()##此处指定继承Base的父类Object的__init__方法,而Object的初始化方法打印任何东西,所以没有输出
        print("leave A")

a=A()

输出结果: 由于Object的初始化函数中没有任何打印信息,所有仅执行A类中的打印信息

enter A
leave A

在多重继承中,继承顺序是按照mro列表顺序执行的

启动方法按照mro表顺序先进,执行完成方法时,却按照后进先出的原则,从以下例子的输出结果可见.

class Base(object):
    def __init__(self):
        print("enter Base")
        print("leave Base")

class A(Base):
    def __init__(self):
        print("enter A")
        super().__init__()
        print("leave A")

class B(Base):
    def __init__(self):
        print("enter B")
        super().__init__()
        print("leave B")

class C(A, B):
    def __init__(self):
        print("enter C")
        super().__init__()#未指定__class__则继承当前对象所有父类的方法
        print("leave C")

c=C()
print(C.__mro__)

输出结果:

enter C
enter A
enter B
enter Base
leave Base
leave B
leave A
leave C
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Base'>, <class 'object'>)

除了使用super继续执行继承外,还有一种更直接的继承的方法,直接调用父类的方法,但不建议使用

以下是例子:

class Base(object):
    @classmethod
    def greet(cls):
        print('This is greet from Base')

class B(Base):
    def __init__(self):
        Base.greet()

class C(Base):
    def __init__(self):
        super().greet()
b=B()
c=C()

输出如下:

This is greet from Base
This is greet from Base
再次强调:严重不建议直接使用父类名称进行调用

八.魔术方法和定制类

在Python中,以双下划线'__'开头和结果的内置方法被称为魔法方法,魔术方法非常的多,目前我已知的都超过200个,以下列出一部分供参考:

类型
魔法方法
说明
基本魔术方法 __new__(cls[, ...]) 1. __new__ 是在一个对象实例化的时候所调用的第一个方法
2. 它的第一个参数是这个类,其他的参数是用来直接传递给 __init__ 方法
3. __new__ 决定是否要使用该 __init__ 方法,因为 __new__ 可以调用其他类的构造方法或者直接返回别的实例对象来作为本类的实例,如果 __new__ 没有返回实例对象,则 __init__ 不会被调用
4. __new__ 主要是用于继承一个不可变的类型比如一个 tuple 或者 string
__init__(self[, ...]) 构造器,当一个实例被创建的时候调用的初始化方法
__del__(self) 析构器,当一个实例被销毁的时候调用的方法
__call__(self[, args...]) 允许一个类的实例像函数一样被调用:x(a, b) 调用 x.__call__(a, b)
__len__(self) 定义当被 len() 调用时的行为
__repr__(self) 定义当被 repr() 调用时的行为
__str__(self) 定义当被 str() 调用时的行为
__bytes__(self) 定义当被 bytes() 调用时的行为
__hash__(self) 定义当被 hash() 调用时的行为
__bool__(self) 定义当被 bool() 调用时的行为,应该返回 True 或 False
__format__(self, format_spec) 定义当被 format() 调用时的行为
属性相关 __getattr__(self, name) 定义当用户试图获取一个不存在的属性时的行为
__getattribute__(self, name) 定义当该类的属性被访问时的行为
__setattr__(self, name, value) 定义当一个属性被设置时的行为
__delattr__(self, name) 定义当一个属性被删除时的行为
__dir__(self) 定义当 dir() 被调用时的行为
__get__(self, instance, owner) 定义当描述符的值被取得时的行为
__set__(self, instance, value) 定义当描述符的值被改变时的行为
__delete__(self, instance) 定义当描述符的值被删除时的行为
比较运算符 __lt__(self, other) 定义小于号的行为:x < y 调用 x.__lt__(y)
__le__(self, other) 定义小于等于号的行为:x <= y 调用 x.__le__(y)
__eq__(self, other) 定义等于号的行为:x == y 调用 x.__eq__(y)
__ne__(self, other) 定义不等号的行为:x != y 调用 x.__ne__(y)
__gt__(self, other) 定义大于号的行为:x > y 调用 x.__gt__(y)
__ge__(self, other) 定义大于等于号的行为:x >= y 调用 x.__ge__(y)
算数运算符 __add__(self, other) 定义加法的行为:+
__sub__(self, other) 定义减法的行为:-
__mul__(self, other) 定义乘法的行为:*
__truediv__(self, other) 定义真除法的行为:/
__floordiv__(self, other) 定义整数除法的行为://
__mod__(self, other) 定义取模算法的行为:%
__divmod__(self, other) 定义当被 divmod() 调用时的行为
__pow__(self, other[, modulo]) 定义当被 power() 调用或 ** 运算时的行为
__lshift__(self, other) 定义按位左移位的行为:<<
__rshift__(self, other) 定义按位右移位的行为:>>
__and__(self, other) 定义按位与操作的行为:&
__xor__(self, other) 定义按位异或操作的行为:^
__or__(self, other) 定义按位或操作的行为:|
反运算 __radd__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__rsub__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__rmul__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__rtruediv__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__rfloordiv__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__rmod__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__rdivmod__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__rpow__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__rlshift__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__rrshift__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__rxor__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__ror__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
增量赋值运算 __iadd__(self, other) 定义赋值加法的行为:+=
__isub__(self, other) 定义赋值减法的行为:-=
__imul__(self, other) 定义赋值乘法的行为:*=
__itruediv__(self, other) 定义赋值真除法的行为:/=
__ifloordiv__(self, other) 定义赋值整数除法的行为://=
__imod__(self, other) 定义赋值取模算法的行为:%=
__ipow__(self, other[, modulo]) 定义赋值幂运算的行为:**=
__ilshift__(self, other) 定义赋值按位左移位的行为:<<=
__irshift__(self, other) 定义赋值按位右移位的行为:>>=
__iand__(self, other) 定义赋值按位与操作的行为:&=
__ixor__(self, other) 定义赋值按位异或操作的行为:^=
__ior__(self, other) 定义赋值按位或操作的行为:|=
一元操作符 __neg__(self) 定义正号的行为:+x
__pos__(self) 定义负号的行为:-x
__abs__(self) 定义当被 abs() 调用时的行为
__invert__(self) 定义按位求反的行为:~x
类型转换 __complex__(self) 定义当被 complex() 调用时的行为(需要返回恰当的值)
__int__(self) 定义当被 int() 调用时的行为(需要返回恰当的值)
__float__(self) 定义当被 float() 调用时的行为(需要返回恰当的值)
__round__(self[, n]) 定义当被 round() 调用时的行为(需要返回恰当的值)
__index__(self) 1. 当对象是被应用在切片表达式中时,实现整形强制转换
2. 如果你定义了一个可能在切片时用到的定制的数值型,你应该定义 __index__
3. 如果 __index__ 被定义,则 __int__ 也需要被定义,且返回相同的值
上下文管理 __enter__(self) 1. 定义当使用 with 语句时的初始化行为
2. __enter__ 的返回值被 with 语句的目标或者 as 后的名字绑定
__exit__(self, exc_type, exc_value, traceback) 1. 定义当一个代码块被执行或者终止后上下文管理器应该做什么
2. 一般被用来处理异常,清除工作或者做一些代码块执行完毕之后的日常工作
容器类型 __len__(self) 定义当被 len() 调用时的行为(返回容器中元素的个数)
__getitem__(self, key) 定义获取容器中指定元素的行为,相当于 self[key]
__setitem__(self, key, value) 定义设置容器中指定元素的行为,相当于 self[key] = value
__delitem__(self, key) 定义删除容器中指定元素的行为,相当于 del self[key]
__iter__(self) 定义当迭代容器中的元素的行为
__reversed__(self) 定义当被 reversed() 调用时的行为
__contains__(self, item) 定义当使用成员测试运算符(in 或 not in)时的行为

定制类就是通过在类上实现特定的魔术方法,从而让这个类可以使用特定的方法或者函数:

注意:魔术方法一定要用return返回特定的值,否则或报错

class S(object):
    name='测试'
    def out(self):
        print('Method out is running')
        return self.out.__name__
    def __str__(self):
        return self.name
    def __call__(self):
        return self.out()
    def __bool__(self):
        if self.name != '':
            return True
        else:
            return False

s=S()
print(s)##打印实例s
print(s())##对实例s进行调用
print(bool(s))##用内置函数bool判断实例s
s.name=''
print(bool(s))
---------------------------------------
##输出结果:
测试##打印实例的结果
Method out is running##调用实例的结果
out##调用实例的结果
True
False

部分实现的魔术方法与函数的对应关系如下:

函数或方法 需要实行的魔术方法
abs __abs__
bool __bool__
bytes __bytes__
print __str__
可以像函数一样被调用 __call__
dir __dir__
实现isinstance(obj,calss) __instancecheck__
让实例像字典一样赋值 __setitem__
__getitem__
__delitem__
循环迭代 __getitem__
__iter__(优先级高)
__next__

9.元类和__new__

首先从作用概括下:__init__方法用于实例的初始化,__new__方法用于类的初始化.

元类的概念和__new__是紧密结合的,所有先从__new__说起.

class People(object):
    def __init__(self):
        print('__init__ is called first')
    def __new__(cls, *args, **kwargs):
        print('__new__ is called first')
        # return super().__new__(cls, *args, **kwargs)##为了达到演示效果,故意注释掉的

P=People()
print(type(P))
---------------------------------------
##输出结果:
__new__ is called first
<class 'NoneType'>

从以上代码可见:

1.在创建实例对象时,__new__最先被调用

2.__init__方法并没有被调用

3.,实例对象P的类型居然是NoneType

其实,以上第2点和第3点的本质原因是,__new__方法没有返回任何类对象导致的,就也就是故意注释掉一行代码的原因.

所以,非常重要!!!__new__方法一定要返回类对象!!

正确的代码演示如下:

class People(object):
    def __init__(self):
        print('__init__ is called first')
    def __new__(cls, *args, **kwargs):
        print('__new__ is called first')
        return super().__new__(cls, *args, **kwargs)

P=People()
print(type(P))
---------------------------------------
##输出结果:
__new__ is called first
__init__ is called first
<class '__main__.People'>

从以上代码可见:

1.__new__方法比__init__方法先被调用后,才调用的__init__方法

2.因为__new__方法返回了类对象,P实例才能被该返回的类对象所创建,从这一点也说明了,__new__方法是创建类对象时调用的,__init__方法是创建实例对象调用的

3.实例对象时由类对象创建而来,所以很明显,没有__new__方法创建类的话,__init__方法是无法创建实例的


大概了解了__new__ 方法的作用后,再来研究下类的创建方法.

前文已经提到,类有两种语法,但实际上,在Python解释中,会把class定义的语法方式转换为type的方式,看以下的例子:

解释下,因为此处讨论的是类的创建,所以都使用类方法.

class St1(object):
    name='Lip'
    @classmethod
    def greet(cls):
        print('Heloo,I am %s'%St1.name)

print(St1.name)
St1.greet()
---------------------------------------
##输出结果:
Lip
Heloo,I am Lip

以上的class语法的创建方法,在Python解释器中,会被转译为使用type的方式,既以下的例子:

@classmethod
def greet(cls):
    print('Heloo,I am %s' % St2.name)

St2=type('St2',(object,),{'name':'grant','greet':greet})

print(St2.name)
St2.greet()
---------------------------------------##输出结果:
grant
Heloo,I am grant

所以,讨论的核心点又回到了type身上.

我们来查看下一些内置类的父类或者自己创建的类的父类:

print(int.__class__)
print(list.__class__)
print(str.__class__)
print(St1.__class__.__class__)#该类继续自object,所以需要查看它父类的父类
print(St2.__class__.__class__)#同上

输出结果:

<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>

可见,这些类最终都指向了type,而class语法创建类的方式也会被转化为type的方式,所以,什么是元类!!!type!!type就是python中一切类的元类!

OK!以实例的创建为例子,重新梳理一下:

通过__init__初始化方法,在创建实例时,可以为实例绑定上与身俱来的属性或者方法

而通过__new__方法和metaclass(python2中使用__metaclass__ 属性)的配合,在创建类时,也可以为类绑定与身俱来的属性或方法

所以说,__init__方法用于实例的初始化,__new__方法用于类的初始化.

下面来看看例子:

class St3MateClass(type):
    def __new__(cls,name,bases,attrs):
        print('This is metaclass')
        attrs['age']=18
        return super().__new__(cls,name,bases,attrs)

class St4(St2,metaclass=St3MateClass):
    pass

s4=St4()
print(s4.name)
s4.greet()
print(s4.age)

输出结果:

This is metaclass##创建类时打印的
grant
Heloo,I am grant
18##新增加了age属性

以上代码可见:

1.St4继承于St2,所以拥有了name属性和greed方法

2.St4创建时,也使用了metaclass来初始化类的创建,不仅打印了This is metaclass 信息,还新增了类属性age

3.关于name,bases,attrs这几个参数,其实与type函数创建类时是一致的,name代表类名,bases表达继承的父类元组,attrs代表的是该类的属性,方法的对应关系字典,所以这几个参数也是固定的

4.可以通过__dict__方法查看对应的属性,可见,age属性确实添加到了St4类的属性列表中,所以attrs其实也就是与__dict__对应.

print(St4.__dict__)
{'__module__': '__main__', 'age': 18, '__doc__': None}

总结一下:

1.__init__方法用于实例的初始化,__new__方法用于类的初始化.

2.__new__方法一定要返回类对象,否则类其实没有创建成功,从而也无法使用该类去创建实例

3.使用__new__方法和metaclass配合,可以为改变类与身俱来的功能

4.一般元类后面都加上MetaClass,以表示是元类