python-3.x ジェネレータ - Pythonでの遅延評価





ジェネレータ式 内包表記 (4)


Pythonのパターンwikipediaというgithubリポジトリは、怠惰な評価が何であるかを教えてくれます。

exprの値が必要になるまでexprの評価を遅らせ、繰り返しevalsを避ける。

Python3のrangeは、繰り返しの評価を避けないため、完全な遅延評価ではありません。

遅延評価のより古典的な例は、 cached_propertyです。

import functools

class cached_property(object):
    def __init__(self, function):
        self.function = function
        functools.update_wrapper(self, function)

    def __get__(self, obj, type_):
        if obj is None:
            return self
        val = self.function(obj)
        obj.__dict__[self.function.__name__] = val
        return val

cached_property(aka lazy_property)は、funcを遅延評価プロパティに変換するデコレータです。 最初にアクセスされたプロパティは、関数を呼び出すためにfuncが呼び出され、次にそのプロパティにアクセスするときに値が使用されます。

例えば:

class LogHandler:
    def __init__(self, file_path):
        self.file_path = file_path

    @cached_property
    def load_log_file(self):
        with open(self.file_path) as f:
            # the file is to big that I have to cost 2s to read all file
            return f.read()

log_handler = LogHandler('./sys.log')
# only the first time call will cost 2s.
print(log_handler.load_log_file)
# return value is cached to the log_handler obj.
print(log_handler.load_log_file)

適切な単語を使うために、 範囲のようなpythonジェネレータオブジェクトは、 遅延評価ではなく、 call_by_needパターンによって設計されたものとよく似ています

Pythonで遅延評価とは何ですか?

あるウェブサイトが言った:

Python 3.xでは、 range()関数は、必要に応じてリストの要素を計算する特別な範囲オブジェクトを返します(遅延評価または遅延評価)。

>>> r = range(10)
>>> print(r)
range(0, 10)
>>> print(r[3])
3

これは何を意味していますか?




簡単に言えば、遅延評価は、オブジェクトが作成されたときではなく、必要なときに評価されることを意味します。

Python 2では、rangeがリストを返します。つまり、大きな数値を指定すると、範囲を計算し、作成時に戻ります。

>>> i = range(100)
>>> type(i)
<type 'list'>

しかし、Python 3では、特別な範囲オブジェクトを取得します。

>>> i = range(100)
>>> type(i)
<class 'range'>

あなたがそれを消費するときだけ、それは実際に評価されます - 言い換えれば、あなたが実際にそれらを必要とするときに範囲内の数値を返します。




range() (またはxrange() range()によって返されるオブジェクトはgeneratorとして知られています。

[0,1,2,..,9]範囲全体をメモリに格納する代わりに、ジェネレータは(i=0; i<10; i+=1)定義を格納し、必要なときにのみ次の値を計算します(AKAレイジー評価)。

本質的に、ジェネレータを使用すると構造体のようなリストを返すことができますが、ここではいくつかの違いがあります:

  1. リストには、作成されたすべての要素が格納されます。 ジェネレータは、必要なときに次の要素を生成します。
  2. リストは必要なだけ繰り返すことができ、ジェネレータは1回だけ繰り返すことができます。
  3. リストはインデックスで要素を取得できますが、最初から最後まで値を生成することはできません。

ジェネレータは、次の2つの方法で作成できます。

(1)リストの理解に非常に似ています:

# this is a list, create all 5000000 x/2 values immediately, uses []
lis = [x/2 for x in range(5000000)]

# this is a generator, creates each x/2 value only when it is needed, uses ()
gen = (x/2 for x in range(5000000)) 

(2)関数として、 yieldを使用して次の値を返す:

# this is also a generator, it will run until a yield occurs, and return that result.
# on the next call it picks up where it left off and continues until a yield occurs...
def divby2(n):
    num = 0
    while num < n:
        yield num/2
        num += 1

# same as (x/2 for x in range(5000000))
print divby2(5000000)

注: range(5000000)はPython3.xのジェネレータですが、 range(5000000) [x/2 for x in range(5000000)]はまだリストです。 range(...)はジョブであり、一度に1つずつxを生成しますが、このリストが作成されているときはx/2値のリスト全体が計算されます。




メタクラスはクラスのクラスです。 クラスと同様に、クラスのインスタンスがどのように動作するかを定義します。メタクラスは、クラスの動作を定義します。 クラスはメタクラスのインスタンスです。

Pythonでは( Jerubショーのような)メタクラスに任意の呼び出し可能コードを使用することができますが、実際には実際のクラスそのものにする方が便利です。 typeはPythonの通常のメタクラスです。 あなたが思っているのであれば、 typeはそれ自体がクラスであり、 typeはそれ自身です。 純粋にPythonでtypeようなものを再作成することはできませんが、Pythonはちょっとした詐欺をします。 Pythonで独自のメタクラスを作成するには、実際にtypeをサブクラス化したいだけです。

メタクラスは、クラスファクトリとして最も一般的に使用されます。 クラスを呼び出すことによってクラスのインスタンスを作成するように、Pythonはメタクラスを呼び出して新しいクラス( 'class'文を実行するとき)を作成します。 したがって、通常の__init__メソッドと__new__メソッドを組み合わせることで、新しいクラスをレジストリに登録するなど、クラスを作成するときにメタクラスで「余分なもの」を実行したり、クラスをまったく別のものに置き換えることができます。

class文が実行されると、Pythonはまずclass文の本体を通常のコードブロックとして実行します。 結果として得られる名前空間(dict)は、class-to-beの属性を保持します。 メタクラスは、class-to-be(存在する場合)または__metaclass__グローバル変数の__metaclass__属性で、class-to-be(メタクラスが継承される)のベースクーラスを調べることによって決定されます。 メタクラスは、クラスの名前、基底、属性でインスタンス化され、インスタンス化されます。

しかし、メタクラスは実際にはクラスの型だけを定義するのではなく、クラスのを定義します。 たとえば、メタクラスで通常のメソッドを定義することができます。 これらのメタクラスメソッドは、インスタンスなしでクラスで呼び出すことができるという点でクラスメソッドに似ていますが、クラスのインスタンスで呼び出すことができないというクラスメソッドとは異なります。 type.__subclasses__()は、 typeメタクラスのメソッドの例です。 クラスの振る舞いを実装または変更するために、 __iter____getattr__ __add____iter__などの通常の「魔法」メソッドを定義することもできます。

ここにビットとピースの集約された例があります:

def make_hook(f):
    """Decorator to turn 'foo' method into '__foo__'"""
    f.is_hook = 1
    return f

class MyType(type):
    def __new__(mcls, name, bases, attrs):

        if name.startswith('None'):
            return None

        # Go over attributes and see if they should be renamed.
        newattrs = {}
        for attrname, attrvalue in attrs.iteritems():
            if getattr(attrvalue, 'is_hook', 0):
                newattrs['__%s__' % attrname] = attrvalue
            else:
                newattrs[attrname] = attrvalue

        return super(MyType, mcls).__new__(mcls, name, bases, newattrs)

    def __init__(self, name, bases, attrs):
        super(MyType, self).__init__(name, bases, attrs)

        # classregistry.register(self, self.interfaces)
        print "Would register class %s now." % self

    def __add__(self, other):
        class AutoClass(self, other):
            pass
        return AutoClass
        # Alternatively, to autogenerate the classname as well as the class:
        # return type(self.__name__ + other.__name__, (self, other), {})

    def unregister(self):
        # classregistry.unregister(self)
        print "Would unregister class %s now." % self

class MyObject:
    __metaclass__ = MyType


class NoneSample(MyObject):
    pass

# Will print "NoneType None"
print type(NoneSample), repr(NoneSample)

class Example(MyObject):
    def __init__(self, value):
        self.value = value
    @make_hook
    def add(self, other):
        return self.__class__(self.value + other.value)

# Will unregister the class
Example.unregister()

inst = Example(10)
# Will fail with an AttributeError
#inst.unregister()

print inst + inst
class Sibling(MyObject):
    pass

ExampleSibling = Example + Sibling
# ExampleSibling is now a subclass of both Example and Sibling (with no
# content of its own) although it will believe it's called 'AutoClass'
print ExampleSibling
print ExampleSibling.__mro__




python python-3.x lazy-evaluation