小白容易犯的错误 (and)元类
作者:程小航
版权声明:原创作品,谢绝转载!否则将追究法律责任。
小白容易犯的错误
1.面向对象的程序设计看起来高大上,所以我在编程时就应该保证通篇class,这样写出的程序一定是好的程序(面向对象只适合那些可扩展性要求比较高的场景)
2.很多人喜欢说面向对象三大特性(这是从哪传出来的,封装,多态,继承?漏洞太多太多,好吧暂且称为三大特性),那么我在基于面向对象编程时,我一定要让我定义的类中完整的包含这三种特性,这样写肯定是好的程序
好家伙,我说降龙十八掌有十八掌,那么你每次跟人干仗都要从第一掌打到第18掌这才显得你会了是么:面对敌人,你打到第三掌对方就已经倒下了,你说,不行,你给老子起来,老子还没有show完...
3.类有类属性,实例有实例属性,所以我们在定义class时一定要定义出那么几个类属性,想不到怎么办,那就使劲的想,定义的越多越牛逼
这就犯了一个严重的错误,程序越早面向对象,死的越早,为啥面向对象,因为我们要将数据与功能结合到一起,程序整体的结构都没有出来,或者说需要考虑的问题你都没有搞清楚个八九不离十,你就开始面向对象了,这就导致了,你在那里干想,自以为想通了,定义了一堆属性,结果后来又都用不到,或者想不通到底应该定义啥,那就一直想吧,想着想着就疯了。
你见过哪家公司要开发一个软件,上来就开始写,肯定是频繁的开会讨论计划,请看第八节
4.既然这么麻烦,那么我彻底解脱了,我们不要用面向对象编程了,你啊,你有大才,你能成事啊,傻叉。
python中关于OOP的常用术语
抽象/实现
抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。
对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。
封装/接口
封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。
注意:封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”
真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明
(注意:对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)
合成
合成扩充了对类的 述,使得多个不同的类合成为一个大的类,来解决现实问题。合成 述了 一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为, 所有这些合在一起,彼此是“有一个”的关系。
派生/继承/继承结构
派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。
继承描述了子类属性从祖先类继承这样一种方式
继承结构表示多“代”派生,可以述成一个“族谱”,连续的子类,与祖先类都有关系。
泛化/特化
基于继承
泛化表示所有子类与其父类及祖先类有一样的特点。
特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。
多态与多态性
多态指的是同一种事物的多种状态:水这种事物有多种不同的状态:冰,水蒸气
多态性的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。
冰,水蒸气,都继承于水,它们都有一个同名的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的过程,虽然调用的方法都一样
自省/反射
自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像__dict__,__name__及__doc__
8.类的特殊成员方法
A>."__doc__"表示类的描述信息
#!/usr/bin/env python #_*_coding:utf-8_*_ #@author :rianley #blog:http://www.cnblogs.com/rianley #EMAIL:rianley@qq.com class Foo: """ 未成年勿进,老司机要开车了""" def func(self): pass print(Foo.__doc__) #以上代码执行结果如下: 未成年勿进,老司机要开车了 __doc__用法展示
B>.__module__ 和 __class__
假设存在以下目录结构:
#!/usr/bin/env python #_*_coding:utf-8_*_ #@author :rianley #blog:http://www.cnblogs.com/rianley #EMAIL:rianley@qq.com class Name: def __init__(self): self.name = 'rianley'
#!/usr/bin/env python #_*_coding:utf-8_*_ #@author :rianley #blog:http://www.cnblogs.com/rianley #EMAIL:rianley@qq.com from lib.test import Name ''' __module__ 表示当前操作的对象在那个模块. __class__ 表示当前操作的对象的类是什么. ''' obj = Name() print(obj.__module__) # 输出模块 print(obj.__class__) # 输出类 #以上代码执行结果如下: lib.test <class 'lib.test.Name'>
#!/usr/bin/env python #_*_coding:utf-8_*_ #@author :rianley #blog:http://www.cnblogs.com/rianley #EMAIL:rianley@qq.com import importlib ''' 导入模块,我们可以花式玩,请往下看。 ''' #导入方法一:这是puthon解释器自己内部用的。 __import__('lib.test') #导入方法二:与上面这局效果一样,官方建议用这个。 importlib.import_module('lib.test')
C>.__init__ 构造方法,通过类创建对象时,自动触发执行。
D.__del__析构方法,当对象在内存中被释放时,自动触发执行。
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的
E. __call__ 对象后面加括号,触发执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
#!/usr/bin/env python #_*_coding:utf-8_*_ #@author :rianley #blog:http://www.cnblogs.com/rianley #EMAIL:rianley@qq.com class Foo: def __init__(self): pass def __call__(self, *args, **kwargs): print("My name is rianley") a = Foo() #对这个类进行实例化。 a() #调用该实例。 #以上代码执行结果如下: My name is rianley
F. __dict__ 查看类或对象中的所有成员
#!/usr/bin/env python #_*_coding:utf-8_*_ #@author :rianley #blog:http://www.cnblogs.com/rianley #EMAIL:rianleyqq.com 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__) obj1 = Province('陕西', "1924km") print(obj1.__dict__) # 获取 对象obj1 的成员 obj2 = Province('河北', "365km") print(obj2.__dict__) # 获取 对象obj2 的成员
G.__str__ 如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值。
#!/usr/bin/env python #_*_coding:utf-8_*_ #@author :rianley #blog:http://www.cnblogs.com/rianley #EMAIL:rianley@qq.com class Foo: def __str__(self): return 'rianley is good boy !' obj = Foo() print(obj) #以上代码执行结果如下: rianley is good boy !
H.__getitem__、__setitem__、__delitem__
#!/usr/bin/env python #_*_coding:utf-8_*_ #@author :rianley #blog:http://www.cnblogs.com/rianley #EMAIL:rianley@qq.com class Foo(object): def __getitem__(self, key): print('__getitem__', key) def __setitem__(self, key, value): print('__setitem__', key, value) def __delitem__(self, key): print('__delitem__', key) obj = Foo() result = obj['Boy'] # 自动触发执行 __getitem__ obj['k2'] = 'rianley' # 自动触发执行 __setitem__ del obj['123'] #以上代码执行结果如下: __getitem__ Boy __setitem__ k2 rianley __delitem__ 123
I.__new__ 和 __metaclass__
#!/usr/bin/env python #_*_coding:utf-8_*_ #@author :rianley #blog:http://www.cnblogs.com/rianley #EMAIL:rianley@qq.com class Foo(object): def __init__(self, name): self.name = name f = Foo("rianley") ''' 注意: 上述代码中,obj 是通过 Foo 类实例化的对象,其实,不仅 obj 是一个对象,Foo类本身也是一个对象,因为在Python 中一切事物都是对象。如果按照一切事物都是对象的理论:obj对象是通过执行Foo类的构造方法创建,那么Foo类对象应该也是 通过执行某个类的 构造方法 创建。 ''' print(type(f)) print(type(Foo)) 以上代码执行结果如下: <class '__main__.Foo'> #输出:<class '__main__.Foo'> 表示,obj 对象由Foo类创建。 <class 'type'> # 输出:<type 'type'> 表示,Foo类对象由 type 类创建。
所以,f对象是Foo类的一个实例,Foo类对象是 type 类的一个实例,即:Foo类对象 是通过type类的构造方法创建。
#!/usr/bin/env python #_*_coding:utf-8_*_ #@author :rianley #blog:http://www.cnblogs.com/rianley #EMAIL:rianley@qq.com #那么,创建类就可以有两种方式: #a).普通方式 class Foo(object): def func(self): print('hello rianley') print(Foo) print(type(Foo)) print("*"*50,"我是分割线","*"*50) #b).特殊方式 def func(self,name): print('hello',name) f = type('Test', (object,), {'func': func}) # type第一个参数:类名 # type第二个参数:当前类的基类,也就是第一个参数的类名的父类。 # type第三个参数:类的成员(将其他函数封装进去) print(f) print(type(f)) print(dir(f)) #查看“f”类有哪些可用的方法 f_obj = f() f_obj.func("程小航") #以上代码执行结果如下: <class '__main__.Foo'> <class 'type'> ************************************************** 我是分割线 ************************************************** <class '__main__.Test'> <class 'type'> ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'func'] hello 程小航
#!/usr/bin/env python #_*_coding:utf-8_*_ #@author :rianley #blog:http://www.cnblogs.com/rianley #EMAIL:rianley@qq.com def func(self): print("hello %s"%self.name) def __init__(self,name,age): self.name = name self.age = age Foo = type('Foo',(object,),{'func':func,'__init__':__init__}) f = Foo("rianley",25) f.func() #以上代码执行结果如下: hello rianley
So ,孩子记住,类 是由 type 类实例化产生
那么问题来了,类默认是由 type 类实例化产生,type类中如何实现的创建类?类又是如何创建对象?
答:类中有一个属性 __metaclass__,其用来表示该类由 谁 来实例化创建,所以,我们可以为 __metaclass__ 设置一个type类的派生类,从而查看 类 创建的过程。
#!/usr/bin/env python #_*_coding:utf-8_*_ #@author :rianley #blog:http://www.cnblogs.com/rianley #EMAIL:rianley@qq.com class MyType(type): def __init__(self,*args,**kwargs): print("Mytype __init__",*args,**kwargs) def __call__(self, *args, **kwargs): print("Mytype __call__", *args, **kwargs) obj = self.__new__(self) print("obj ",obj,*args, **kwargs) print(self) self.__init__(obj,*args, **kwargs) return obj def __new__(cls, *args, **kwargs): print("Mytype __new__",*args,**kwargs) return type.__new__(cls, *args, **kwargs) print("*"*50,"我是分割线","*"*50) class Foo(object,metaclass=MyType): def __init__(self,name): self.name = name print("Foo __init__") def __new__(cls, *args, **kwargs): print("Foo __new__",cls, *args, **kwargs) return object.__new__(cls) f = Foo("rianley") print("对象是:",f) print("程小航",f.name) 以上代码执行结果如下: ************************************************** 我是分割线 ************************************************** Mytype __new__ Foo (<class 'object'>,) {'__init__': <function Foo.__init__ at 0x006C0078>, '__new__': <function Foo.__new__ at 0x006C00C0>, '__qualname__': 'Foo', '__module__': '__main__'} Mytype __init__ Foo (<class 'object'>,) {'__init__': <function Foo.__init__ at 0x006C0078>, '__new__': <function Foo.__new__ at 0x006C00C0>, '__qualname__': 'Foo', '__module__': '__main__'} Mytype __call__ Yinzhengjie Foo __new__ <class '__main__.Foo'> obj <__main__.Foo object at 0x006BBC50> Yinzhengjie <class '__main__.Foo'> Foo __init__ 对象是: <__main__.Foo object at 0x006BBC50> 程小航 rianley
类的生成 调用 顺序依次是 __new__ --> __init__ --> __call__
metaclass 详解文章:http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python 得票最高那个答案写的非常好
毒鸡汤:
加油吧!让程序中的对象成为现实! 拥有自定义对象的能力(学习!)