Python面向对象编程,元编程metaclass

面向对象的最重要概念就是类class和实例instance,类是抽象的模板,实例是根据类创建出来的具体对象。

没有继承类的情况下,就使用object类,这是所有类最终都会继承的类。

类的__init__方法:
由于类起到模板的作用,所以在创建实例的时候,把一些我们认为必须绑定的属性写进特殊定义的__init__方法:

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

其他不是必须绑定的只需要使用instance.attribute = value来动态创建新的对象属性。

数据封装:
每个实例拥有各自的数据,我们可以封装成函数来访问这些数据:

class Student(object):
    def print_score(self):
        print('%s: %s' % (self.name, self.score))

定义一个方法,除了第一个参数是self以外,其他的和普通函数一样。

访问限制:
如果我们要让内部属性不被外部访问,那么我们可以在属性的名称前面加上两个下划线__,这样它就成了一个私有变量,只有内部可以访问:

class Student(object):
    def __init__(self, name, score):
        self.__name = name
        self.__score = score

这样就确保了外部不能直接修改对象内部的状态,但是外部依然需要访问name和score这两个属性,可以给类添加方法:

 def get_name(self):
        return self.__name

如果又需要外部修改score:

def set_score(self, score):
        self.__score = score

有时候我们可以看到以一个下划线开头的变量名,这样的变量外部可以访问,但是意思是请不要随意访问。


继承

我们定义一个class的时候,可以从某个现有的类继承,新的类为子类subclass,被继承的类为基类,父类或者超类:

class Animal(object):
    def run(self):
        print('Animal is running...')
class Dog(Animal):
    pass

继承的好处是子类获得了父类的全部功能,如果重写方法,相当于覆盖了父类的方法。在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以是父类。

静态语言和动态语言:

class Timer(object):
    def run(self):
        print('Start...')

对于Java那样的静态语言来说,如果需要传入animal类型,则传入的对象必须是animal类型或者它的子类,否则,无法调用run()方法。但是对于Python这样的动态语言来说,则不一定需要传入animal类型,只需要传入的对象有一个run()方法就可以了。
这就是动态语言的鸭子类型,不要求严格的继承体系,一个对象只要看起来像鸭子,走起来像鸭子,就可以被看作是鸭子。

判断对象的类型:

>>> isinstance(h, Husky)
True


深入理解元类metaclass:

在Python和大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段,但是Python中类同样也是一种对象:只要使用class关键字,解释器执行的时候就会在内存中创建一个对象,名字是ObjectCreator,这个对象(类)自身拥有创建对象的能力,这也是它是一个类的原因,但是它的本质仍然是一个对象,既然这是一个对象,那么可以:

  • 可以将它赋值给一个变量
  • 可以拷贝
  • 可以为它增加属性!!!
  • 可以作为函数的参数进行传递

动态的创建类:

>>> def choose_class(name):
…       if name == 'foo':
…           class Foo(object):
…               pass
…           return Foo     # 返回的是类,不是对象,不是类的实例
…       else:
…           class Bar(object):
…               pass
…           return Bar
…
>>> MyClass = choose_class('foo')
>>> print MyClass              # 这里同样得到的也是类,不是对象实例
<class '__main__'.Foo>
>>> print MyClass()            # 加上括号相当于创建了类的实例,所以这里是对象
<__main__.Foo object at 0x89c6d4c>

类也是对象,所以可以动态创建,那么就在函数中创建类,像上面那样。但是这样仍然需要自己编写整个类的代码。这里就开始使用到强大的内建函数type()了,这个函数不仅能让你知道对象的类型是什么,还能动态的创建类

type可以接受一个类的描述作为参数,然后返回一个类:
type(类名,父类的元组(这是假设有继承关系的情况下,可以为null),包含属性的字典(名称和值))

class MyShinyClass(object):  # 这个类可以通过下面的方式手动创建
    pass
MyShinyClass = type('MyShinyClass', (), {})  # 返回一个类对象,这里MyShinyClass作为类名,也当做变量作为类的引用,类与变量不同。
print MyShinyClass()   # 创建该类的实例
--------------

class Foo(object):
    bar = True
Foo = type('Foo', (), {'bar':True})  # 接受字典来定义类的属性
-----------------

class FooChild(Foo):
    pass
FooChild = type('FooChild', (Foo,),{})  # 可以向Foo继承,它的bar属性就继承自父类
-----------------

def echo_bar(self):
    print self.bar
FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})  # 为类增加方法,将函数作为属性赋值就可以了

到底什么是元类:
元类就是用来创建类这种对象的东西,元类就是类的类:

MyClass = MetaClass()
MyObject = MyClass()
MyClass = type('MyClass', (), {})

函数type实际上是一个元类,就是Python在背后用来创建所有类的元类,可以把元类称之为“类工厂”,type是Python的内建元类。

可以在写一个类的时候添加__metaclass__属性:

class Foo(object):  # 首先写下,但是这时候类对象还没有在内存中创建
	__metaclass__ = something…   # 这样会用元类来创建这个Foo类,如果有这个属性就用它创建,如果没有就用内建type创建这个类

具体分析:

class Foo(Bar):
    pass

当写下如上代码的时候,Python首先看Foo中有没有metaclass属性。如果有,会在内存中通过metaclass创建一个名字为Foo的类对象。反之,它会在父类Bar中寻找metaclass属性。如果在任何父类中都找不到metaclass,它就会在模块层次中去寻找metaclass。如果最后最后都还是找不到这个属性,那么就会用内置的type来创建这个类对象。



自定义metaclass:

__metaclass__中可以放置什么代码呢,我们需要放入用来创建类的东西。metaclass的主要用处就是当创建类的时候可以自由的改变类的结构,比如说你想根据当前情形创建符合当前上下文的类。

# 元类会自动将你通常传给‘type’的参数作为自己的参数传入,返回一个类对象,将里面的属性都转换为大写形式
def upper_attr(future_class_name, future_class_parents, future_class_attr):
    #  选择所有不以'__'开头的属性
    attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
Python

    # 将它们转为大写形式
    uppercase_attr = dict((name.upper(), value) for name, value in attrs)

    # 通过'type'来做类对象的创建
    return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr  #  作用到模块中的所有类

----------------------------------------------------------
class Foo(object):
    # 我们也可以只在这里定义__metaclass__,这样就只会作用于这个类中
    bar = 'bip'
print hasattr(Foo, 'bar')
# 输出: False
print hasattr(Foo, 'BAR')
# 输出:True 
f = Foo()
print f.BAR
# 输出:'bip'

上面的例子我们使用了一个函数来当做元类,下面我们使用一个真正的class来当做元类:

# 请记住,'type'实际上是一个类,所以,你可以从type继承
class UpperAttrMetaClass(type):
    # __new__ 是在__init__之前被调用的特殊方法
    # __new__是用来创建对象并返回之的方法
    # 而__init__只是用来将传入的参数初始化给对象
    # 你很少用到__new__,除非你希望能够控制对象的创建
    # 这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
    # 如果你希望的话,你也可以在__init__中做些事情
    def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):
        attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
        # 这里需要使用__new__方法,基本的OOP编程
        return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)

上面注意到参数upperattr_metaclass,类方法的第一个参数总是代表当前的实例,就好比self,在真是的产品代码中一个metaclass如下所示:

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 type.__new__(cls, name, bases, uppercase_attr)
        # 这里我们使用super方法使之更清晰,可以从元类继承,可以从type继承
        return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)

总之,元类的主要用途就是创建API,比如说SQLAlchemy的ORM就是基于元类实现的。Python中的一切都是对象,要么是类的实例,要么是元类的实例。
当不希望使用元类来对类做修改的时候,可以通过其他两种技术来动态的修改类:

  • 猴子补丁
  • 类装饰器
    99%的时间里最好使用上面两种技术来实现对类的修改。

猜你喜欢

转载自blog.csdn.net/qq_43355223/article/details/84333849
今日推荐