小待

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 :: 管理 ::

Python学习第十二篇:面向对象进阶

一、isinstance和issubclass

1.isinstance(obj,cls)检查是否obj是否是类 cls 的对象

 

isinstance是Python中的一个内建函数

语法:

isinstance(object, classinfo)

如果参数object是classinfo的实例,或者object是classinfo类的子类的一个实例, 返回True。如果object不是一个给定类型的的对象, 则返回结果总是False

class Foo(object):
    pass

obj=Foo()
print(isinstance(obj, Foo))

输出

True

 

2.issubclass(sub, super)检查sub类是否是 super 类父类的派生类

B是A类的子类, 返回True。否则返回False

    pass

class B(A):
    pass

print(issubclass(B, A))

输出

True

 

二、反射

1.反射定义

反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。

 

2.反射的实现

python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象,都可以使用反射

利用四个可以实现自省的函数,适用于类和对象(一切皆对象,类本身也是一个对象)

1.hasattr 2.setattr 3.getattr 4.delattr

 

1. hasattr(object,name)  

判断一个对象里面是否有name属性或者name方法,返回BOOL值,有name特性返回True, 否则返回False。需要注意的是name要用括号括起来。

>>> class test():
...     name="xiaohua"
...     def run(self):
...             return "HelloWord"
...
>>> t=test()
>>> hasattr(t, "name") #判断对象有name属性
True
>>> hasattr(t, "run")  #判断对象有run方法
True
>>>

 2. getattr(object, name, default=None)

获取对象object的属性或者方法,如果存在打印出来,如果不存在,打印出默认值,默认值可选。需要注意的是,如果是返回的对象的方法,返回的是方法的内存地址,如果需要运行这个方法,可以在后面添加一对括号。

...     name="xiaohua"
...     def run(self):
...             return "HelloWord"
...
>>> t=test()
>>> getattr(t, "name") #获取name属性,存在就打印出来。
'xiaohua'
>>> getattr(t, "run")  #获取run方法,存在就打印出方法的内存地址。
<bound method test.run of <__main__.test instance at 0x0269C878>>
>>> getattr(t, "run")()  #获取run方法,后面加括号可以将这个方法运行。
'HelloWord'
>>> getattr(t, "age")  #获取一个不存在的属性。
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: test instance has no attribute 'age'
>>> getattr(t, "age","18")  #若属性不存在,返回一个默认值。
'18'
>>>

3. setattr(object, name, values)

给对象的属性赋值,若属性不存在,先创建再赋值。

>>> class test():
...     name="xiaohua"
...     def run(self):
...             return "HelloWord"
...
>>> t=test()
>>> hasattr(t, "age")   #判断属性是否存在
False
>>> setattr(t, "age", "18")   #为属相赋值,并没有返回值
>>> hasattr(t, "age")    #属性存在了
True
>>>

4. delattr(object, name)

删除object对象名为name的属性。

 

综合例子

class BlackMedium:
    feature='Ugly'
    def __init__(self,name,addr):
        self.name=name
        self.addr=addr

    def sell_house(self):
        print('%s 卖房子' %self.name)
    def rent_house(self):
        print('%s 租房子' %self.name)

b1=BlackMedium('恒大','回龙观')

#检测是否含有某属性
print(hasattr(b1,'name'))
print(hasattr(b1,'sell_house'))

#获取属性
n=getattr(b1,'name')
print(n)
func=getattr(b1,'rent_house')
func()

# getattr(b1,'aaaaaaaa') #报错
print(getattr(b1,'aaaaaaaa','不存在啊'))

#设置属性
setattr(b1,'sb',True)
setattr(b1,'show_name',lambda self:self.name+'123')
print(b1.__dict__)
print(b1.show_name(b1))

#删除属性
delattr(b1,'addr')
delattr(b1,'show_name')
#delattr(b1,'show_name111')#不存在,则报错

print(b1.__dict__)


结果:
True
True
恒大
恒大 租房子
不存在啊
{'show_name': <function <lambda> at 0x10c9e8f28>, 'sb': True, 'addr': '回龙观', 'name': '恒大'}
恒大123
{'sb': True, 'name': '恒大'}

 

类也是对象

class Foo(object):

    staticField = "old"

    def __init__(self):
        self.name = '123'

    def func(self):
        return 'func'

    @staticmethod
    def bar():
        return 'bar'

print(getattr(Foo, 'staticField'))
print(getattr(Foo, 'func'))
print(getattr(Foo, 'bar'))

结果:
old
<function Foo.func at 0x10f89f488>
<function Foo.bar at 0x10f89f510>

 

模块的反射

import sys                          
                                    
                                    
def s1():                           
    print('s1')                     
                                    
                                    
def s2():                           
    print('s2')                     
                                    
                                    
this_module = sys.modules[__name__] 
                                    
print(hasattr(this_module, 's1'))   
print(getattr(this_module, 's2'))

结果:
True
<function s2 at 0x108f4d400>

 

先判断再获取使用

if hasattr(f1,'get'):
     func=getattr(f1,'get')
     func()

  

3.反射的好处

  • 实现可插拔机制

可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能。

程序员A未实现功能

class FtpClient:
    'ftp客户端,但是还么有实现具体的功能'
    def __init__(self,addr):
        print('正在连接服务器[%s]' %addr)
        self.addr=addr

但不影响程序员B继续实现其他逻辑,利用反射事先做判断

#from module import FtpClient
f1=FtpClient('192.168.1.1')
if hasattr(f1,'get'):        #判断方法是否实现
    func_get=getattr(f1,'get')
    func_get()
else:
    print('---->不存在此方法')
    print('处理其他的逻辑')

 

  • 动态导入模块(基于反射当前模块成员)

 

三、__enter__ 和__exit__

上下文管理协议,

即with语句,为了让一个对象兼容with语句

对对象的操作才会触发

class Foo:
    def __enter__(self):
        print('__enter__')
     return '1234' #会把enter这儿的值返回,若是as赋值,则这需要返回值 def __exit__(self,exc_type,exec_val,exc_tb): print('__exit__') with Foo() : #也是类的实例化 print('==>')
结果: #都是绑定到对象的方法,先执行enter方法,再执行exit __enter__ ==> __exit__

with Foo() as x:   #实际就是x=Foo()
print('==>')
print(x)

结果: #在enter那儿返回值给x
__enter__
==>
1234
__exit__

 

模拟with打开文件:

class Open:
    def __init__(self, name,mode='r',encoding='utf-8'):   #对象默认执行
        self.name = name
        self.mode=mode
        self.encoding=encoding
        self.f=open(self.name,mode=self.mode,encoding=self.encoding)
    def __enter__(self):                                  #with上下文管理里会先执行这个
        return self.f                                     #返回self.f =open(...)
    def __exit__(self, exc_type, exc_val, exc_tb):        #with上下文管理里最后执行这个,依次为异常类型、异常的值、异常的追踪信息
        print(exc_type)
        print(exc_val)
        print(exc_tb)
        self.f.close()                                    #关闭                       
        return True                                       #加上这个with出错也不会抛异常(表示with自己处理了,不需要系统抛),不加的话with以外的代码也不会执行(程序崩溃)

with Open('c.txt','w') as f:          #f=self.f
print(f)
1/0 #1除以0报异常,下面代码就不会运行
print('===>')
f.write('11111\n') #上面代码正常的话就能运行
f.write('22222\n')
#最后执行__exit__

结果:

<_io.TextIOWrapper name='c.txt' mode='w' encoding='utf-8'>
<class 'ZeroDivisionError'>
division by zero
<traceback object at 0x0000026A83D6F788>

 

关于异常(自定义):

 

  

 四、__init__  、__str__

__init__()

在类中__init__()函数叫构造函数,又叫构造方法,也可以叫初始化函数。它的作用就是初始化实例时,初始化传入实例的的默认值。如果不写__init__(),就会调用的默认为空的__init__()。说白了,这个方法不管你写不写,都会调用,一旦实例化就会调

class Chinese:
    country = 'China'
    def __init__(self, name, age, sex):  # p1的实例化过程其实就是调用Chinese的init方法,并把自己传进去了: Chinese.__init__(p1,'egon','18','male')      
        self.Name = name                 # 实例化(初始化)过程其实就是依次赋值: p1.Name=name;p1.Age=age,p1.Sex=sex
        self.Age = age
        self.Sex = sex
    def talk(self):
        print('%s is talking' %self.Name)
        
p1=Chinese('egon','18','male')         #实际就是:Chinese.__init__(p1,'egon','18','male')
p2=Chinese('alex','9000','female')     #实际就是:Chinese.__init__(p1,'egon','18','male')

     __init__里面不能有返回值

 

__str__()

类里面有__str__(self),默认会执行,表示什么都不干打印内存结果,

所以可以自己重写这个函数来返回属性值(list等实例化时就是重写了)

def __str__(self):         #重写这个什么都不做的__str__
    # print('run __str__')
    return 'name:%s age:%s' %(self.name,self.age)

 必须有返回值,必须返回字符串类型 

 

 

额外:---------------------------------------------------------

__str__,__repr__,__format__

改变对象的字符串显示__str__,__repr__

自定制格式化字符串__format__

  • __str__,__repr__

我们先定义一个Student类,打印一个实例:

>>> class Student(object):
...     def __init__(self, name):
...         self.name = name
...
>>> print(Student('Michael'))
<__main__.Student object at 0x109afb190>

打印出一堆<__main__.Student object at 0x109afb190>,不好看。

怎么才能打印得好看呢?只需要定义好__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)

这样打印出来的实例,不但好看,而且容易看出实例内部重要的数据。

但是细心的朋友会发现直接敲变量不用print,打印出来的实例还是不好看:

>>> s = Student('Michael')
>>> s
<__main__.Student object at 0x109afb310>

这是因为直接显示变量调用的不是__str__(),而是__repr__(),两者的区别是__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说,__repr__()是为调试服务的。

解决办法是再定义一个__repr__()。但是通常__str__()__repr__()代码都是一样的,所以,有个偷懒的写法:

class Student(object):
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return 'Student object (name=%s)' % self.name
    __repr__ = __str__

 

  • __format__
date_dic={
    'ymd':'{0.year}:{0.month}:{0.day}',
    'dmy':'{0.day}/{0.month}/{0.year}',
    'mdy':'{0.month}-{0.day}-{0.year}',
}
class Date:
    def __init__(self,year,month,day):
        self.year=year
        self.month=month
        self.day=day

    def __format__(self, format_spec):
        if not format_spec or format_spec not in date_dic:
            format_spec='ymd'
        fmt=date_dic[format_spec]
        return fmt.format(self)

d1=Date(2016,12,29)
print(format(d1))
print('{:mdy}'.format(d1))

输出

2016:12:29
12-29-2016

 

五、__del__

析构方法,析构函数

---》当对象在内存中被释放时,自动触发执行。

此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。

定义:

class Foo:

    def __del__(self):
        print('执行删除')

f1=Foo()
print('**')
del f1             #正常是程序执行完再删除,再才打印析构函数。但是这里手动先删除释放对象了,所以先执行
print('------->')

结果:
**
执行删除
------->

用处:

比如在建立数据库连接后,默认执行完不关闭。可以在这里手动执行关闭。做清理操作。

 

六、__setitem__,__getitem__,__delitem__

当实例中有类似字典的操作触发

用字典的方法来操作时会执行这几个方法。这时key是操作的目标,value是实际值。可以修改函数内容来做想要的操作

class Foo:
    def __init__(self,name):
        self.name=name

    def __getitem__(self, item):
        print("====>",item)
        return self.__dict__[item]             #通过这样修改成字典取值和可以另外赋值其他没有的属性--->一个接口产生多个对象(属性)

    def __setitem__(self, key, value):
        self.__dict__[key]=value
        print("====>",value)        #对象本身不能字典取值,加上setitem就可以字典取值。这里输出赋的值,字典形式设置时会默认执行setitem,不是字典形式不会

    def __delitem__(self, key):
        self.__dict__.pop(key)                #通过这样就修改成了可以删除其他没有属性
        print('===>del obj[key]')

    def __delattr__(self, item):              #不是字典形式的输出
        self.__dict__.pop(item)
        print('===>del obj.key')

f1=Foo('sb')


f1.name='jh'               #不是字典形式,所以不会执行setitem
f1['age']=18
f1['age1']=20

print(f1['age1'])          #记住 这样其实拿不到值,需要通过getitem返回值。因为本身字典里面没有age 属性,通过这样改成了字典里可以赋值,所以需要return
print(f1['name'])          #原本有name属性,所以可以取到

del f1['age']
del f1.age1                #不同形式


结果:
====> 18
====> 20
====> age1
20
====> name
jh
===>del obj[key]
===>del obj.key

用于需要字典形式操作对象时候,就需要这三个方法了:

def func(obj,key,value):
    obj[key]=value             


dic={'name':'egon','age':18}  #调用这个函数就需要字典操作了
print(dic)
func(dic,'name','666')             #obj['name']='666'           
print(dic)

结果:
{'name': 'egon', 'age': 18}
{'name': '666', 'age': 18}


当需要把对象传进去,需要字典操作,但对象本身不能字典操作,加上上面三个方法就可以了:
func(f1,'name','666')
print(f1.name)

 

七、__setattr__,__getattr__,__delattr__

当对对象的属性有. 的操作时触发

1.__getattr__

拦截点号运算。当对未定义的属性名称和实例进行点号运算时,就会用属性名作为字符串调用这个方法。如果继承树可以找到该属性,则不调用此方法

 找不到才会执行 

 

2.__setattr__    (__init__ 实例化时有.属性操作,也会触发)

会拦截所有属性的的赋值语句。如果定义了这个方法,self.arrt = value 就会变成self,__setattr__("attr", value).

这个需要注意:当在__setattr__方法内对属性赋值时,不可使用self.attr = value方式,因为会再次调用self,__setattr__("attr", value),这样会形成无穷递归循环,最后导致堆栈溢出异常。

  

 应该通过对属性字典做索引运算来赋值任何实例属性,也就是使用 self.__dict__['name'] = value --> 默认就会这样,若是自定义了setattr (改了就只执行改了的) 则需要加上这个  

             (使用setattr()也是self.key=value)

 

3.__delattr__

删除对象下的属性

  (__del__是删除对象---程序运行完也会释放对象)

 

class Foo:
    x=1
    def __init__(self,y):
        self.y=y

    def __getattr__(self, item):
        print('----> from getattr---你找的属性不存在')


    def __setattr__(self, key, value):
        print('----> from setattr')
        # self.key=value             #这就无限递归了,不能使用.的设置方法
        self.__dict__[key]=value     #应该使用它

    def __delattr__(self, item):
        print('----> from delattr')
        # del self.item              #无限递归了,这样也是.的操作
        self.__dict__.pop(item)


#__setattr__ 添加/修改属性会触发它的执行 f1=Foo(10) print(f1.__dict__) # 因为重写了__setattr__,凡是赋值操作都会触发自定义的操作,什么都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值 f1.z=3 print(f1.__dict__) #__delattr__ 删除属性的时候会触发 f1.__dict__['a']=3 #我们可以直接修改属性字典,来完成添加/修改属性的操作 del f1.a print(f1.__dict__) #__getattr__只有在使用点调用属性且属性不存在的时候才会触发 f1.xxxxxx

输出

----> from setattr
{}
----> from setattr
{}
----> from delattr
{}
----> from getattr---你找的属性不存在

 

八、二次加工,二次加工标准类型

标准类型:字符串,字典,列表。。。

想自定义一个只能加字符串的列表就需要二次加工标准类型

 

1.包装

python为大家提供了标准数据类型,以及丰富的内置方法,

但在很多场景下需要基于标准数据类型来定制自己的数据类型,新增/改写方法,这就用到了继承/派生知识

(其他的标准类型均可以通过下面的方式进行二次加工)

class List(list):                       #继承list所有的属性,也可以派生出自己新的,比如append和mid
    def append(self, p_object):         #派生自己的append,这里面加了一个类型检查
        if not isinstance(p_object,int):
            raise TypeError('must be int')
        super().append(p_object)        #使用super()重用父类

    @property                           #把动词变成名词
    def mid(self):                      #加一个求中间值的函数
        index=len(self)//2
        return self[index]

l=List([1,2,3,4]) print(l) l.append(5) print(l) # l.append('1111111') #报错,必须为int类型 print(l.mid) #其余的方法还是继续都继承原list的 l.insert(0,-123) print(l) l.clear() print(l)

输出

[1, 2, 3, 4]
[1, 2, 3, 4, 5]
3
[-123, 1, 2, 3, 4, 5]
[]

 

2.授权   (自己没有的想用别人的,反射过来)

授权是包装的一个特性, 包装一个类型通常是对已存在类型的一些定制。这种做法可以新建,修改或删除原有产品的功能,其它的则保持原样。

授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。(类才有继承,修改函数功能就没办法用继承,只能授权处理)

实现授权的关键点就是覆盖 __getattr__ 方法  

  --> 找不到的函数属性就会执行__getattr__ -->然后getattr里面利用反射返回存在的函数,这样就能执行了

class List:
    def __init__(self,seq,permission=False):
        self.seq=seq
        self.permission=permission
def clear(self): if not self.permission: #self.permission是自带的属性,所以需要重写__init__ raise PermissionError('not allow the operation') self.seq.clear() def __getattr__(self, item): #找不到就会执行这里 return getattr(self.seq,item) #反射回存在的函数 def __str__(self): return str(self.seq)
l=List([1,2,3])
# l.clear() #此时没有权限,抛出异常 l.permission=True print(l) l.clear() print(l) #基于授权,获得insert方法 (去__getattr__里找的) l.insert(0,-123) print(l)


结果:
[1, 2, 3]
[]
[-123]

 

九、迭代器协议:

__next__ 、__iter__

可以通过这两个函数模拟迭代器:

class Foo:
    def __init__(self,n,stop):   #定义停止位置
        self.n=n
        self.stop=stop
    def __next__(self):
        if self.n >= self.stop:
            raise StopIteration    #抛异常,for循环会捕捉这个异常暂停
        x=self.n
        self.n+=1
        return x                   #如果直接return self.n下次就没值了

    def __iter__(self):            #迭代器的__iter__是本身
        return self

obj=Foo(0,5)       #0 1 2 3 4
print(next(obj))   #obj.__next__()
print(next(obj))   #obj.__next__()

结果:
0
1


from collections import Iterator
print(isinstance(obj,Iterator))      #判断是否是迭代器

结果:
True

for i in obj:   #还可以循环取值
    print(i)

结果:
0
1
2
3
4

  

  

十、__call__

一般情况下,对象不可被调用

可以在类里加一个绑定到对象的方法__call__就能使对象后面加括号,触发执行(可以被调用) --->此时也会执行__call__里的代码

注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;

       而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

       __call__里面有返回值,默认None

class Foo:
    def __init__(self):
        pass
    
    def __call__(self, *args, **kwargs):
        print('__call__')

obj = Foo()  # 执行 __init__
obj()        # 执行 __call__,对象被调用

结果:
__call__

print(type(Foo))
结果:
<class 'type'> #python里一切皆对象,类的类就是type,type里面有__call__(),最终都是执行函数

 

 

__doc__:查看文档信息--注释信息 -- if not value.__doc__:

 

十一、eval(),exec()

1、eval()

  • 函数的作用

计算指定表达式的值。也就是说它要执行的Python代码只能是单个运算表达式(注意eval不支持任意形式的赋值操作),而不能是复杂的代码逻辑,这一点和lambda表达式比较相似。

  • 函数定义
eval(expression, globals=None, locals=None)
  • 参数说明:
  1. expression:必选参数,可以是字符串,也可以是一个任意的code对象实例(可以通过compile函数创建)。如果它是一个字符串,它会被当作一个(使用globals和locals参数作为全局和本地命名空间的)Python表达式进行分析和解释。
  2. globals:可选参数,表示全局命名空间(存放全局变量),如果被提供,则必须是一个字典对象。
  3. locals:可选参数,表示当前局部命名空间(存放局部变量),如果被提供,可以是任何映射对象。如果该参数被忽略,那么它将会取与globals相同的值。
  4. 如果globals与locals都被忽略,那么它们将取eval()函数被调用环境下的全局命名空间和局部命名空间。
  • 返回值:
  1. 如果expression是一个code对象,且创建该code对象时,compile函数的mode参数是'exec',那么eval()函数的返回值是None;
  2. 否则,如果expression是一个输出语句,如print(),则eval()返回结果为None;
  3. 否则,expression表达式的结果就是eval()函数的返回值;
x = 10

def func():
    y = 20
    a = eval('x + y')
    print('a: ', a)
    b = eval('x + y', {'x': 1, 'y': 2})
    print('b: ', b)
    c = eval('x + y', {'x': 1, 'y': 2}, {'y': 3, 'z': 4})
    print('c: ', c)
    d = eval('print(x, y)')
    print('d: ', d)

func()

输出

a:  30
b:  3
c:  4
10 20
d:  None
  • 对输出结果的解释:
  1. 对于变量a,eval函数的globals和locals参数都被忽略了,因此变量x和变量y都取得的是eval函数被调用环境下的作用域中的变量值,即:x = 10, y = 20,a = x + y = 30
  2. 对于变量b,eval函数只提供了globals参数而忽略了locals参数,因此locals会取globals参数的值,即:x = 1, y = 2,b = x + y = 3
  3. 对于变量c,eval函数的globals参数和locals都被提供了,那么eval函数会先从全部作用域globals中找到变量x, 从局部作用域locals中找到变量y,即:x = 1, y = 3, c = x + y = 4
  4. 对于变量d,因为print()函数不是一个计算表达式,没有计算结果,因此返回值为None

 

2.exec()

  • 函数的作用:

动态执行Python代码。也就是说exec可以执行复杂的Python代码,而不像eval函数那么样只能计算一个表达式的值。

  • 函数定义:
exec(object[, globals[, locals]])
  • 参数说明:
  1. object:必选参数,表示需要被指定的Python代码。它必须是字符串或code对象。如果object是一个字符串,该字符串会先被解析为一组Python语句,然后在执行(除非发生语法错误)。如果object是一个code对象,那么它只是被简单的执行。
  2. globals:可选参数,同eval函数
  3. locals:可选参数,同eval函数
  • 返回值:

exec函数的返回值永远为None.

需要说明的是在Python 2中exec不是函数,而是一个内置语句(statement),但是Python 2中有一个execfile()函数。可以理解为Python 3把exec这个statement和execfile()函数的功能够整合到一个新的exec()函数中去了:

  • eval()函数与exec()函数的区别:
  1. eval()函数只能计算单个表达式的值,而exec()函数可以动态运行代码段。
  2. eval()函数可以有返回值,而exec()函数返回值永远为None。
x = 10

def func():
    y = 20
    a = exec('x + y')
    print('a: ', a)
    b = exec('x + y', {'x': 1, 'y': 2})
    print('b: ', b)
    c = exec('x + y', {'x': 1, 'y': 2}, {'y': 3, 'z': 4})
    print('c: ', c)
    d = exec('print(x, y)')
    print('d: ', d)

func()

输出

a:  None
b:  None
c:  None
10 20
d:  None

 

x = 10
expr = """
z = 30
sum = x + y + z
print(sum)
"""
def func():
    y = 20
    exec(expr)
    exec(expr, {'x': 1, 'y': 2})
    exec(expr, {'x': 1, 'y': 2}, {'y': 3, 'z': 4})

func()

输出

60
33
34
  • 对输出结果的解释:

前两个输出跟上面解释的eval函数执行过程一样,不做过多解释。关于最后一个数字34,我们可以看出是:x = 1, y = 3是没有疑问的。关于z为什么还是30而不是4,这其实也很简单,我们只需要在理一下代码执行过程就可以了,其执行过程相当于:

x = 1
y = 2

def func():
    y = 3
    z = 4
    
    z = 30
    sum = x + y + z
    print(sum)

func()

 

十二、元类

1.元类的定义

元类是用来控制类的创建行为(如何创建类的),正如类是创建对象的模板一样,元类是类的类,是类的模版。控制class

元类的实例化的结果为我们用class定义的类,正如类的实例为对象 ( f1对象是Foo类的一个实例Foo类是 type 类的一个实例)

type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象

 

 

2.创建类的方式  (执行类的过程就是执行类体的代码,产生名称空间)

 

准备工作:

 

创建类的三要素:

 

  1 类名

  2 类的父类

 

  3 类体

 

 

方式:

1. 使用class关键字

class Chinese(object):
    country='China'
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def talk(self):
        print('%s is talking' %self.name)

 

 

2. 手动模拟class创建类的过程:

将创建类的步骤拆分开,手动去创建

#类名
class_name='Chinese'
#类的父类
class_bases=(object,)
#类体
class_body="""
country='China'
def __init__(self,name,age):
    self.name=name
    self.age=age
def talk(self):
    print('%s is talking' %self.name)
"""

 

步骤一(先处理类体->名称空间):类体定义的名字都会存放于类的名称空间中(一个局部的名称空间),

我们可以事先定义一个空字典,然后用exec去执行类体的代码(exec产生名称空间的过程与真正的class过程类似,只是后者会将__开头的属性变形),生成类的局部名称空间,即填充字典

class_dic={}
exec(class_body,globals(),class_dic)


print(class_dic)
#{'country': 'China', 'talk': <function talk at 0x101a560c8>, '__init__': <function __init__ at 0x101a56668>}

步骤二:调用元类type(也可以自定义)来产生类Chinense

Foo=type(class_name,class_bases,class_dic) #实例化type得到对象Foo,即我们用class定义的类Foo


print(Foo)
print(type(Foo))
print(isinstance(Foo,type))
'''
<class '__main__.Chinese'>
<class 'type'>
True
'''

我们看到,type 接收三个参数:

  1. 第 1 个参数是字符串 ‘Foo’,表示类名

  2. 第 2 个参数是元组 (object, ),表示所有的父类

  3. 第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法

 

补充:若Foo类有继承,即class Foo(Bar):.... 则等同于type('Foo',(Bar,),{})

      一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类 

                                            ----> 使用metaclass=Mytype使用元类

自定制元类精简版
class Mytype(type):           #自定义元类,写上继承type
    def __init__(self,what,bases=None,dict=None):
        print(what,bases,dict)

    def __call__(self, *args, **kwargs):
        print('--->')
        obj=object.__new__(self)
        self.__init__(obj,*args,**kwargs)
        return obj
class Room(metaclass=Mytype): #继承自定义元类 def __init__(self,name): self.name=name r1=Room('alex') print(r1.__dict__)

 

posted on 2017-06-24 11:10  小待  阅读(79)  评论(0)    收藏  举报