面向对象高阶--方法论(第二部分)

             方法论

绑定方法 & 非绑定方法

在类中定义的函数,总共有两种类型:

  • 绑定方法
  • 非绑定方法

绑定方法(绑定给谁,谁来调用就自动将它本身当作第一个参数传入):

1.绑定到类的方法:用classmethod装饰器装饰的方法

     为类量身定制

     类.boud_method(),自动将类当作第一个参数传入

   (其实对象也可调用,但仍将类当作第一个参数传入)

2.绑定到对象的方法:没有被任何装饰器装饰的方法

    为对象量身定制

    对象.boud_method(),自动将对象当作第一个参数传入

  (属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说)

非绑定方法:用staticmethod装饰器装饰的方法

不与类或对象绑定,类和对象都可以调用,但是没有自动传值功能,仅仅是个函数工具

绑定方法详解:

绑定给类的方法(classmethod)

classmethod是给类用的,绑定到类,类在使用时会将类本身当做参数传给类方法的第一个参数(对象调用也一样)

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

    @classmethod
    def tell(self,name):

        print('My name is %s '% self.name)

print('未装饰前:',Foo.tell)
# 未装饰前: <function Foo.tell at 0x000001C3A1422A60>

print('装饰后:',Foo.tell)
# 装饰后: <bound method Foo.tell of <class '__main__.Foo'>>

# 类调用
Foo.tell()  # <class '__main__.Foo'>

# 对象调用   
f =Foo('Jack')
f.tell()   # <class '__main__.Foo'>

非绑定方法

在类内部用statimethod装饰器装饰的函数即为非绑定方法,就是普通函数

statimethod 不与类或对象绑定,但是两者均可调用,没有自动传值效果

import hashlib,time
class MySQL:
    def __init__(self,host,port):
        self.host = host
        self.port = port
        self.id = self.creat_id()

    @staticmethod
    def creat_id():   # 此时为函数工具
        m = hashlib.md5(str(time.time()).encode('utf-8'))
        return m.hexdigest()

print(MySQL.creat_id)     # <function MySQL.creat_id at 0x000001AB908C2D90>

m = MySQL('127.01.11.01',6868)
print(m.creat_id)        # <function MySQL.creat_id at 0x000001AB908C2D90>

# 查看结果均为普通函数

 适用场景

import settings
import hashlib,time

class People:
    def __init__(self,name,age,sex):
        self.id =self.creat_id()
        self.name =name
        self.age =age
        self.sex =sex

    def tell_info(self):    #  绑定到对象的方法
        print('Name:%s Age:%s Sex:%s'%(self.name,self.age,self.sex))

    @classmethod      # 绑定到类的方法
    def from_conf(cls):
        obj = cls(
            settings.name,
            settings.age,
            settings.sex
        )
        return obj
    
    @staticmethod   # 非绑定方法
    def creat_id():
        m = hashlib.md5(str(time.time()).encode('utf-8'))

        return m.hexdigest()


p =People('林采儿',18,'gril')
# 绑定给对象,就应该由对象调用,并自动将对象作为第一个参数传入
p.tell_info()    # Name:林采儿 Age:18 Sex:gril

p = People.from_conf()   # from_conf(People)
# 绑定给类,就应该由类调用,并自动将类作为第一个参数传入
p.tell_info()        # Name:林采儿 Age:18 Sex:girl

# 非绑定方法 类 和对象均可调用,但没有自动传值
p = People('林采儿',18,'gril')
print(p.id)       # 17078cec4cc907867984221bdcb6dfcf

内置方法

一 isinstance(obj,cls)和issubclass(sub,super)

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

class Foo:
    pass
obj = Foo()
print(isinstance(obj,Foo))   # True

issubclass(sub, super): 检查sub类是否是 super 类的派生类

class Foo:
    pass
class Bar(Foo):
    pass

print(issubclass(Bar,Foo))   # True

二.反射

1.什么是反射

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

2.python面向 对象中的反射:通过字符串的形式操作对象相关的属性。python内一切皆对象(都可以使用反射)

python中 共有四种可以实现自省函数,以下方法适用于类的对象

  1. hasattr(object,name):判断object里是否有属性name
  2. getattr(object,name,default=None):获取object里的属性name的内容
  3. setattr(object,name,arge):设置object里的属性:name = arge
  4. delattr(object,name):删除object里的属性name

方法演示:

class People:
    country = 'china'
    def __init__(self,name,age):
        self.name =name
        self.age = age

    def tell_info(self):
        print('Name:%s Age:%s'%(self.name,self.age))

p =People('萧薰儿',18)

# 检测属性 查看p.__dict__里的属性是否有name
print(hasattr(p,'name'))  # True
print(hasattr(p,'tell_info'))  # True
print(hasattr(People,'country'))  # True

# 获取属性
print(getattr(p,'name'))   # 萧薰儿
print(getattr(p,'tell_info'))   # <bound method People.tell_info of <__main__.People object at 0x000001411BF7FC88>>

# 设置属性
setattr(p,'name','legend')
print(p.name)       # legend

# 删除属性
delattr(p,'name')
print(p.__dict__)  #{'age': 18}

 三. item:将对象变成字典的形式,可以进行字典式的操作

class People:
    country = 'china'
    def __init__(self,name,age):
        self.name =name
        self.age = age

    def __getitem__(self, item): # item= ’name‘
        print('get',item)

        return self.__dict__.get(item)  # 通过对象属性空间获取对应值

    def __setitem__(self, key, value):

        print('set',key,value)
        self.__dict__[key] = value

    def __delitem__(self, key):
        print('del',key)

        del self.__dict__[key]
        
# 查看属性
p = People('萧薰儿',18)
print(p['name'])    # # 执行此句时 会触发方法__getitem__: p.name

# 获取属性
p['age'] = 19      # # 执行此句时 会触发方法__setitem__ : p.age =19
print(p.__dict__)   # {'name': '萧薰儿', 'age': 19}

# 删除属性
del p['age']    # 执行此句时 会触发方法__delitem__ : del p.age
print(p.__dict__)   # {'name': '萧薰儿'}

四.__str__ 、__repr__、__format__:

改变对象的字符串显示__str__,__repr__
自定制格式化字符串__format__

format_dict = {
    'nat':'{obj.name}-{obj.addr}-{obj.type}',
    'tna':'{obj.type}:{obj.name}:{obj.addr}',
    'tan':'{obj.type}/{obj.addr}/{obj.name}'
}

class School:
    def __init__(self,name,addr,type):
        self.name =name
        self.addr =addr
        self.type =type

    def __str__(self):
        return '<%s,%s>'%(self.name,self.addr)

    def __repr__(self):
        return 'shcool(%s,%s)'%(self.name,self.addr)

    def __format__(self, format_spec):
        if not format_spec or format_spec not in format_dict: # 当format_spec为空或者不在format_dict里时
            format_spec ='nat'                                # 将格式设为nat
        fmt =format_dict[format_spec]
        print(fmt)
        return fmt.format(obj=self)

s1 = School('蓝翔','山东','私立')
# print('from rapr:',repr(s1))     # 触发方法__repr__: from rapr: shcool(蓝翔,山东)
# print('from str:',str(s1))      # 触发方法__str__:  from str: <蓝翔,山东>
# print(s1)  # 打印对象 触发方法__str__: <蓝翔,山东>

'''
str函数或者print函数 --> obj.__str__()  
repr函数或交互解释器 --> obj.__str__()
如果str方法没有定义,那么就会使用__repr__方法来代替
注意:这两个方法的返回值必须会字符串,否则会报错(TypeError: __str__ returned non-string (type NoneType))
'''
print(format(s1,'nat'))
print(format(s1,'tan'))
print(format(s1,'tna'))
print(format(s1,'sdfsfd'))
print(format(s1))

输出:
{obj.name}-{obj.addr}-{obj.type}
蓝翔-山东-私立
{obj.type}/{obj.addr}/{obj.name}
私立/山东/蓝翔
{obj.type}:{obj.name}:{obj.addr}
私立:蓝翔:山东
{obj.name}-{obj.addr}-{obj.type}
蓝翔-山东-私立
{obj.name}-{obj.addr}-{obj.type}
蓝翔-山东-私立

五.__next__和__iter__实现迭代器协议

示例:

class Foo:
    def __init__(self,x):
        self.x =x
    def __iter__(self):
        return self
    def __next__(self):
        n =self.x
        self.x +=1
        return self.x

f= Foo(3)
for i in f:
    print(i)

加上范围

class Foo:
    def __init__(self,start,stop):
        self.num =start
        self.stop =stop

    def __iter__(self):
        return self

    def __next__(self):
        if self.num>self.stop:   
            raise StopIteration
        n =self.num
        self.num +=1
        return n

f= Foo(1,6)

from collections import Iterable,Iterator
print(isinstance(f,Iterator))   # 判断f是否为迭代器 :True

for i in f:
    print(i)
输出:
1
2
3
4
5
6

模仿 range 方法

class Range:
    def __init__(self,x):
        self.x =x
        self.min =0  # 设定最小值为0
        self.max = self.x-1   # 设置最大值为x-1

    def __iter__(self):
        return self

    def __next__(self):
        if self.min >self.max:
            raise StopIteration
        n =self.min
        self.min +=1
        return n

for i in Range(5):
    print(i)
输出:
0
1
2
3
4

斐波那契数列

class Foo:
    def __init__(self,x):
        self.x =x
        self.a =0
        self.b=1

    def __iter__(self):
        return self

    def __next__(self):
        if self.a > self.x:
            raise StopIteration
        self.b =self.a+self.b
        if self.a ==0:
            self.a =2
        else:
            self.a += 1
        return self.b

f = Foo(5)
for i in f:
    print(i)
输出:
1
3
6
10
15

六.__doc__:类的描述信息

class Foo:
    '''
    描述信息
    '''
    pass
print(Foo.__doc__)   # 描述信息

该属性无法被继承

class Foo:
    '''
    描述信息
    '''
    pass

class Bar(Foo):
    pass

print(Foo.__doc__)   # 描述信息
print(Bar.__doc__)   # None

七.__del__

析构方法,当对象在内存中被释放时,自动触发执行。

注:如果产生的对象仅仅只是python程序级别的(用户级),那么无需定义__del__,如果产生的对象的同时还会向操作系统发起系统调用,即一个对象有用户级与内核级两种资源,比如(打开一个文件,创建一个数据库链接),则必须在清除对象的同时回收系统资源,这就用到了__del__。

示例:

class Foo:

    def __del__(self):
        print('执行此处')

f1=Foo()
del f1
print('------->')

#输出执行此处
------->

典型的应用场景:

创建数据库类,用该类实例化出数据库链接对象,对象本身是存放于用户空间内存中,而链接则是由操作系统管理的,存放于内核空间内存中

当程序结束时,python只会回收自己的内存空间,即用户态内存,而操作系统的资源则没有被回收,这就需要我们定制__del__,在对象被删除前向操作系统发起关闭数据库链接的系统调用,回收资源

这与文件处理是一个道理:

f=open('info.txt') #做了两件事,在用户空间拿到一个f变量,在操作系统内核空间打开一个文件
del f #只回收用户空间的f,操作系统的文件还处于打开状态

#所以我们应该在del f之前保证f.close()执行,即便是没有del,程序执行完毕也会自动del清理资源,于是文件操作的正确用法应该是
f=open('a.txt')
读写...
f.close()
很多情况下大家都容易忽略f.close,这就用到了with上下文管理

八.__enter__和__exit__

在学习文件操作时,除了常规的打开方式外,还有一种方式:

With open('test.txt') as f:
    代码块

上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法

上下文管理协议

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

    def __enter__(self):
        print('出现with语句,对象的方法__enter__就会被触发,有返回值的话就把返回值赋予变量f')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('with 语句代码块执行完毕后,执行此处!')

with open('test.txt') as f:
    print('执行with语句代码块')
    print(f.name)  # test.txt
    print(f)   # <_io.TextIOWrapper name='test.txt' mode='r' encoding='cp936'>

方法__enter__中的三个参数分别代表异常类型、异常值和追溯信息。with语句的代码块出现问题,则with后的代码都无法执行

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

    def __enter__(self):
        print('出现with语句,对象的就会被触发,有返回值的话就把返回值赋予变量f')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('with 语句代码块执行完毕后,执行此处!')
        print(exc_type)
        print(exc_val)
        print(exc_tb)

with open('test.txt') as f:
    print('执行with语句代码块')
    # print(f.name)  # test.txt
    # print(f)   # <_io.TextIOWrapper name='test.txt' mode='r' encoding='cp936'>
    raise AttributeError('啦啦')   # 触发异常,则代码终止

print('after with')

输出:
执行with语句代码块
Traceback (most recent call last):
AttributeError: 啦啦

如果__exit__返回值为True,那么异常将会清空,with之后 的代码继续执行

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

    def __enter__(self):
        print('出现with语句,对象的就会被触发,有返回值的话就把返回值赋予变量f')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('with 语句代码块执行完毕后,执行此处!')
        print(exc_type)
        print(exc_val)
        print(exc_tb)
        return True       # 返回值为True  异常清空继续执行后面代码
with open('test.txt') as f:
    print('执行with语句代码块')>
    raise AttributeError('啦啦')   # 触发异常,则代码终止

print('after with')

模拟Open

class Open:
    def __init__(self, filepath, mode='r', encoding='utf-8'):
        self.filepath = filepath
        self.mode = mode
        self.encoding = encoding

    def __enter__(self):
        self.f=open(self.filepath,mode=self.mode,encoding=self.encoding)
        return self.f

    def __exit__(self, exc_type, exc_val, exc_tb):   # 处理异常

        self.f.close()
        return True

    def __getattr__(self, item):
        return getattr(self.f,item)

with Open('test2.txt','w') as f:
    f.write('hello')
    f.age # 抛出异常,交给__exit__处理
print('after with')

九.__module__和__class__

__module__ 表示当前操作的对象在那个模块

__class__ 表示当前操作的对象的类是什么

setting.py

class Test:
    def __init__(self):
        pass

导入setting,py里的类Test

from sht.settings import Test
obj = Test()
print(obj.__class__)      # 当前操作的类
print(obj.__module__)   # 当前操作的模块
输出:
<class 'sht.settings.Test'>
sht.settings

十.__call__

方法call仅当对象后面加括号,才会被触发执行

注:构造方法的执行是由创建对象触发的,即:对象=类(),而__call__ 方法是由对象后面加括号触发的。即:对象()或  类()

class Test:
    def __init__(self,name,age):
        self.name = name
        self.age =age

    def __call__(self, *args, **kwargs):
        print(self)
        print(args)
        print(kwargs)

t =Test('Jack',18)   # 触发__init__方法
t()   # 触发__call__方法

输出:   # 由于调用t时未传值,arge,kwargs 所以两者均为空
<__main__.Test object at 0x000001A9538DFBE0>
()
{}

元类

一.相关知识

exec  三个参数

参数一:字符串形式的命令
参数二:全局作用域(字典形式),如果不指定,默认为globals()
参数三:局部作用域(字典形式),如果不指定,默认为locals()

exec的使用

可一把exec命令的执行当做一个函数的执行,会将执行期间产生的名字放在局部名车空间

g ={
    'x':1,
    'y':2,
}
l ={}
exec('''
global x,m  # 声明调用全局变量
x= 6  # 修改
m =8  # 新建

s =666
''',g,l)

print(g)   #  {'x': 6, 'y': 2 ,'s':666}
print(l)   # {'s': 666}

python中一切皆对象,类本身也是一个对象  ,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象值得是类,而非类的实例)。因而我们可以把类当做一个对象去使用同样满足第一类对象的概念:

  • 可以把类赋值给一个变量
  • 可以把类作为函数参数进行传入
  • 可以把类当做返回值
  • 可以运行时动态创建参数
class Foo:
    def __init__(self):
        pass
    
f= Foo()  # f 为F实例化对象 即将类Foo赋值给变量f

从代码可以看出f 为F实例化对象,而类Foo本身也是 对象,那类Foo是由哪个类产生的呢?

# 可以用函数type来查看类型,也可以查看类和对象

print(type(Foo))    #  <class 'type'>
print(type(f))      # <class '__main__.Foo'>

二.什么是元类

元类是类的模板

元类是用来控制如何创建类的,正如类创建对象一样。而元类的主要目的是用来控制类的创建行为

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

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

三.创建类的两种方式

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创建类的过程:将创建类的步骤拆分开,手动去创建

#准备工作:

#创建类主要分为三部分

  1 类名

  2 类的父类

  3 类体

#类名
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_dict ={}
exec(class_body,globals(),class_dict)
print(class_dict)
输出:
{'country': 'China', '__init__': <function __init__ at 0x00000236978E29D8>, 'talk': <function talk at 0x00000236978E2A60>}

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

Foo = type(class_name,class_bases,class_dict)  # 实例化type得到对象Foo
print(Foo)
print(type(Foo))
print(isinstance(Foo,type))
输出:
<class '__main__.Chinese'>
<class 'type'>
True

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

  • 第 1 个参数是字符串 ‘Foo’,表示类名
  • 第 2 个参数是元组 (object, ),表示所有的父类
  • 第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法

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

四.自定义元类控制类的行为

一个类没有声明自己的元类,那么就默认它的元类是type。除了使用元类type,用户也可通过继承type来自定义元类,通过以下步骤来看下实现过程

#知识储备:
    #产生的新对象 = object.__new__(继承object类的子类)
#:如果说People=type(类名,类的父类们,类的名称空间),那么我们定义元类如下,来控制类的创建
class Mymeta(type):   # 继承默认元类的一堆属性
    def __init__(self,class_name,class_bases,class_dict):
        if  '__doc__' not in class_dict or not class_dict.get('__doc__').strip():
            raise TypeError('必须为类写文档注释')
        if not class_name.istitle():
            raise TypeError('首字母必须大写')

        super().__init__(class_name,class_bases,class_dict)
    def __call__(self, *args, **kwargs):
        # print(self)
        # print(args)
        # print(kwargs)
        # 1.调用__new__方法新建对象obj
        obj =object.__new__(self)
        # 2.初始化obj
        self.__init__(self,*args, **kwargs)
        # 3.返回obj
        return obj

class Peopel(object,metaclass=Mymeta):

    country = 'china'
    def __init__(self,name,age):
        self.name =name
        self.age =age

    def talk(self):
        print('%s is talking'%self.name)

p =Peopel('Jack',18)
步骤一
#步骤二:如果我们想控制类实例化的行为,那么需要先储备知识__call__方法的使用
class People(object,metaclass=type):
    def __init__(self,name,age):
        self.name=name
        self.age=age

    def __call__(self, *args, **kwargs):
        print(self,args,kwargs)

# 调用类People,并不会出发__call__
obj=People('egon',18)
obj(1,2,3,a=1,b=2,c=3)
输出:
<__main__.People object at 0x000001DE5631FBE0> (1, 2, 3) {'a': 1, 'b': 2, 'c': 3}
#总结:如果说类People是元类type的实例,那么在元类type内肯定也有一个__call__,会在调用People('egon',18)时触发执行,然后返回一个初始化好了的对象obj
步骤二
#步骤三:自定义元类,控制类的调用(即实例化)的过程
class Mymeta(type): #继承默认元类的一堆属性
    def __init__(self,class_name,class_bases,class_dic):
        if not class_name.istitle():
            raise TypeError('类名首字母必须大写')

        super(Mymeta,self).__init__(class_name,class_bases,class_dic)

    def __call__(self, *args, **kwargs):
        #self=People
        print(self,args,kwargs) #<class '__main__.People'> ('egon', 18) {}

        #1、实例化People,产生空对象obj
        obj=object.__new__(self)


        #2、调用People下的函数__init__,初始化obj
        self.__init__(obj,*args,**kwargs)


        #3、返回初始化好了的obj
        return obj

class People(object,metaclass=Mymeta):
    country='China'

    def __init__(self,name,age):
        self.name=name
        self.age=age

    def talk(self):
        print('%s is talking' %self.name)

obj=People('egon',18)
print(obj.__dict__) #{'name': 'egon', 'age': 18}
步骤三
class Mymeta(type): #继承默认元类的一堆属性
    def __init__(self,class_name,class_bases,class_dic):
        if not class_name.istitle():
            raise TypeError('类名首字母必须大写')

        super(Mymeta,self).__init__(class_name,class_bases,class_dic)

    def __call__(self, *args, **kwargs):
        #self=People
        print(self,args,kwargs) #<class '__main__.People'> ('egon', 18) {}

        #1、调用self,即People下的函数__new__,在该函数内完成:1、产生空对象obj 2、初始化 3、返回obj
        obj=self.__new__(self,*args,**kwargs)

        #2、一定记得返回obj,因为实例化People(...)取得就是__call__的返回值
        return obj

class People(object,metaclass=Mymeta):
    country='China'

    def __init__(self,name,age):
        self.name=name
        self.age=age

    def talk(self):
        print('%s is talking' %self.name)

    def __new__(cls, *args, **kwargs):
        obj=object.__new__(cls)
        cls.__init__(obj,*args,**kwargs)
        return obj

obj=People('egon',18)
print(obj.__dict__) #{'name': 'egon', 'age': 18}
步骤四
#基于元类实现单例模式,比如数据库对象,实例化时参数都一样,就没必要重复产生对象,浪费内存
class Mysql:
    __instance=None
    def __init__(self,host='127.0.0.1',port='3306'):
        self.host=host
        self.port=port

    @classmethod
    def singleton(cls,*args,**kwargs):
        if not cls.__instance:
            cls.__instance=cls(*args,**kwargs)
        return cls.__instance


obj1=Mysql()
obj2=Mysql()
print(obj1 is obj2) #False

obj3=Mysql.singleton()
obj4=Mysql.singleton()
print(obj3 is obj4) #True
步骤五
#应用:定制元类实现单例模式
class Mymeta(type):
    def __init__(self,name,bases,dic): #定义类Mysql时就触发
        self.__instance=None
        super().__init__(name,bases,dic)

    def __call__(self, *args, **kwargs): #Mysql(...)时触发

        if not self.__instance:
            self.__instance=object.__new__(self) #产生对象
            self.__init__(self.__instance,*args,**kwargs) #初始化对象
            #上述两步可以合成下面一步
            # self.__instance=super().__call__(*args,**kwargs)

        return self.__instance
class Mysql(metaclass=Mymeta):
    def __init__(self,host='127.0.0.1',port='3306'):
        self.host=host
        self.port=port


obj1=Mysql()
obj2=Mysql()

print(obj1 is obj2)
实现单例模式

 

posted @ 2018-08-06 18:05  繁华无殇  阅读(224)  评论(0编辑  收藏  举报