Python新式类 单例模式与作用域

Posted 诚实善良小郎君

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python新式类 单例模式与作用域相关的知识,希望对你有一定的参考价值。

1 新式类与旧式类

新式类拥有经典类的全部特性之外,还有一些新的特性,比如 __init__发生变化,新增了静态方法__new__,python3目前都采用新式类,新式类是广度优先,旧式类是深度优先

#新式类
class C(object):
    pass
#经典类
class B:
    pass
(1)内置的object对象

1. __new__,__init__方法
这两个方法是用来创建object的子类对象,静态方法__new__()用来创建类的实例,然后再调用
__init__()来初始化实例。
 
2. __delattr__, __getattribute__, __setattr__方法
对象使用这些方法来处理属性的访问
 
3. __hash__, __repr__, __str__方法
print(someobj)会调用someobj.__str__(), 如果__str__没有定义,则会调用someobj.__repr__(),
 
__str__()和__repr__()的区别:
默认的实现是没有任何作用的
__repr__的目标是对象信息唯一性
__str__的目标是对象信息的可读性
容器对象的__str__一般使用的是对象元素的__repr__
如果重新定义了__repr__,而没有定义__str__,则默认调用__str__时,调用的是__repr__
也就是说好的编程习惯是每一个类都需要重写一个__repr__方法,用于提供对象的可读信息,
而重写__str__方法是可选的。实现__str__方法,一般是需要更加好看的打印效果,比如你要制作
一个报表的时候等。
(2)类的方法

静态方法

静态方法可以被类或者实例调用,它没有常规方法的行为(比如绑定,非绑定,默认的第一个self参数),当有一
堆函数仅仅是为了一个类写的时候,采用静态方法声明在类的内部,可以提供行为上的一致性。
使用装饰符@staticmethod进行创建 

类方法

也是可以通过类和它的实例进行调用,不过它是有默认第一个参数,叫做是类对象,一般被
命名为cls,当然你也可以命名为其它名字,这样就你可以调用类对象的一些操作,
代码如下,使用装饰符@classmethod创建:

新式类(new-style-class)

__init__方法: 类的初始化方法

__new__静态方法

新式类都有一个__new__的静态方法,它的原型是object.__new__(cls[, ...])

cls是一个类对象,当你调用C(*args, **kargs)来创建一个类C的实例时,python的内部调用是

C.__new__(C, *args, **kargs),然后返回值是类C的实例c,在确认

c是C的实例后,python再调用C.__init__(c, *args, **kargs)来初始化实例c。

所以调用一个实例c = C(2),实际执行的代码为:

c = C.__new__(C, 2)
if isinstance(c, C):
    C.__init__(c, 23)#__init__第一个参数要为实例对象
    
class Singleton(object):
    _singletons = {}
    def __new__(cls):
        if not cls._singletons.has_key(cls):            #若还没有任何实例
            cls._singletons[cls] = object.__new__(cls)  #生成一个实例
        return cls._singletons[cls]                     #返回这个实例
    a = Singleton()
    b = Singleton()
    id(a)  #35966666
    id(b)  #35966666       
 #注:单例模式 ,两个实例指向同一个内存地址 
(3)新式类实例

新式类的实例也具有新的特性。比如它拥有Property功能,该功能会对属性的访问方式产生影响;还有__slots__新属性,该属性会对生成子类实例产生影响;还添加了一个新的方法__getattribute__,比原有的__getattr__更加通用。

__slots__属性

通常每一个实例x都会有一个__dict__属性,用来记录实例中所有的属性和方法,也是通过这个字典,

可以让实例绑定任意的属性。而__slots__属性作用就是,当类C有比较少的变量,而且拥有__slots__属性时,

类C的实例 就没有__dict__属性,而是把变量的值存在一个固定的地方。如果试图访问一个__slots__中没有

的属性,实例就会报错。这样操作有什么好处呢?__slots__属性虽然令实例失去了绑定任意属性的便利,

但是因为每一个实例没有__dict__属性,却能有效节省每一个实例的内存消耗,有利于生成小而精

干的实例。

定义__slots__属性

class A(object):
    def __init__(self):
        self.x = 1
        self.y = 2
        __slots__ = (‘x‘,‘y‘)
a = A()
a.z = 3
a.u = 4  #都会报错,不能对实例新增属性,__dict__字典集没有任何改变

class A(object):
    def __init__(self):
        self.x = 1
        self.y = 2
a = A()
a.x = 3  #不会报错,在__dict__字典字典集会新增‘x‘属性

使用时__slots__时需要注意的几点:

1.  当一个类的父类没有定义__slots__属性,父类中的__dict__属性总是可以访问到的,所以只在子
类中定义__slots__属性,而不在父类中定义是没有意义的。 
2. 如果定义了__slots__属性,还是想在之后添加新的变量,就需要把‘__dict__‘字符串添加到__slots__的
元组里。
3. 定义了__slots__属性,还会消失的一个属性是__weakref__,这样就不支持实例的weak reference,
如果还是想用这个功能,同样,可以把‘__weakref__‘字符串添加到元组里。
4. __slots__功能是通过descriptor实现的,会为每一个变量创建一个descriptor。
5. __slots__的功能只影响定义它的类,因此,子类需要重新定义__slots__才能有它的功能。
__getattribute__方法

对新式类的实例来说,所有属性和方法的访问操作都是通过__getattribute__完成,这是由object基类实现的。如果有特殊的要求,可以重载__getattribute__方法.

2 __init____new__区别

  1. __new__是一个静态方法,而__init__是一个实例方法.
  2. __new__方法会返回一个创建的实例,而__init__什么都不返回.
  3. 只有在__new__返回一个cls的实例时后面的__init__才能被调用.
  4. 当创建一个新实例时调用__new__,初始化一个实例时用__init__.

__metaclass__是创建类时起作用.所以我们可以分别使用__metaclass__,__new____init__来分别在类创建,实例创建和实例初始化的时候做一些操作

3 单例模式

? 单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源,系统中存在多个该类的实例对象,而这样会严重浪费内存资源 如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。

__new__()__init__()之前被调用,用于生成实例对象。利用这个方法和类的属性的特点可以实现设计模式的单例模式。单例模式是指创建唯一对象,单例模式设计的类只能实例一次

一般python中可以采用以下方法实现单例模式

  • 使用模块(import导入)
  • 使用 __new__
  • 使用装饰器(decorator)
  • 使用元类(metaclass)

(1)使用__new__方法

class Singleton(object):
    def __new__(cls, *args, **kw):
        if not hasattr(cls, ‘_instance‘): #如果该类是否含有实例
            orig = super(Singleton, cls)
            cls._instance = orig.__new__(cls, *args, **kw)
        return cls._instance

class MyClass(Singleton):
    a = 1

#如果 cls._instance 为 None 则创建实例,否则直接返回 cls._instance。

(2)共享属性(使用metaclass)

元类(metaclass)可以控制类的创建过程,它主要做三件事:

  • 拦截类的创建
  • 修改类的定义
  • 返回修改后的类

创建实例时把所有实例的__dict__指向同一个字典,这样它们具有相同的属性和方法.

class Borg(object):
    _state = {}
    def __new__(cls, *args, **kw):
        ob = super(Borg, cls).__new__(cls, *args, **kw)
        ob.__dict__ = cls._state
        return ob

class MyClass2(Borg):
    a = 1

(3) 装饰器版本

def singleton(cls):
    instances = {}
    def getinstance(*args, **kw):
        if cls not in instances:
            instances[cls] = cls(*args, **kw)
        return instances[cls]
    return getinstance

@singleton
class MyClass:
  ...

(4) import 方法(使用模块)

作为python的模块是天然的单例模式

# mysingleton.py
class My_Singleton(object):
    def foo(self):
        pass
my_singleton = My_Singleton()

# to use
from mysingleton import my_singleton #将类进行导入使用
my_singleton.foo()

4 Python作用域

python的作用域分全局和局部,python变量的作用域分为以下四类:

L(local) 局部作用域
E(Enclosing) 闭包函数外的函数中
G(Global) 全局作用域
B(Built-in) 内建作用域

(1) 局部作用域

在了解局部作用域之前,先了解下块级作用域的概念

#块级作用域
if 1 == 1:
    name = "lol"
print(name) 

for i in range(10):
    age = i
print(age)

#输出:
C:/Users/L/PycharmProjects/s14/preview/Day8/作用域/test.py
lol
9

可以发现python代码运行ok,为什么外部可以调用内部的变量呢? 因为在python中没有块级作用域的概念,代码块里的变量,外部可以调用,所以可运行成功, 也就是说,类似条件判断(if…..else)、循环语句(for x in data)、异常捕捉(try…catch)等的变量是可以全局使用的 .但是不区分作用域明显是不行的,因此python引入局部作用域

#局部作用域
def  func():
    name = "lol"
func()  #调用函数
print(name)
#输出
Traceback (most recent call last):
  File "C:/Users/L/PycharmProjects/s14/preview/Day8/作用域/test.py", line 23, in <module>
    print(name)
NameError: name ‘name‘ is not defined

即使执行了一下函数,name的作用域也只是在函数内部,外部依然无法进行调用 ,因此函数可以产生局部作用域,在python中模块(module),类(class)、函数(def、lambda)会产生新的作用域 .

(2) 作用域链

#作用域链
name = "张三"
def func1():
    name = "李四"
    def func2():
        name = "王五"
        print(name)
    func2()
func1()
#输出
"王五"

func1()调用函数执行变量赋值操作,当调用func2()时 , print(name)中name属性会先从局部作用域开始寻找,由内至外,最先找到的当然是 ‘王五‘.Python中有作用域链,变量会由内到外找,先去自己作用域去找,自己没有再去上级去找,直到找不到报错 .

(3) 全局作用域与内建作用域

x = int(2.9)  # 内建作用域
num = 0  # 全局作用域
def outer():
    num2 = 1  # 闭包函数外的函数中
    def inner():
        num3 = 2  # 局部作用域

(4) global关键字

全局变量是指在函数外的变量,可以在程序全局使用,局部变量是指定义在函数内的变量,只能在函数内被声明使用若内部作用域的想要修改外部作用域的变量,就要使用global关键字

a = 100

def demo():
    global a
    a = 123
    print(a)
demo()
print(a)

#运行结果是
123
123

本来运行结果应该是123,100,但是因为global声明a为全局作用域,因此在执行赋值后的a指向123,因此两次打印的都是123.

总结下:

? Python 中,一个变量的作用域总是由在代码中被赋值的地方所决定的。当 Python 遇到一个变量的话他会按照这样的顺序进行搜索:本地作用域(Local)→当前作用域被嵌入的本地作用域(Enclosing locals)→全局/模块作用域(Global)→内置作用域(Built-in)




以上是关于Python新式类 单例模式与作用域的主要内容,如果未能解决你的问题,请参考以下文章

Python new 类方法和 init 实例方法以及单例模式的简单讨论

Spring Controller单例与线程安全那些事儿

python设计模式之单例

类与对象的属性

八类

Spring Bean 作用域和生命周期