面向对象进阶

 

1 类型判断

  1.1 issubclass:判断x类是否是y类型的子类,返回bool值

class Base:
    pass

class Foo(Base):
    pass

class Bar(Foo):
    pass

print(issubclass(Bar, Foo))  # True
print(issubclass(Foo, Bar))  # False
print(issubclass(Bar, Base))  # True

  1.2 type:查看obj是由哪个类创建的

class Foo:
    pass

obj = Foo()
print(obj, type(obj))  #<__main__.Foo object at 0x0000023138218518>  <class '__main__.Foo'>

  1.3 isinstance:以判断x是y类型的数据

# isinstance可以判断该对象是否是家族体系中的(只能往上判断类)。

class Base:
    pass

class Foo(Base):
    pass

class Bar(Foo):
    pass

print(isinstance(Bar(), Foo))  # True
print(isinstance(Foo(), Base))  # True
print(isinstance(Bar(), Base))  # True
print(isinstance(Foo(), Bar))   # False

2 反射

  2.1 什么是反射?

  之前我们导入模块都是先引入模块,然后通过模块去访问各个我们要用的功能,现在呢?我们手动输入要运行的功能,然后拿着这个功能去模块里查找,这就叫反射。  

  就是通过字符串的形式操作对象相关的属性。Python中的一切事物都是对象(都可以使用反射)。

# 下面的代码中用到了如下两个方法:
# hasattr(对象, 字符串):是用来判断对象是否有字符串名称对应的这个属性(功能)。
# getattr(对象,字符串):是用来获取对象中字符串名称对应的属性(功能)。

import daniu
while 1:
    print('''
        作为大牛,我帮你写了
        chi
        he
        la
        shui
        等功能,你自己看着办吧...
    ''')

    func_str = input('请输入你要测试的功能:').strip()
    if hasattr(daniu, func_str):
        func = getattr(daniu, func_str)
        func()
    else:
        print('大牛就这几个功能,别搞事情!')

  2.2 反射应用

# 类中的内容可以这样动态的进⾏获取
print(getattr(Person, "country"))
print(getattr(Person, "eat"))  # 相当于class.func 函数
# 对象⼀样可以
obj = Person()
print(getattr(obj, "country"))
print(getattr(obj, "eat"))  # 相当于obj.func ⽅法

  2.3 反射的四个函数

  关于反射, 其实⼀共有4个函数:(这些操作都是在内存中进⾏的,并不会影响源代码。)

  1. hasattr(obj, str) 判断obj中是否包含str成员
  2. getattr(obj,str) 从obj中获取str成员。
  3. setattr(obj, str, value) 把obj中的str成员设置成value。这⾥的value可以是值,也可以是函数或者⽅法。
  4. delattr(obj, str) 把obj中的str成员删除掉。
    f = Foo()
    print(hasattr(f, 'eat'))    # False
    setattr(f, 'eat', "123")    # 被添加了⼀个属性信息
    print(f.eat)                # 123
    
    setattr(f, "eat", lambda x: x + 1)   # 被添加了一个eat函数,函数体是lambda;覆盖了原来set的eat了
    print(f.eat(3))             # 4
    
    # 此时的eat既不是静态⽅法, 也不是实例⽅法, 更不是类⽅法. 就相当于你在类中写了个self.chi = lambda 是⼀样的
    print(f.eat)         # <function <lambda> at 0x000002DA2C913E18>
    print(f.__dict__)    # {'eat': <function <lambda> at 0x1015a2048>}
    delattr(f, "eat")
    print(hasattr(f, "eat"))   # False

  2.4  补充importlib

  importlib是一个可以根据字符串的模块名实现动态导入模块的库。

  详情参考 https://www.cnblogs.com/meishandehaizi/p/5863233.html

  举个例子:

  目录结构:

├── aaa.py
├── bbb.py
└── mypackage
    ├── __init__.py
    └── xxx.py

   使用importlib动态导入模块:

  bbb.py

import importlib

func = importlib.import_module('aaa')
print(func)
func.f1()

m = importlib.import_module('mypackage.xxx')
print(m.age)

3 类的其他成员

  3.1 __str__

  改变对象的字符串显示。可以理解为使用print函数打印一个对象时,会自动调用对象的__str__方法。

class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    # 定义对象的字符串表示
    def __str__(self):
        return self.name

s1 = Student('张三', 24)
print(s1)        # 会调用s1的__str__方法:张三

  3.2 __repr__

  在python解释器环境下,会默认显示对象的repr表示。

>>> class Student:
...     def __init__(self, name, age):
...         self.name = name
...         self.age = age
...     def __repr__(self):
...         return self.name
... 
>>> s1 = Student('张三', 24)
>>> s1
张三

  总结:

  str函数或者print函数调用的都是  obj.__str__()
  repr函数或者交互式解释器调用的是 obj.__repr__()

  注意:
  如果__str__没有被定义,那么就会使用__repr__来代替输出。
  __str__和__repr__方法的返回值都必须是字符串。

  3.3 __format__

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

    # 定义对象的字符串表示
    def __str__(self):
        return self.name

    def __repr__(self):
        return self.name

    __format_dict = {
        'n-a': '{obj.name}-{obj.age}',  # 姓名-年龄
        'n:a': '{obj.name}:{obj.age}',  # 姓名:年龄
        'n/a': '{obj.name}/{obj.age}',  # 姓名/年龄
    }

    def __format__(self, format_spec):
        """
        :param format_spec: n-a,n:a,n/a
        :return:
        """
        if not format_spec or format_spec not in self.__format_dict:
            format_spec = 'n-a'
        fmt = self.__format_dict[format_spec]
        return fmt.format(obj=self)   #就像这样:'{obj.name}-{obj.age}'.format(obj = self)

s1 = Student('张三', 24)
ret = format(s1, 'n/a')
print(ret)    # 张三/24
ret = format(s1, 'n*a')
print(ret)    # 张三-24
View Code

  3.4 __del__

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

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

class A:
    def __del__(self):
        print('删除了...')


a = A()
print(a)  # <__main__.A object at 0x10164fb00>
del a  # 删除了...
print(a)  # NameError: name 'a' is not defined
View Code

  3.5 __dict__和__slots__

  Python中的类,都会从object里继承一个__dict__属性,这个属性中存放着类的属性和方法对应的键值对。一个类实例化之后,这个类的实例也具有这么一个__dict__属性。但是二者并不相同。 

class A:
    some = 1

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

a = A(10)
print(a.__dict__)  # {'num': 10}

a.age = 10
print(a.__dict__)  # {'num': 10, 'age': 10}

  由于每实例化一个类都要分配一个__dict__变量,容易浪费内存。因此在Python中有一个内置的__slots__属性。

  当一个类设置了__slots__属性后,这个类的__dict__属性就不存在了。__slots__通常都是作为一种优化工具来使用。

class A:
    __slots__ = ['name', 'age']

a1 = A()
# print(a1.__dict__)  # AttributeError: 'A' object has no attribute '__dict__'
a1.name = '张三'
a1.age = 24
# a1.hobby = '吹牛逼'  # AttributeError: 'A' object has no attribute 'hobby'
print(a1.__slots__)   #['name', 'age']

  注意事项:
  __slots__的很多特性都依赖于普通的基于字典的实现。
  定义了__slots__属性的类就不支持多继承。

  3.6 __item__系列

class Foo:
    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]时,执行我__delitem__')
        self.__dict__.pop(key)

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

f1 = Foo('sb')  
print(f1.__dict__)   #{'name': 'sb'}
f1['age'] = 18
f1.hobby = '吹牛逼'
print(f1.__dict__)  #{'name': 'sb', 'age': 18, 'hobby': '吹牛逼'}
del f1.hobby        #del obj.key时,执行我__delattr__
del f1['age']       #del obj[key]时,执行我__delitem__
f1['name'] = 'alex'
print(f1.__dict__)   #{'name': 'alex'}
View Code

  3.7 __init__

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

    def __str__(self):
        return '<Person: {}({})>'.format(self.name, self.age)
    
p1 = Person('张三', 24)
print(p1)   #<Person: 张三(24)>
View Code

  3.8 __new__

  __init__是在类实例被创建之后调用的,它完成的是类实例的初始化操作;

   __new__方法正是创建这个类实例的方法。

class Person:

    def __new__(cls, *args, **kwargs):
        print('调用__new__,创建类实例')
        return super().__new__(Person)

    def __init__(self, name, age):
        print('调用__init__,初始化实例')
        self.name = name
        self.age = age

    def __str__(self):
        return '<Person: {}({})>'.format(self.name, self.age)

p1 = Person('张三', 24)
print(p1)

# 输出结果:
调用__new__,创建类实例
调用__init__,初始化实例
<Person: 张三(24)>

  __new__方法在类定义中不是必须写的,如果没定义的话默认会调用object.__new__去创建一个对象(因为创建类的时候默认继承的就是object)。

  如果我们在类中定义了__new__方法,就是重写了默认的__new__方法,我们可以借此自定义创建对象的行为。

#重写类的__new__方法来实现单例模式。

class Singleton:

    # 重写__new__方法,实现每一次实例化的时候,返回 同一个instance对象
    def __new__(cls, *args, **kw):
        if not hasattr(cls, '_instance'):
            cls._instance = super().__new__(Singleton)
        return cls._instance

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


s1 = Singleton('张三', 24)
s2 = Singleton('李四', 20)
print(s1, s2)             # 这两实例都一样,<__main__.Singleton object at 0x000002D463F28550>  <__main__.Singleton object at 0x000002D463F28550>
print(s1.name, s2.name)   #李四 李四

  3.9 __call__

  __call__ 方法的执行是由对象后加括号触发的,即:对象()。拥有此方法的对象可以像函数一样被调用。(不是必须写的)

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

    def __call__(self, *args, **kwargs):
        print('调用对象的__call__方法')

a = Person('张三', 24)    # 类Person可调用
a()           # 调用对象的__call__方法

  3.10 __doc__

  定义类的描述信息。注意该信息无法被继承。

class A:
    """我是A类的描述信息"""
    pass

print(A.__doc__)

  3.11 __iter__和__next__

  如果一个对象拥有了__iter__和__next__方法,那这个对象就是可迭代对象。

class A:
    def __init__(self, start, stop=None):
        if not stop:   # stop = None,也就是不传值使用默认参数时
            start, stop = 0, start
        self.start = start
        self.stop = stop

    def __iter__(self):
        return self

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

a = A(1, 5)
print(isinstance(a, Iterator))   #True

for i in A(1, 5):
    print(i)          # 1 2 3 4
View Code

  3.12 __enter__和__exit__

  一个对象如果实现了__enter__和___exit__方法,那么这个对象就支持上下文管理协议,即with语句。

  上下文管理协议适用于那些进入和退出之后自动执行一些代码的场景,比如文件、网络连接、数据库连接或使用锁的编码场景等。

class A:
    def __enter__(self):
        print('进入with语句块时执行此方法,此方法如果有返回值会赋值给as声明的变量')

    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        :param exc_type: 异常类型
        :param exc_val: 异常值
        :param exc_tb: 追溯信息
        :return:
        """
        print('退出with代码块时执行此方法')
        print('1', exc_type)
        print('2', exc_val)
        print('3', exc_tb)

with A() as f:
    print('进入with语句块')
    # with语句中代码块出现异常,则with后的代码都无法执行。
    # raise AttributeError('sb')
print('嘿嘿嘿')

# 运行结果:
进入with语句块时执行此方法,此方法如果有返回值会赋值给as声明的变量
进入with语句块
退出with代码块时执行此方法
1 None
2 None
3 None
嘿嘿嘿
View Code

  3.13 __len__

  拥有__len__方法的对象支持len(obj)操作。

class A:
    def __init__(self):
        self.x = 1
        self.y = 2
        self.z = 5

    def __len__(self):
        return len(self.__dict__)

a = A()
print(len(a))   #3
View Code

  3.14 __hash__

  拥有__hash__方法的对象支持hash(obj)操作。

class A:
    def __init__(self):
        self.x = 1
        self.y = 2

    def __hash__(self):
        return hash(str(self.x) + str(self.y))

a = A()
print(hash(a))  #4242458207803389448
View Code

  3.15 __eq__

  拥有__eq__方法的对象支持相等的比较操作,__eq__ 定义了 类的等号(==)行为

class A(object):
    def __init__(self, name):
        self.name = name

    def __eq__(self, obj):
        return self.name == obj.name


if __name__ == '__main__':
    a = A("Leon")
    b = A("Mike")
    print(a == b)  #False
View Code
原文地址:https://www.cnblogs.com/timetellu/p/10686489.html