Python 面向对象进阶

Python面向对象进阶

类的特数成员方法

1.__doc__:类的描述信息:

class Foo:
    """描述类信息,滴答滴答滴啊..."""
    pass

f = Foo()
print("这是类的描述信息:", f.__doc__)     # 这是类的描述信息: 描述类信息,滴答滴答滴啊..
print("这是类的描述信息:", Foo.__doc__)     # 这是类的描述信息: 描述类信息,滴答滴答滴啊...

2.__module__:当前操作对象所在模块:

class Foo:
    """描述类信息,滴答滴答滴啊..."""
    pass

print("当前对象的模块:", Foo.__module__)       # 当前对象的模块: __main__

f = Foo()
print("当前对象的模块:", f.__module__)       # 当前对象的模块: __main__

3.__class__:当前操作对象的类:

class Foo:
    """描述类信息,滴答滴答滴啊..."""
    pass


print("当前对象的类:", Foo.__class__)     # 当前对象的类: <class 'type'>

f = Foo()
print("当前对象的类:", f.__class__)     # 当前对象的类: <class '__main__.Foo'>

4.__init__:构造方法,通过类创建实例对象时,自动触发执行;
5.__del__:析构方法,当对象在内存中被释放时,自动触发执行;

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

class Foo:
    def __del__(self):
        print("执行删除!")


f = Foo()
print("*"*10)       # **********
del f       # 执行删除!
print("#"*10)       # ##########

6.__call__:对象后面加括号(),触发执行;

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

class Foo:
    def __init__(self):
        pass

    def __call__(self, *args, **kwargs):
        print("() is execute %s ." % "__call__")


obj = Foo()     # 执行__init__
obj()       # 执行__call__:() is execute __call__ .

7.__dict__:查看类或者对象中的所有成员;

class Province:

    country = "China"

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

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


# 获取类的成员,即:静态属性、方法
print(Province.__dict__)    # {'__module__': '__main__', 'country': 'China', '__init__': <function Province.__init__ at 0x000001B4CACC9950>, 'func': <function Province.func at 0x000001B4CACC99D8>, '__dict__': <attribute '__dict__' of 'Province' objects>, '__weakref__': <attribute '__weakref__' of 'Province' objects>, '__doc__': None}

shanghai = Province("Shang Hai", 12000000)  
# 获取对象shanghai的成员
print(shanghai.__dict__)        # {'name': 'Shang Hai', 'count': 12000000}

hongkong = Province("Hong Kong", 240000000)
# 获取对象hongkong的成员
print(hongkong.__dict__)        # {'name': 'Hong Kong', 'count': 240000000}

8.__str__、__repr__和__format__:__str__、__repr__,改变对象的字符串显示;__format__,自定义格式化字符串;

  • __str__、__repr__
>>> class Student:
...     def __init__(self, name):
...             self.name = name
... 
>>> print(Student("Jack Ma"))
<__main__.Student object at 0x7fedeb012e80>

打印出<__main__.Student object at 0x7fedeb012e80>,不明觉厉。那么怎么才能打印出我们好读的内容呢?秩序定义好__str__()方法,返回我们想要的内容即可:

>>> class Student:
...     def __init__(self, name):
...             self.name = name
...     def __str__(self):
...             return "Student object name: %s" % self.name
... 
>>> print(Student("Jack"))
Student object name: Jack

这样打印出来的实例,不但好看,而且容易看出实例内部的重要数据。但是,细心的同学会发现直接敲变量不用print()函数,打印出来的实例还是不好看

>>> s = Student("Pony")
>>> s
<__main__.Student object at 0x7fedeb0197f0>

这是因为直接显示变量调用的不是__str__(),而是__repr__(),两者的区别是__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说,repr()是为调试服务的
解决办法是再定义一个__repr__()。但是通常__str__()和__repr__()代码都是一样的,所以,有个偷懒的写法:

>>> class Student:
...     def __init__(self, name):
...             self.name = name
...     def __str__(self):
...             return "Student object name: %s" % self.name
...     __repr__ = __str__
... 
>>> s = Student("LiYanhong")
>>> s
Student object name: LiYanhong
  • __format__:自定义格式化字符串:
date_dict = {
    "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_dict:
            format_spec = "ymd"
        fmt = date_dict[format_spec]
        return fmt.format(self)


d = Date(2018, 5, 14)
print(format(d))                # 2018:5:14
print("{:mdy}".format(d))       # 5-14-2018

9.__getitem__、__setitem__和__delitem__:用于类似字典的操作,分别表示获取、设置和删除数据:

class Foo:
    def __getitem__(self, item):
        print("__getitem__", item)

    def __setitem__(self, key, value):
        print("__setitem__", key, value)

    def __delitem__(self, key):
        print("__delitem__", key)


f = Foo()

res = f["Jack"]             # 自动触发执行__getitem__:__getitem__ Jack
f["Rose"] = "Marry"         # 自动触发执行__setitem__:__setitem__ Rose Marry
del f["Jack"]               # 自动触发执行__delitem__:__delitem__ Jack

10.__getattr__、__setattr__和__delattr__

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

  • __setattr__
    拦截所有属性赋值语句,如果定义了这个方法,self.attr = value就会变成self.setattr("attr", value)。这个需要特别注意,当在__setattr__方法内对属性进行赋值时,不可使用self.attr=value,因为它会再次调用self.setattr("attr", value),则会形成无限递归循环,最后导致堆栈溢出异常。应该通过对属性字典做索引运算来赋值任何实例属性,也就是使用self.dict["attr"]=value。

  • __delattr__
    删除属性时触发执行;

class Foo:
    static_var = 10

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

    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__:添加/修改属性都会触发它的执行
f = Foo(20)
print(f.__dict__)   # 因为这里重写了__setattr__,凡是赋值操作都会触发它的执行,这里什么都没写,也就没有赋值,除非直接操作字典,否则永远无法赋值
f.temp = 30
print(f.__dict__)

f.__dict__["x"] = 40    # 我们可以直接修改属性字典,来完成添加/修改属性的操作
print(f.__dict__)

# __delattr__:删除属性的时候触发执行
del f.x
print(f.__dict__)


# __getattr__:只有在使用点调用属性且不存在时才会触发
f.variable

运行输出结果为:

from __setattr__
{}
from __setattr__
{}
{'x': 40}
from __delattr__
{}
from __getattr__:你找的属性不存在!

isinstance和issubclass

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

>>> class Foo:
...     pass
... 
>>> f = Foo()
>>> isinstance(f, Foo)
True

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

>>> class Foo:
...     pass
... 
>>> 
>>> class Bar(Foo):
...     pass
... 
>>> issubclass(Bar, Foo)
True

反射

1.反射的概念
反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。
2.反射的实现
Python面向对象中的反射:通过字符串的形式操作对象相关的属性,Python中的一切事物都是对象,都可以使用反射。
Python中4个可以实现自省的函数,下列方法适用于类和对象(一切皆对象,类本身也是一个对象)。

  • hasattr(object, name)
    判断一个对象里面是否有name属性或name方法,返回bool值,有name属性或方法返回true,否则返回false。需要注意的是name要用括号()括起来:
>>> class Test:
...     name = "USA"
...     def run(self):
...             return "Hello World!"
...
>>> t = Test()
>>> hasattr(t, "name")
True
>>> hasattr(t, "run")
True
>>> hasattr(t, "everything")
False
  • getattr(object, name, default=None)
    获取对象object的属性或方法,如果存在打印出来,如果不存在打印出默认值,默认值可选。需要注意的是,如果获取的对象方法,返回的是方法的内存地址,如果需要运行这个方法,可以在后面添加括号()执行:
>>> class Test:
...     name = "Jack"
...     def run(self):
...             return "Hello World!"
...
>>> t = Test()
>>> getattr(t, "name")      # 获取name属性,存在就打印出来
'Jack'
>>> getattr(t, "run")       # 获取run方法,存在就打印出方法的内存地址
<bound method Test.run of <__main__.Test object at 0x7f0d66cbee80>>
>>> getattr(t, "run")()     # 获取run方法,后面加括号()可以执行该方法
'Hello World!'
>>> getattr(t, "age")       # 获取一个不存在的属性
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute 'age'
>>> getattr(t, "age", "18")     # 若属性不存在,返回默认值
'18'
  • setattr(object, name, values)
    给对象的属性赋值,若属性不存在,先创建再赋值:
>>> class Test:
...     name = "Jack"
...     def run(self):
...             return "Hello World!"
...
>>> t = Test()
>>> hasattr(t, "age")
False
>>> setattr(t, "age", "22")
>>> hasattr(t, "age")
True
>>> getattr(t, "age")
'22'
  • delattr(object, name)
    删除object对象名为name的属性,如下综合例子:
class Intermediary:
    feature = "white"

    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)


intermediary = Intermediary("Wanke", "高新区")

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

# 获取属性
name = getattr(intermediary, "name")
func = getattr(intermediary, "rent_house")

# 获取不存在的属性,设置默认值
print(getattr(intermediary, "something", "不存在的属性!"))

# 设置属性
setattr(intermediary, "flag", True)
setattr(intermediary, "show_name", lambda self: self.name + " suffix")
print(intermediary.__dict__)
print(intermediary.show_name(intermediary))

# 删除属性
delattr(intermediary, "addr")
delattr(intermediary, "show_name")
# delattr(intermediary, "gender")     # 删除不存在的属性,报错

print(intermediary.__dict__)

运行输出结果为:

True
True
不存在的属性!
{'name': 'Wanke', 'addr': '高新区', 'flag': True, 'show_name': <function <lambda> at 0x000002326DA11EA0>}
Wanke suffix
{'name': 'Wanke', 'flag': True}

万物皆对象,类也是对象

class Foo:
    static_field = "static"

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

    def func(self):
        return "func"

    @staticmethod
    def bar():
        return "bar"


print(getattr(Foo, "static_field"))     # static
print(getattr(Foo, "func"))     # <function Foo.func at 0x000001BB676D99D8>
print(getattr(Foo, "bar"))      # <function Foo.bar at 0x000001BB676D9A60>

3.反射的好处

  • 实现可拔插机制
    可以预先定义好接口,接口只有在被调用时才会真正执行,这实现了即插即用,这其实是一种"后期绑定",即可以先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能:
# 程序员A未实现功能
class FtpClient:
    """ftp客户端,但是还未实现具体功能"""
    def __init__(self, addr):
        print("正在连接服务器[%s]" % addr)
        self.addr = addr
但不影响程序员B继续实现其他逻辑,利用反射事先做判断
# from module import FtpClient
f = FtpClient("192.168.1.100")
if hasattr(f, "get"):       # 判断方法是否实现
    func_get = getattr(f, "get")
    func_get()
else:
    print("不存在此方法!")
    print("处理其他的逻辑!")
  • 动态导入模块
    动态导入模块方法:1.import;2.import importlib

说明:
1.函数功能用于动态的导入模块,主要用于反射或者延迟加载模块;
2.import(module)相当于import module

方法1实例:首先,创建目录language;然后在该目录下创建模块:lang.py,其代码为:

class Foo:
    def __str__(self):
        return "Python language"

在language目录的平级目录创建测试模块:test.py,使用__import__动态以字符串形式导入language下的lang模块:

l = __import__("language.lang")      # 相当于import language
python = l.lang.Foo()
print(python)

方法2实例:使用importlib进行动态导入(此方法好理解,也是官方推荐使用)

import importlib
la = importlib.import_module("language.lang")
res = la.Foo()
print(res)

实例如下截图:

元类

1.元类的定义
元类是用来控制如何创建类的,正如类时创建对象的模板一样,而元类的主要目的是为了控制类的创建行为。元类的实例化结果为我们用class定义的类,正如类的实例为对象(f对象是Foo类的一个实例,Foo类是type类的一个实例)。type是Python的一个内建元类,用来直接控制类生成,Python中任何class定义的类其实都是type类实例化的对象。

2.创建类的方式

  • 使用class关键字
class Chinese:
    country = "China"

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

    def talk(self):
        print("%s is talking..." % self.name)
  • 手动模拟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)
"""

步骤1(先处理类体->名称空间):类体定义的名字都会存放于类的名称空间中(一个局部的命名空间),我们可以预先定义一个空字典,然后用exec去执行类体的代码(exec产生名称空间的过程与真正的class过程类似,只是后者会将__开头的属性变形),生成类的局部名称空间,即填充字典

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

print(class_dict)
# {'country': 'China', '__init__': <function __init__ at 0x000001EDE6851EA0>, 'talk': <function talk at 0x000001EDE8529950>}

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

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

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

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

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

补充:若Foo类有继承,即class Foo(Bar):...,则等同于type("Foo", (Bar,), {});一个类没有声明自己的元类,默认它的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类;

自定义元类精简版

class MyType(type):

    def __init__(self, what, bases=None, dict=None):
        print(what, bases, dict)

    def __call__(self, *args, **kwargs):
        print("*"*12)
        obj = object.__new__(self)
        self.__init__(obj, *args, **kwargs)
        return obj


class Room(metaclass=MyType):
    def __init__(self, name):
        self.name = name


room = Room("Province")
print(room.__dict__)

运行输出结果为:

Room () {'__module__': '__main__', '__qualname__': 'Room', '__init__': <function Room.__init__ at 0x000002007B099A60>}
************
{'name': 'Province'}
posted @ 2018-05-14 17:29  月缺一格  阅读(247)  评论(0编辑  收藏  举报