老式类与新式类
老式类
在老式类中,class与type不是一回事,老式类的实例通过一内建类型instance实现。如obj实例是一个老式类的实例,则type(obj)为instance,obj.class为实例的类名称。
>>> class Foo:
... pass
...
>>> x = Foo()
>>> x.__class__
>>> type(x)
新式类
新式类中统一了class与type,如果obj是一个新式类,则type(obj)与obj.class相同。
>>> class Foo:
... pass
>>> obj = Foo()
>>> obj.__class__
>>> type(obj)
>>> obj.__class__ is type(obj)
True
class与type
在Python2.2以前,都是老式类,在Python2.2以后,可以通过继承object类,显示定义新式类。如
class Foo(object):
pass
在Python3中,所有的类都是新式类。
在新式类中,所有类的都是type元类的实例。类可以通过class关键字或type()动态创建。
class关键字创建类
当一个class
声明被执行时,Python首先执行class
声明的主体,正如执行其他正常的代码块一样。产生的命名空间(一个dict)包含所有类的属性。
正如大多数语言一样,类是描述怎么产生对象的代码块,但是在Python中,类不仅仅如此,其还是Python的对象(object
)、
一旦你使用关键词class
,python执行它,产生一个OBJECT。命令如下:
class ObjectCreator(object):
pass
在内存中产生名为ObjectCreator
的对象。这个对象本身有能力产生其他的对象(实例),因此,称呼其为类。
但是其仍为对象,因此:
- 可以将其赋值给一个变量
- 可以拷贝他
- 可以为其添加属性
- 可以将他作为函数参数进行传递
>>> print(ObjectCreator) # you can print a class because it's an object
>>> def echo(o):
... print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>
动态语言和静态语言最大的不同,就是函数与类的定义,不是在编译前,而是在运行时动态创建。
Python类也是对象,可以像任何对象一样动态地创建类。
首先,可以在函数中使用class
创建类:
>>> def choose_class(name):
... if name == 'foo':
... class Foo(object):
... pass
... return Foo # return the class, not an instance
... else:
... class Bar(object):
... pass
... return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>
使用type
产生类
type
有两种用法:
- 一种用法是告诉对象的类型:
>>> print(type(1))
>>> print(type("1"))
>>> print(type(ObjectCreator))
>>> print(type(ObjectCreator()))
- 另一种用法是动态的产生类:
工作方式如下:
type(类的名字,
父类元组(用于继承,可为空),
包含类属性名字与值的字典)
例如,如下类:
>>> class Foo(object):
... bar = True
可通过type
表述为:
>>> Foo = type('Foo', (), {'bar':True})
You’ll notice that we use "MyShinyClass" as the name of the class and as the variable to hold the class reference. They can be different, but there is no reason to complicate things.
通过type
定义的类与通常使用class
定义的类使用上完全一样。
最后,如果想添加方法到type
定义的类中,方法如下:
>>> def echo_bar(self):
... print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True
也可以使用如下方法动态地添加方法到类中,
>>> def echo_bar_more(self):
... print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True
type()所做的事情与Python在使用关键字class时所做的事情一样
元类
自定义元类
考虑如下例子的执行流程:
>>> class Foo:
... pass
...
>>> f = Foo()
表达式Foo()创建Foo类的一个实例,解析器遇到Foo()时,将执行如下:
- Foo父类的call方法。因为Foo是标准的新式类,因此其父类为type元类,因此type的call()方法被调用。
- call()方法依次调用如下函数:
- new()
- init()
因为Foo类没有定义new()和init(),默认的方法从Foo祖先类继承。
但是,如果Foo定义了这些方法,他们将覆盖祖先类的相应方法,从而定制Foo初始化时的各种行为。
元类的目的:在创建类时,可以自动地改变类。通常,会在API做这类事情,可能希望可以创建符合当前上下文的类。
例如,你决定在你的模块里所有的类的属性都应该是大写形式。有好几种方法可以办到,但其中一种就是通过在模块级别设定metaclass。采用这种方法,这个模块中的所有类都会通过这个元类来创建,我们只需要告诉元类把所有的属性都改成大写形式就万事大吉了。
现在我们再做一次,这一次用一个真正的class来当做元类
class UpperAttrMetaclass(type):
def __new__(cls, name, bases, dct):
attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)
__metaclass__ = UpperAttrMetaclass
class Foo(object):
# 我们也可以只在这里定义__metaclass__,这样就只会作用于这个类中
bar = 'bip'
测试:
print hasattr(Foo, 'bar')
# 输出: False
print hasattr(Foo, 'BAR')
# 输出:True
f = Foo()
print f.BAR
# 输出:'bip'
元类的作用是什么?
使用到元类的代码比较复杂,这背后的原因倒并不是因为元类本身,而是因为你通常会使用元类去做一些晦涩的事情,依赖于自省,控制继承等等。
元类本身而言,其实是很简单的:
-
- 拦截类的创建
-
- 修改类
-
- 返回修改之后的类
为什么要用metaclass类而不是函数?
- 意图更加清晰。
- 可使用OOP编程。元类可以从元类继承,改写元类的方法。元类甚至可以继承元类。
- 可以把代码组织的更好。将多个方法归总到一个类中会很有帮助,也会使得代码更容易阅读。
- 可以使用new, init以及call这样的特殊方法。
究竟为什么要使用元类
元类的主要用途是创建API。
一个典型的例子是Django ORM。
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
guy = Person(name='bob', age='35')
print guy.age
这并不会返回一个IntegerField对象,而是会返回一个int,甚至可以直接从数据库中取出数据。这是有可能的,因为models.Model定义了metaclass,
并且使用了一些魔法能够将你刚刚定义的简单的Person类转变成对数据库的一个复杂hook。
Django框架将这些看起来很复杂的东西通过暴露出一个简单的使用元类的API将其化简,通过这个API重新创建代码,在背后完成真正的工作。
元类的使用
例1: 使用元类修改类的成员名称
假设下面例子:
class Foo(object):
name = 'foo'
def bar(self):
print 'bar'
下面,通过元类设计一种方法给Foo
类的属性与方法名称前面加上my_
前缀,即name
编程my_name
,bar
变成my_bar
。
另外,添加一个echo
的方法。有很多种方法实现,下面我们通过元类实现。
首先,定义一个元类,按照默认习惯,类名以Metaclass
结尾,代码如下:
class PrefixMetaclass(type):
def __new__(cls, name, bases, attrs):
'''给所有属性与方法的名称加上前缀my_'''
_attrs = dict(('my_' + key, value) for key, value in attrs.items())
_attrs['echo'] = lambda self, phrase: phrase
return super(PrefixMetaclass, cls).__new__(cls, name, bases, _attrs)
接着,使用元类创建Foo类。
- 在Python2中,需在Foo类中添加
__metaclass__
属性,
class Foo(object):
__metaclass__ = PrefixMetaclass
name = 'foo'
def bar(self):
print 'bar'
- 在Python3中,需改代码如下:
class Foo(metaclass=PrefixMetaclass):
name = 'foo'
def bar(self):
print 'bar'
运行如下命令,
f = Foo()
print("f dir:%s" % dir(f))
print("Foo dir:%s" % dir(Foo))
输出结果如下:
f dir:['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'echo', 'my___metaclass__', 'my___module__', 'my_bar', 'my_name']
Foo dir:['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'echo', 'my___metaclass__', 'my___module__', 'my_bar', 'my_name']
例2:元类的解析顺序
与所有类的成员解析顺序一样,新式类中使用C3算法。
举例如下:
class PrefixMetaclass(type):
def __new__(cls, name, bases, attrs):
'''给所有属性与方法的名称加上前缀my_'''
_attrs = dict(('my_' + key, value) for key, value in attrs.items())
_attrs['echo'] = lambda self, phrase: phrase
return super(PrefixMetaclass, cls).__new__(cls, name, bases, _attrs)
class Foo(object):
__metaclass__ = PrefixMetaclass # 注意跟 Python3 的写法有所区别
name = 'foo'
def bar(self):
print 'bar'
class Bar(Foo):
prop = 'bar'
运行如下命令
>>> b = Bar()
>>> b.prop # 发现没这个属性
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
in ()
----> 1 b.prop
AttributeError: 'Bar' object has no attribute 'prop'
>>> b.my_prop
'bar'
>>> b.my_name
'foo'
>>> b.my_bar()
bar
>>> b.echo('hello')
'hello'
Python 会首先在当前类,即 Bar 中寻找 metaclass,如果没有找到,就会在父类 Foo 中寻找 metaclass,如果找不到,就继续在 Foo 的父类寻找,如此继续下去。
总结
类是能够创建实例的对象,其实,类本身也是实例,是元类的实例
Python中一切都是对象,他们要么是类的实例,要么是元类的实例。除了type,type是自己的元类。
>>>class Foo(object): pass
>>> id(Foo)
142630324
参考文献
Was this helpful?
0 / 0