Python面向对象进阶

一 类的成员

类的成员可以分为三大类:字段、方法和属性

类的成员

相关概念描述:

类(Class):用来描述具有相同的属性和方法的对象的集合,它定义了该集合中每个对象所共有的属性和方法,对象是类的实例

类变量(静态字段):类变量在整个实例化的对象中是公用的,类变量定义在类中且在函数体之外,类变量通常不作为实例变量使用,它属于类

实例变量(普通字段):实例化对象时创建的变量,它属于对象

数据成员:类变量或实例变量用于处理类及其实例对象的相关的数据

实例化:创建一个类的实例,类的具体对象

方法:类中定义的函数

对象:通过类定义的数据结构实例,对象包括两个数据成员(类变量和实例变量)和方法

注意:所有成员中,只有普通字段的内容保存对象中,即:根据此类创建了多少对象,在内存中就有多少个普通字段。而其他的成员,则都是保存在类中,即:无论对象的多少,在内存中只创建一份

二 字段

字段包括:普通字段和静态字段,他们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同

  • 普通字段属于对象
  • 静态字段属于
class Student:

    # 静态字段
    school = '社大'

    def __init__(self, name, age):

        # 普通字段
        self.name = name
        self.age = age


# 直接访问普通字段
obj = Student('zhangsan',18)
print(obj.name)

# 直接访问静态字段
print(Student.school)

由上述代码可以看出普通字段需要通过对象来访问、静态字段通过类访问,在使用上可以看出普通字段和静态字段的归属是不同的。其在内容的存储方式类似如下图:

image

由上图可是:

  • 静态字段在内存中只保存一份
  • 普通字段在每个对象中都要保存一份

应用场景: 通过类创建对象时,如果每个对象都具有相同的字段,那么就使用静态字段

三 方法

方法包括:普通方法、静态方法和类方法,三种方法在内存中都归属于类,区别在于调用方式不同

普通方法:对象调用;至少一个self参数;执行普通方法时,自动将调用该方法的对象赋值给self

类方法:调用; 至少一个cls参数;执行类方法时,自动将调用该方法的复制给cls

静态方法:调用;无默认参数

class Foo:

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

    def func(self):
        """ 定义普通方法,至少有一个self参数 """
        print('普通方法')

    @classmethod
    def class_func(cls):
        """ 定义类方法,至少有一个cls参数 """
        print('类方法')

    @staticmethod
    def static_func():
        """ 定义静态方法 ,无默认参数"""
        print('静态方法')

f = Foo()

# 调用普通方法
f.func()

# 调用类方法
Foo.class_func()      # 将类本身当做参数传给类方法的第一个参数
f.class_func()        # 对象调用时也会将类当作第一个参数传入

# 调用静态方法
Foo.static_func()       # 类或对象都可以调用,没有自动传值效果
f.static_func()         # 类或对象都可以调用,没有自动传值效果

有时我们会将普通方法(绑定到对象)和类方法(绑定到类)称为绑定方法,其会自动传值;静态方法称为非绑定方法,不会自动传值

注意:对于所有的方法而言,均属于类(非对象)中,所以在内存中只保存一份

四 属性 

上面我们已经了解了Python类中的方法,那么属性就非常简单了,因为Python中的属性其实是普通方法的变种

4.1 属性的使用

# ############### 定义 ###############
class Foo:

    def __init__(self):
        pass
    
    def func(self):
        pass

    # 定义属性
    @property
    def prop(self):
        return '属性值'


# ############### 调用 ###############
f = Foo()
f.func()        # 调用方法
f.prop          # 调用属性

由属性的定义和调用要注意以下几点:

  • 定义时,在普通方法的基础上添加 @property 装饰器
  • 定义时,属性仅有一个self参数
  • 调用时,无需括号

注意:

1. 属性存在意义是:访问属性时可以制造出和访问字段完全相同的假象

2. 属性由方法变种而来,如果Python中没有属性,方法完全可以代替其功能

实例:对于网页展示时,每次请求不可能把数据库中的所有内容都显示到当前页面上,而是通过分页的功能局部显示,所以在向数据库中请求数据时就要指定获取从第m条到第n条的所有数据(即:limit m,n)进行显示,这个分页的功能包括:

  • 根据用户请求的当前页和总数据条数计算出 m 和 n
  • 根据m 和 n 去数据库中请求数据
# ############### 定义 ###############
class Pager:

    def __init__(self, current_page, per_items = 10):
        # 用户当前请求的页码
        self.current_page = current_page
        # 每页默认显示数据条数
        self.per_items = per_items

    @property
    def start(self):
        val = (self.current_page - 1) * self.per_items
        return val

    @property
    def end(self):
        val = self.current_page * self.per_items
        return val


# ############### 调用 ###############
p = Pager(1)
p.start       # 就是起始值,即:m
p.end         # 就是结束值,即:n
View Code

从上述可见,Python的属性的功能是:属性内部进行一系列的逻辑计算,最终将计算结果返回

4.2 属性的两种定义方式

方式一:装饰器方式(在类的普通方法上应用@property装饰器)

该方式在经典类和新式类上有所不同,具体如下:

经典类:具有一种@property装饰器(如上一步实例)

新式类:具有三种@property装饰器

# ############### 定义 ###############
class Goods(object):

    @property
    def price(self):
        return 'price'

    @price.setter
    def price(self, value):
        print(value)
        return 'price.setter'

    @price.deleter
    def price(self):
        return 'price.deleter'

# ############### 调用 ###############
g = Goods()

g.price          # 自动执行 @property 修饰的 price 方法,并获取方法的返回值
g.price = 100    # 自动执行 @price.setter 修饰的 price 方法,并将123赋值给方法的value参数
g.price          # 自动执行 @price.deleter 修饰的 price 方法

由于新式类中具有三种访问方式,我们可以根据他们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除

class Goods(object):

    def __init__(self, original_price, discount):
        self.original_price = original_price        # 原价
        self.discount = discount                    # 折扣

    @property
    def price(self):
        discount_price = self.original_price * self.discount      # 实际价格 = 原价 * 折扣
        return discount_price

    @price.setter
    def price(self, value):
        self.original_price = value

    @price.deleter
    def price(self):
        del self.original_price

g = Goods(100, 0.8)
print(g.price)          # 获取商品价格
g.price = 200           # 修改商品原价
print(g.price)          # 获取商品价格
del g.price             # 删除商品原价
View Code

方式二:静态字段方式(创建值为property对象的静态字段)

使用该方式创建属性时,经典类和新式类无区别

# ############### 定义 ###############
class Foo:

    def __init__(self):
        pass

    def prop(self):
        return '属性值'

    PROP = property(prop)


# ############### 调用 ###############
f = Foo()
res = f.PROP          # 自动调用prop方法,并获取方法的返回值
print(res)

property的构造方法中有个四个参数

property(fget=None, fset=None, fdel=None, doc=None)
    # 第一个参数是方法名,调用 对象.属性 时自动触发执行方法
    # 第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法
    # 第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法
    # 第四个参数是字符串,调用 对象.属性.__doc__ ,此参数是该属性的描述信息

所以,上面通过装饰器实现的例子,我们可以通过以下代码实现:

class Goods(object):

    def __init__(self, original_price, discount):
        self.original_price = original_price        # 原价
        self.discount = discount                    # 折扣

    def get_price(self):
        discount_price = self.original_price * self.discount      # 实际价格 = 原价 * 折扣
        return discount_price

    def set_price(self, value):
        self.original_price = value

    def del_price(self):
        del self.original_price

    PRICE = property(get_price, set_price, del_price)

g = Goods(100.0, 0.8)
print(g.PRICE)          # 获取商品价格,自动调用第一个参数中定义的方法:get_price()
g.PRICE = 200.0         # 修改商品原价,自动调用第二个参数中定义的方法:set_price()
print(g.PRICE)          # 获取商品价格
del g.PRICE             # 删除商品原价,自动调用第三个参数中定义的方法:del_price()
View Code

注:如果给定 doc 参数,其将成为这个属性值的 docstring,否则 property 函数就会复制 fget 函数的 docstring(如果有的话)

Python WEB框架 Django 的视图中 request.POST 就是使用的静态字段的方式创建的属性,具体代码如下:

class WSGIRequest(http.HttpRequest):
    def __init__(self, environ):
        script_name = get_script_name(environ)
        path_info = get_path_info(environ)
        if not path_info:
            # Sometimes PATH_INFO exists, but is empty (e.g. accessing
            # the SCRIPT_NAME URL without a trailing slash). We really need to
            # operate as if they'd requested '/'. Not amazingly nice to force
            # the path like this, but should be harmless.
            path_info = '/'
        self.environ = environ
        self.path_info = path_info
        self.path = '%s/%s' % (script_name.rstrip('/'), path_info.lstrip('/'))
        self.META = environ
        self.META['PATH_INFO'] = path_info
        self.META['SCRIPT_NAME'] = script_name
        self.method = environ['REQUEST_METHOD'].upper()
        _, content_params = cgi.parse_header(environ.get('CONTENT_TYPE', ''))
        if 'charset' in content_params:
            try:
                codecs.lookup(content_params['charset'])
            except LookupError:
                pass
            else:
                self.encoding = content_params['charset']
        self._post_parse_error = False
        try:
            content_length = int(environ.get('CONTENT_LENGTH'))
        except (ValueError, TypeError):
            content_length = 0
        self._stream = LimitedStream(self.environ['wsgi.input'], content_length)
        self._read_started = False
        self.resolver_match = None

    def _get_scheme(self):
        return self.environ.get('wsgi.url_scheme')

    def _get_request(self):
        warnings.warn('`request.REQUEST` is deprecated, use `request.GET` or '
                      '`request.POST` instead.', RemovedInDjango19Warning, 2)
        if not hasattr(self, '_request'):
            self._request = datastructures.MergeDict(self.POST, self.GET)
        return self._request

    @cached_property
    def GET(self):
        # The WSGI spec says 'QUERY_STRING' may be absent.
        raw_query_string = get_bytes_from_wsgi(self.environ, 'QUERY_STRING', '')
        return http.QueryDict(raw_query_string, encoding=self._encoding)

    # ############### 看这里看这里  ###############
    def _get_post(self):
        if not hasattr(self, '_post'):
            self._load_post_and_files()
        return self._post

    # ############### 看这里看这里  ###############
    def _set_post(self, post):
        self._post = post

    @cached_property
    def COOKIES(self):
        raw_cookie = get_str_from_wsgi(self.environ, 'HTTP_COOKIE', '')
        return http.parse_cookie(raw_cookie)

    def _get_files(self):
        if not hasattr(self, '_files'):
            self._load_post_and_files()
        return self._files

    # ############### 看这里看这里  ###############
    POST = property(_get_post, _set_post)

    FILES = property(_get_files)
    REQUEST = property(_get_request)
View Code

所以,定义属性共有两种方式,分别是【装饰器】和【静态字段】,而【装饰器】方式针对经典类和新式类又有所不同。

五 类成员的修饰符

类的所有成员在上面已经做了详细的介绍,对于每一个类的成员而言都有两种形式:

  • 公有成员,在任何地方都能访问
  • 私有成员,只有在类的内部才能方法

私有成员和公有成员的定义不同:私有成员命名时,前两个字符是下划线。(特殊成员除外,例如:__init__、__call__、__dict__等)

class Foo:

    def __init__(self):
        self.public = '公有字段'
        self.__private = '私有字段'
class People:

    school = '社大'
    __addr = '地球村'               # 变形为self._People__addr

    def __init__(self, name, age, gender):
        self.name = name
        self.__age = age           # 变形为self._People__age
        self.__gender = gender     # 变形为self._People__gender

    def get_school(self):
        return People.school       # 公有成员在类内部可以访问

    def get_addr(self):
        return People.__addr       # 私有成员仅在类内部可以访问

    def get_name(self):
        return self.name            # 公有成员在类内部可以访问

    def get_age(self):
        return self.__age           # 私有成员仅在类内部可以访问

    def get_gender(self):
        return self.__gender        # 私有成员仅在类内部可以访问


class Student(People):

    def show(self):
        print(People.school)            # 公有成员在子类中可以访问
        # print(People.__addr)            # 报错,私有成员仅在类内部可以访问
        print(People._People__addr)     # 非要访问,可以通过_People__addr
        print(self.name)                # 公有成员在子类中可以访问
        # print(self.__age)               # 报错,私有成员仅在类内部可以访问
        print(self._People__age)        # 非要访问,可以通过_People__age,注意不是_Student__age

p = People('joe', 18, 'male')

print(p.school)           # 公有成员在类外部可以访问
# print(p.__addr)         # 报错,私有成员仅在类内部可以访问
print(p.get_addr())       # 类内部开放接口访问
print(p.name)             # 公有成员在类外部可以访问
# print(p.__age)          # 报错,私有成员仅在类内部可以访问
# print(p.__gender)       # 报错,私有成员仅在类内部可以访问
print(p.get_age())        # 类内部开放接口访问
print(p.get_gender())     # 类内部开放接口访问

s = Student('joe1', 18, 'male')
s.show()

# 非要访问私有属性的话,可以通过对象._类__属性名
print(p._People__addr)
print(p._People__age)
print(p._People__gender)
字段示例

在方法、属性的访问于上述方式相似,即:私有成员只能在类内部使用。补充一点:在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的

# 正常情况
class A:

    def func(self):
        print('A.func')

    def test(self):
        self.func()


class B(A):

    def func(self):
        print('B.func')

b = B()
b.test()         # B.func


# 设置为私有方法
class A:

    def __func(self):       # 在定义时就形为_A__func
        print('A.func')

    def test(self):
        self.__func()       # 只会与自己所在的类为准,即调用_A__func


class B(A):

    def __func(self):       # 在定义时变形为_B__func
        print('B.func')

b = B()
b.test()         # A.func
View Code

注意:虽然我们可以通过 【对象._类名__私有字段名 】强制访问私有成员,但不建议这样做

六 类的特殊成员

上文介绍了Python的类成员以及成员修饰符,从而了解到类中有字段、方法和属性三大类成员,并且成员名前如果有两个下划线,则表示该成员是私有成员,私有成员只能由类内部调用。无论人或事物往往都有不按套路出牌的情况,Python的类成员也是如此,存在着一些具有特殊含义的成员,详情如下:

6.1 __init__

构造方法,通过类创建对象时,自动触发执行

class Foo:

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


obj = Foo('joe1991')   # 自动执行类中的 __init__ 方法
View Code

6.2 __doc__

类的描述信息

class Foo:
    """类的描述信息"""

    def __init__(self):
        pass


print(Foo.__doc__)   # 获取类的描述信息
View Code

6.3 __del__

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

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

class Foo:

    def __del__(self):
        pass
View Code

6.4 __module__ & __class__

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

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

class ModuleTest:

    def __init__(self):
        pass
lib/module_test
from lib.module_test import ModuleTest

m = ModuleTest()
print(m.__module__)     # lib.module_test, 输出模块
print(m.__class__)      # <class 'lib.module_test.ModuleTest'>,输出类
.py文件

6.5 __call__

对象后面加括号,触发执行

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

class Foo:
    def __init__(self):
        pass

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


obj = Foo()     # 执行 __init__
obj()           # 执行 __call__
Foo()()         # 执行 __call__
View Code

6.6  __dict__

类或对象中的所有成员

class Student:

    school = '社大'

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

    def func(self, *args, **kwargs):
        print('func')

print(Student.__dict__)
# 结果:{'school': '社大', '__doc__': None, '__init__': <function Student.__init__ at 0x000001CB102C11E0>,
# '__dict__': <attribute '__dict__' of 'Student' objects>,
# '__weakref__': <attribute '__weakref__' of 'Student' objects>,
# 'func': <function Student.func at 0x000001CB102C1268>, '__module__': '__main__'}

s = Student('joe',18)
print(s.__dict__)   # 结果:{'name': 'joe', 'age': 18}
View Code

6.7  __str__&__repr__

如果一个类中定义了__str__方法,那么在打印对象时,默认输出该方法的返回值

class Student:

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

    def __str__(self):
        return '学生姓名:%s,年龄:%s' %(self.name,self.age)

    def __repr__(self):
        return '<学生姓名:%s,年龄:%s>' % (self.name, self.age)

s = Student('joe',18)
print(s)        # 优先执行__str__里面的内容
print(str(s))   # 当执行str(s)时,会去找__str__这个方法,如果找不到的时候,__repr__这个方法替补上去
print(repr(s))  # 直接执行__repr__方法

print('%s'%s)   # 执行的是__str__方法,如果找不到的时候,__repr__这个方法替补上去
print('%r'%s)   # 执行的是__repr__方法
View Code

总结

1.当打印一个对象的时候,如果实现了__str__方法,打印__str__中的返回值

2.当__str__没有被实现的时候,调用__repr__方法

3.当使用字符串格式化的时候,%s和%r会分别调用__str__和__repr__方法

4.不管是在字符串格式化的时候还是在打印对象的时候,__repr__方法都可以作为__str__方法的替补,但反之则不行

5.用于友好的表示对象。如果__str__和__repr__方法你只能实现一个,先实现__repr__

注意:__str__方法和__repr__方法的返回值必须是字符串,否则抛出异常

6.8 __slots__

简单来说就是限制实例自定义属性范围

f = Foo()
f.name = 'joe'
f.age = 18

# f.xxx = 'xxx'     # 报错:AttributeError: 'Foo' object has no attribute 'xxx'
print(f.__slots__)
# print(f.__dict__)   # 报错:AttributeError: 'Foo' object has no attribute '__dict__',统一归__slots__管,节省内存
View Code

注意:__slots__定义的属性仅对当前类实例起作用,对继承的子类不起作用,除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__

6.9 __setattr__,__delattr__,__getattr__

class Student:

    school = '社大'

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

    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__添加/修改属性会触发它的执行
s = Student('joe')          # 会触发__setattr__的执行
print(s.__dict__)
s.age = 18                  # 会触发__setattr__的执行
print(s.__dict__)

# __delattr__删除属性的时候会触发
s.__dict__['xxx'] ='xxx'    # 我们可以直接修改属性字典,来完成添加/修改属性的操作
print(s.__dict__)
del s.xxx                   # 会触发__delattr__的执行
print(s.__dict__)

# __getattr__只有在使用点调用属性且属性不存在的时候才会触发
s.xxx
View Code

6.10 __getattribute__

class Foo:

    def __init__(self,x):
        self.x = x

    def __getattr__(self, item):
        print('访问不存在的属性执行')


f = Foo(10)
print(f.x)
f.xxx       # 不存在的属性访问,触发__getattr__
__getatttr__回顾
class Foo:
    def __init__(self, x):
        self.x = x

    def __getattribute__(self, item):
        print('不管是否存在,都会执行')

f = Foo(10)
f.x         # 不管是否存在,都会执行  注意:此时print(f.x)为None
f.xxx       # 不管是否存在,都会执行
__getatttribute__应用举例
class Foo:

    def __init__(self, x):
        self.x = x

    def __getattr__(self, item):
        print('访问不存在的属性执行')

    def __getattribute__(self, item):
        print('不管是否存在,都会执行')
        raise AttributeError('error')

f = Foo(10)
f.x
f.xxx

# 当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,除非__getattribute__在执行过程中抛出异常AttributeError
两者同时存在

6.11 __getitem,__setitem__,__delitem__

用于索引操作,如字典。以上分别表示获取、设置、删除数据

class Student:

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

    def __getitem__(self, item):
        print(self.__dict__[item])

    def __setitem__(self, key, value):
        self.__dict__[key] = value

    def __delitem__(self, key):
        print('del obj[key]时,我执行')
        self.__dict__.pop(key)

    def __delattr__(self, item):
        print('del obj.key时,我执行')
        self.__dict__.pop(item)


obj = Student('joe')
obj['name']             # 自动触发执行 __getitem__
obj['age'] = '18'       # 自动触发执行 __setitem__
obj['age']              # 自动触发执行 __getitem__
del obj['name']         # 自动触发执行 __delitem__
del obj.age             # 自动触发执行 __delattr__
View Code

6.12 __next__&__iter__

实现迭代器协议

class Foo():

    def __init__(self, num=1):
        self.num = num

    def __iter__(self):
        return self

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

f = Foo()

from collections import Iterable,Iterator
print(isinstance(f,Iterator))   # True
print(isinstance(f,Iterable))   # True

for item in f:
    print(item)
View Code

上面例子中,for … in… 这个语句其实做了两件事:

  • 获得一个可迭代器,即调用了__iter__()函数
  • 循环的过程,循环调用__next__()函数

对于Foo这个类来说,它定义了__iter__和__next__函数,所以是一个可迭代的类,也可以说是一个可迭代的对象(Python中一切皆对象),当然也是迭代器。

class Foo(object):

    def __init__(self, sq):
        self.sq = sq

    def __iter__(self):
        return iter(self.sq)

f = Foo([1,2,3,4])

from collections import Iterable,Iterator
print(isinstance(f,Iterator))   # False
print(isinstance(f,Iterable))   # True

for item in f:
    print(item)
单独使用__iter__
class Foo:

    def __init__(self, num=1):
        self.num = num

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

f = Foo()

from collections import Iterable,Iterator
print(isinstance(f,Iterator))   # True
print(isinstance(f,Iterable))   # True

for i in range(5):   # 超出范围主动报错
    print(f.__next__())
单独使用__next__
# for循环内部实际上就是先调用iter()把Iterable变成Iterator再进行循环迭代的
l = [1,2,3,4,5]
obj = iter(l)
while True:
    try:
        # 获得下一个值
        print(next(obj))
    except StopIteration:
        # 遇到StopIteration就退出循环
        break
for循环内部实现
class Range:
    def __init__(self, start, end, step):
        self.start = start
        self.end = end
        self.step = step

    def __iter__(self):
        return self

    def __next__(self):
        if self.start >= self.end:
            raise StopIteration
        n = self.start
        self.start += self.step
        return n


for i in Range(1,12,2):
    print(i)
模拟range的实现

6.13 __new__

__new__是在新式类中出现的方法,它作用在构造方法__init__建造实例之前。可以这么理解,__new__方法是创建类实例的方法,__init__是在类实例创建之后调用,,__new__可以决定是否要使用该init()方法,因为new()可以调用其他类的构造方法或者直接返回别的对象来作为本类的实例。

__new__方法的特性:

  • 在类准备将自身实例化时调用
  • 它是类的静态方法,即使没有被加上静态方法装饰器

__new__方法的语法如下:

def __new__(cls, *args, **kwargs):
    pass

第一个参数cls是当前正在实例化的类,如果要得到当前类的实例,应当在当前类中的__new__方法语句中调用当前类的父类的__new__方法。
例如,如果当前类是直接继承自object,那当前类的__new__方法返回的对象应该为:

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

事实上如果(新式)类中没有重写__new__方法,即在定义类时没有重新定义__new__,Python会默认调用该类的父类的__new__方法来构造该类的实例,如果该类的父类也没有重写 __new__,那么将一直追溯至object的__new__方法,因为object是所有新式类的基类
而如果新式类中重写了__new__方法,那么我们可以自由选择任意一个的其他的新式类(一定要是新式类,只有新式类才有__new__,因为所有新式类都是object的后代,而经典类则没有__new__ 方法)的__new__方法来制造实例,包括这个新式类的所有前代类和后代类,只要它们不会造成递归死循环。如下代码:

class A(object):

    def __init__(self, *args, **kwargs):
        pass

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

# 以上return等同于
# return object.__new__(A, *args, **kwargs)
# return B.__new__(cls, *args, **kwargs)
# return C.__new__(cls, *args, **kwargs)


class B(object):
    pass


class C(A):
    def __new__(cls, *args, **kwargs):
        return object.__new__(cls, *args, **kwargs)

如果C中没有定义__new__方法,那么会自动调用其父类的__new__方法来制造实例,即:

A.__new__(cls, *args, **kwargs)

注意:

1. 在任何新式类的__new__方法,不能调用自身的__new__来制造实例,因为这会造成死循环。因此必须避免类似以下的写法:

# 在A中避免:
return A.__new__(cls, *args, **kwargs)
return cls.__new__(cls, *args, **kwargs)

2. 使用object或者没有血缘关系的新式类的__new__是安全的,但是如果是在有继承关系的两个类之间,应避免互调造成死循环,例如:

# 在A中
return C.__new__(cls, *args, **kwargs), 
# 在C中
return A.__new__(cls, *args, **kwargs)。

通常来说,新式类开始实例化时,__new__方法会返回cls(cls指代当前类)的实例,然后该类的__init__方法作为构造方法会接收这个实例(即self)作为自己的第一个参数,然后依次传入__new__方法中接收的位置参数和命名参数

注意:如果__new__没有返回cls(即当前类)的实例,那么当前类的__init__方法是不会被调用的。如果__new__返回其他类(新式类或经典类均可)的实例,那么只会调用被返回的那个类的构造方法

class A(object):

    def __init__(self, *args, **kwargs):
        pass

    def __new__(cls, *args, **kwargs):
            return object.__new__(B, *args, **kwargs)


class B(object):
    pass

a = A()
print(type(a))        # <class '__main__.B'>

因此可以这么描述__new__和__init__的区别,在新式类中__new__才是真正的实例化方法,为类提供外壳制造出实例框架,然后调用该框架内的构造方法__init__使其丰满

import threading

class MySingleton(object):
    _instance_lock = threading.Lock()

    def __init__(self):
        pass

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, "_instance"):
            with cls._instance_lock:
                if not hasattr(cls, "_instance"):
                    cls._instance = object.__new__(cls)
        return cls._instance

singleton1 = MySingleton()
singleton2 = MySingleton()

print(singleton1)
print(singleton1)

def task(arg):
    obj = MySingleton()
    print(obj)

for i in range(5):
    t = threading.Thread(target=task,args=[i,])
    t.start()
实例--单例模式的实现
posted @ 2018-07-13 12:01  Joe1991  阅读(102)  评论(0)    收藏  举报