I've learned. I'll share.

February 12, 2008

Pysec: Monadic Combinatoric Parsing in Python (aka Parsec in Python)

Update: It turns out that there's a similar technique being used by pyparsing. I hadn't seen it before and when I first saw it I thought had reinvted the wheel and wasted my time. But upon further inspection, Pysec does a few things a much better than pyparsing, which happen to be the exact things that I need. There's no coincedence, of course, that Pysec matches my needs well. I'll be covering this in more detail in a future article.

Update 2: I got @do syntax to work! Again, stay tuned for another article on how.

I've talked about monads in the past, but I never really covered what purpose they serve. I covered the "how" in Python and Ruby, but I doubt I'll ever full cover the "why" because it's simply too big of a subject. But today, I'd like to share with you one example of how monads are useful. In fact, it's the example that motivated me to do all of the monad stuff in the first place: parsing.

Parsing is a topic that's been around pretty much forever in computer science and most people thing it's pretty much "solved". My experience is that we've still got a long way to go. Specifically, I'm writing an application with lots of distributed concurrency, which requires lots of data serialization and deserialization (aka parsing). There are very few good serialization libraries out there, and I've been through three or four versions of various techniques. Finally, I think I have found a parsing technique that works well: monadic combinatoric parsing. And it's in Python.

What the heck does that mean? "monadic" means we're using monads. "combinatoric" means we can take monad parsers and combine them to make new monad parsers, which is extremly powerful. I call it Pysec. The design is a copy of Parsec brought to Python. Notice how I said "design"; I didn't bother looking at any of their code; The design described on their web page was good enough guidance for me. But, I'm sure that their implementation is WAY better than mine. If you want to see real monadic parsing, look at Parsec. If you're interested in monadic parsing for Python, keep reading.

Here's an example of Pysec for parsing a subset of JSON:

from Pysec import Parser, choice, quoted_chars, group_chars, option_chars, digits, between, pair, spaces, match, quoted_collection

# json_choices is a hack to get around mutual recursion 
# a json is value is one of text, number, mapping, and collection
# text is any characters between quotes
# a number is like the regular expression -?[0-9]+(\.[0-9]+)?
# "parser >> Parser.lift(func)" means "pass the parsed value into func and return a new Parser"
# quoted_collection(start, space, inner, joiner, end)
#   means "a list of inner separated by joiner surrounded by start and end"
# we have to put a lot of "spaces" in since JSON allows lot of optional whitespace

json_choices = []
json         = choice(json_choices)
text         = quoted_chars("'", "'")
number       = group_chars([option_chars(["-"]), digits, option_chars([".", digits])]) >> Parser.lift(float)
joiner       = between(spaces, match(","), spaces)
mapping_pair = pair(text, spaces & match(":") & spaces & json)
collection   = quoted_collection("[", spaces, json,         joiner, "]") >> Parser.lift(list)
mapping      = quoted_collection("{", spaces, mapping_pair, joiner, "}") >> Parser.lift(dict)
json_choices.extend([text, number, mapping, collection])

print json.parseString("{'a' : -1.0, 'b' : 2.0, 'z' : {'c' : [1.0, [2.0, [3.0]]]}}")

Like most monadic or functional code, it's pretty dense, so don't feel bad if you go cross-eyed looking at it the first time. Realize that most of the code is building a Parser monad called "json", which parses the test string at the end. I tried to comment each individual part to explain what's going on.

You may be thinking "why would I want to write code like this?". One response is to look at the code: it's the bulk of JSON parsing in 15 lines that look like a grammar definition! Another response I can give is a challange: go write a parser to parse that string and then compare your code to this code. Which is shorter? Which is easier to read? Which is more elegant? While in college, I had a team project to write a compiler for a subset of Pascal. We were smart enough to use Python, but dumb enough to use Yacc and Flex. I'm sure the parser portion was pretty fast, but it was incredibly painful to get it right. Once we did, we dared not touch it for fear of breaking it. I really wish I had Parse/Pysec back then (ok, Parsec was around back then, but I hadn't even heard of Haskell or monads).

But monadic combinatoric parsing isn't just about making your code look like a grammar definition. It makes it possible to combine parsers in incredibly flexible ways. For example, let's say that on a differnt project, you wrote a simplified CSV parser for numbers like this one:

def line(cell):
    return sep_end_by(cell, match(","))

def csv(cell):
    return sep_end_by(line(cell), match("\n"))

print csv(number).parseString("1,2,3\n4,5,6")

And now you realize you'd really like to put whole JSON values in your simplified CSV. In other words, you want to combine the CVS and JSON parsers. I think that you'll find that doing so really isn't as easy as it sounds. Imagine trying to combine two Yacc grammars. It hurts just thinking about that. Luckily, monadic combinatoric parsers make this incredibly easy:

print csv(json).parseString("{'a' : 'A'},[1, 2, 3],'zzz'\n-1.0,2.0,-3.0")

While this is a slighly contrived example, you must understand that with this technique you can combine any two parsers in a similar fasion. I don't know about you, but that really feels right to me. I haven't seen any parsing techniques as elegant as that.

Everything it's perfect of course, especially since making this work in Python is a bit of a hack. Here are some downsides to this approach:

  • It's hard to debug when you do something wrong.
  • It's annoying to import so many things from Pysec.
  • You have to hack around mutual recursion of values in a strict language like python.
  • There's probably a performance hit.

Most of these can be either fixed or worked around, though, so I think long-term monadic parsing is good bet. I'd like to see what you can do with it or to make it better.

I know how much you all like a working example, so here's some code that you can just cut, paste, and run (in Python 2.5; older versions of Python may require some tweaking). Please ignore the top half. It's mostly stuff I've covered in other articles, like Immutable Records and monad base classes. The meat starts where it says "Parser Monad".

I'd like to talk about it in more detail, but I'll save that for another article. For now, please play around with it and see what you think.

##### Base Libraries included here for convenience ###########

def Record(*props):
    class cls(RecordBase):
        pass

    cls.setProps(props)

    return cls

class RecordBase(tuple):
    PROPS = ()

    def __new__(cls, *values):
        if cls.prepare != RecordBase.prepare:
            values = cls.prepare(*values)
        return cls.fromValues(values)

    @classmethod
    def fromValues(cls, values):
        return tuple.__new__(cls, values)

    def __repr__(self):
        return self.__class__.__name__ + tuple.__repr__(self)

    ## overridable
    @classmethod
    def prepare(cls, *args):
        return args

    ## setting up getters and setters
    @classmethod
    def setProps(cls, props):
        for index, prop in enumerate(props):
            cls.setProp(index, prop)
        cls.PROPS = props

    @classmethod
    def setProp(cls, index, prop):
        getter_name = prop
        setter_name = "set" + prop[0].upper() + prop[1:]

        setattr(cls, getter_name, cls.makeGetter(index, prop))
        setattr(cls, setter_name, cls.makeSetter(index, prop))

    @classmethod
    def makeGetter(cls, index, prop):
        return property(fget = lambda self : self[index])

    @classmethod
    def makeSetter(cls, index, prop):
        def setter(self, value):
            values = (value if current_index == index
                            else current_value
                      for current_index, current_value
                      in enumerate(self))
            return self.fromValues(values)
        return setter

class ByteStream(Record("bytes", "index")):
    @classmethod
    def prepare(cls, bytes, index = 0):
        return (bytes, index)

    def get(self, count):
        start = self.index
        end   = start + count
        bytes = self.bytes[start : end]
        return bytes, (self.setIndex(end) if bytes else self)

def make_decorator(func, *dec_args):
    def decorator(undecorated):
        def decorated(*args, **kargs):
            return func(undecorated, args, kargs, *dec_args) 
        
        decorated.__name__ = undecorated.__name__
        return decorated
    
    decorator.__name__ = func.__name__
    return decorator

decorator = make_decorator

class Monad:
    ## Must be overridden
    def bind(self, func):
        raise NotImplementedError

    @classmethod
    def unit(cls, val):
        raise NotImplementedError

    @classmethod
    def lift(cls, func):
        return (lambda val : cls.unit(func(val)))

    ## useful defaults that should probably NOT be overridden
    def __rshift__(self, bindee):
        return self.bind(bindee)

    def __and__(self, monad):
        return self.shove(monad)
        
    ## could be overridden if useful or if more efficient
    def shove(self, monad):
        return self.bind(lambda _ : monad)

class StateChanger(Record("changer", "bindees"), Monad):
    @classmethod
    def prepare(cls, changer, bindees = ()):
        return (changer, bindees)

    # binding can be slow since it happens at bind time rather than at run time
    def bind(self, bindee):
        return self.setBindees(self.bindees + (bindee,))

    def __call__(self, state):
        return self.run(state)

    def run(self, state0):
        value, state = self.changer(state0) if callable(self.changer) else self.changer
        state        = state0 if state is None else state

        for bindee in self.bindees:
            value, state = bindee(value).run(state)
        return (value, state)

    @classmethod
    def unit(cls, value):
        return cls((value, None))

######## Parser Monad ###########

class ParserState(Record("stream", "position")):
    @classmethod
    def prepare(cls, stream, position = 0):
        return (stream, position)

    def read(self, count):
        collection, stream = self.stream.get(count)
        return collection, self.fromValues((stream, self.position + count))

class Parser(StateChanger):
    def parseString(self, bytes):
        return self.parseStream(ByteStream(bytes))
        
    def parseStream(self, stream):
        state = ParserState(stream)
        value, state = self.run(state)
        return value

class ParseFailed(Exception):
    def __init__(self, message, state):
        self.message = message
        self.state   = state
        Exception.__init__(self, message)

@decorator
def parser(func, func_args, func_kargs):
    def changer(state):
        return func(state, *func_args, **func_kargs)
    changer.__name__ = func.__name__
    return Parser(changer)

##### combinatoric functions #########

@parser
def tokens(state0, count, process):
    tokens, state1 = state0.read(count)

    passed, value = process(tokens)
    if passed:
        return (value, state1)
    else:
        raise ParseFailed(value, state0)
    
def read(count):
    return tokens(count, lambda values : (True, values))

@parser
def skip(state0, parser):
    value, state1 = parser(state0)
    return (None, state1)

@parser
def option(state, default_value, parser):
    try:
        return parser(state)
    except ParseFailed, failure:
        if failure.state == state:
            return (default_value, state)
        else:
            raise
        
@parser
def choice(state, parsers):
    for parser in parsers:
        try:
            return parser(state)
        except ParseFailed, failure:
            if failure.state != state:
                raise failure
    raise ParseFailed("no choices were found", state)

@parser
def match(state0, expected):
    actual, state1 = read(len(expected))(state0)
    if actual == expected:
        return actual, state1
    else:
        raise ParseFailed("expected %r" % (expected,), state0)

def between(before, inner, after):
    return before & inner >> (lambda value : after & Parser.unit(value))

def quoted(before, inner, after):
    return between(match(before), inner, match(after))

def quoted_collection(start, space, inner, joiner, end):
    return quoted(start, space & sep_end_by(inner, joiner), end)

@parser
def many(state, parser, min_count = 0):
    values = []

    try:
        while True:
            value, state = parser(state)
            values.append(value)
    except ParseFailed:
        if len(values) < min_count:
            raise

    return values, state
    
@parser
def group(state, parsers):
    values = []

    for parser in parsers:
        value, state = parser(state)
        values.append(value)

    return values, state

def pair(parser1, parser2):
    # return group((parser1, parser2))
    return parser1 >> (lambda value1 : parser2 >> (lambda value2 : Parser.unit((value1, value2))))

@parser
def skip_many(state, parser):
    try:
        while True:
            value, state = parser(state)
    except ParseFailed:
        return (None, state)

def skip_before(before, parser):
    return skip(before) & parser

@parser
def skip_after(state0, parser, after):
    value, state1 = parser(state0)
    _,     state2 = after(state1)
    return value, state2

@parser
def option_many(state0, first, repeated, min_count = 0):
    try:
        first_value, state1 = first(state0)
    except ParseFailed:
        if min_count > 0:
            raise
        else:
            return [], state0
    else:
        values, state2 = many(repeated, min_count-1)(state1)
        values.insert(0, first_value)
        return values, state2

# parser separated and ended by sep
def end_by(parser, sep_parser, min_count = 0):
    return many(skip_after(parser, sep_parser), min_count)

# parser separated by sep
def sep_by(parser, sep_parser, min_count = 0):
    return option_many(parser, skip_before(sep_parser, parser), min_count)
    
# parser separated and optionally ended by sep
def sep_end_by(parser, sep_parser, min_count = 0):
    return skip_after(sep_by(parser, sep_parser, min_count), option(None, sep_parser))

##### char-specific parsing ###########

def satisfy(name, passes):
    return tokens(1, lambda char : (True, char) if passes(char) else (False, "not " + name))

def one_of(chars):
    char_set = frozenset(chars)
    return satisfy("one of %r" % chars, lambda char : char in char_set)

def none_of(chars):
    char_set = frozenset(chars)
    return satisfy("not one of %r" % chars, lambda char : char and char not in char_set)

def maybe_match_parser(parser):
    return match(parser) if isinstance(parser, str) else parser

def maybe_match_parsers(parsers):
    return tuple(maybe_match_parser(parser) for parser in parsers)

def many_chars(parser, min_count = 0):
    return join_chars(many(parser, min_count))

def option_chars(parsers):
    return option("", group_chars(parsers))

def group_chars(parsers):
    return join_chars(group(maybe_match_parsers(parsers)))
    #return join_chars(group(parsers))

def join_chars(parser):
    return parser >> Parser.lift("".join)

def while_one_of(chars, min_count = 0):
    return many_chars(one_of(chars), min_count)

def until_one_of(chars, min_count = 0):
    return many_chars(none_of(chars), min_count)

def char_range(begin, end):
    return "".join(chr(num) for num in xrange(ord(begin), ord(end)))

def quoted_chars(start, end):
    assert len(end) == 1, "end string must be exactly 1 character"
    return quoted(start, many_chars(none_of(end)), end)

digit  = one_of(char_range("0", "9"))
digits = many_chars(digit, min_count = 1)
space  = one_of(" \v\f\t\r\n")
spaces = skip_many(space)


############# simplified JSON ########################

#from Pysec import Parser, choice, quoted_chars, group_chars, option_chars, digits, between, pair, spaces, match, quoted_collection, sep_end_by

#HACK: json_choices is used to get around mutual recursion 
#a json is value is one of text, number, mapping, and collection, which we define later 
json_choices = []
json         = choice(json_choices)

#text is any characters between quotes
text         = quoted_chars("'", "'")

#sort of like the regular expression -?[0-9]+(\.[0-9]+)?
#in case you're unfamiliar with monads, "parser >> Parser.lift(func)" means "pass the parsed value into func but give me a new Parser back"
number       = group_chars([option_chars(["-"]), digits, option_chars([".", digits])]) >> Parser.lift(float)

#quoted_collection(start, space, inner, joiner, end) means "a list of inner separated by joiner surrounded by start and end"
#also, we have to put a lot of spaces in there since JSON allows lot of optional whitespace
joiner       = between(spaces, match(","), spaces)
mapping_pair = pair(text, spaces & match(":") & spaces & json)
collection   = quoted_collection("[", spaces, json,         joiner, "]") >> Parser.lift(list)
mapping      = quoted_collection("{", spaces, mapping_pair, joiner, "}") >> Parser.lift(dict)

#HACK: finish the work around mutual recursion
json_choices.extend([text, number, mapping, collection])


############# simplified CSV ########################

def line(cell):
    return sep_end_by(cell, match(","))

def csv(cell):
    return sep_end_by(line(cell), match("\n"))

############# testing ####################

print json.parseString("{'a' : -1.0, 'b' : 2.0, 'z' : {'c' : [1.0, [2.0, [3.0]]]}}")
print csv(number).parseString("1,2,3\n4,5,6")
print csv(json).parseString("{'a' : 'A'},[1, 2, 3],'zzz'\n-1.0,2.0,-3.0")

216 comments:

  1. that's really good!! lets kill regular expression :)

    ReplyDelete
  2. How's this different from pyparsing?

    ReplyDelete
  3. Congratulations on a nice little parsing library. For comparison, here is a pyparsing JSON parser (http://pyparsing.wikispaces.com/space/showimage/jsonParser.py). It should look very familiar to you! :)

    Come visit the pyparsing wiki, the concepts are very similar. One of the big differences is that your monads are implemented in functions, and pyparsing uses class instances. By using objects, it is easier to attach additional behavior (such as parse actions and field names) to the parser expression monads. Also, pyparsing creates a very rich return type, the ParseResults class, instead of just returning nested lists of tokens.

    ReplyDelete
  4. Paul, I had no idea pyparsing even existed. I hate reinventing the wheel and searched far and wide for parsing tools, but pyparsing never came up. Now when I Google for "python parsing", I see that pyparsing is the last result on the first page, but I guess my eyes missed it when I looked before. Shame on me.

    I congratulate you on your work. It looks great. It's almost what I was trying to accomplish, but certainly more mature. Had I known about it, I may have used it instead, although I'm glad that I could explore monadic parsing as well.

    But, I have some peculiar needs in a parser that I wrote using Pysec. I'd like to see if pyparsing can do it also, but there's not much documentation that I can find on the wiki. Would you mind answering a few questions? You can respond here or direct me to a better place to have a discussion.

    Here's what I'm wondering if pyparsing is capable of:

    1. I need to read size-prefixed binary data, much like what's found in the bittorrent format called bencode. It looks like this:

    b7:XXXXXXX

    Where each X is a byte. The parser has to read the 7, parse it, and then read 7 bytes, no matter what they are, and then continue parsing from there. Can pyparsing do this? In Pysec, it looks a bit like this:

    (match("b") & integer) >> (lambda size : (match(":") & read(size)))

    2. I need something a lot like macro expansion, except that rather then expand the reference into the text of the anchor, I need it to expand into the parsed value of the anchor. So, for example, I need to parse something that looks like this:

    [&a [1, 2, 3], @a, @a]

    Into what you would get if you did this in python:

    a = [1, 2, 3]
    [a, a, a]

    It requires keeping a table of parsed values, but that's easy because a Pysec Parser is really a StateTransformer monad, so we just carry an inner state in around with the ParserState.

    Can pyparsing do this?

    Thanks in advance Paul.

    ReplyDelete
  5. Peter -

    For an implementation of the size-prefixed binary data, see:
    http://pyparsing.pastebin.com/m72db9ab6

    As for the second question, I don't quite understand what you are describing, but there is a macroProcessor example on the pyparsing wiki Examples page.

    Pyparsing has epydoc-generated class docs, UML diagrams, and two presentations I made at Pycon06, that are included with the source or docs release on SourceForge. If you go to the Documentation page of the wiki, there are some links to articles, blog entries, and if you want to spring for $10, a ShortCut on O'Reilly.

    Post a comment on the wiki home page Discussion tab, if you want to pursue item #2 further.

    -- Paul

    ReplyDelete
  6. Paul, thanks for your response. It helped me compare pyparsing and Pysec. I'll have to write another article comparing the two, but here's what I found initially:

    1. It can parse b7:XXXXXXX, but it's a hack. I'm impressed you figured out such a hack so quickly, but it essentially requires modifying a global variable to work.

    2. It can parse [&a [1, 2, 3] @a @a]. A tweaking of the macro expansion on the wiki is sufficient. But I'm calling this one a hack too because it also requires modifying a global value (the macro table).

    So, it works for my purposes, but I don't really like how. Admittedly, the format I'm parsing isn't definable as a grammar, and so a parsing library built around the idea of a grammar doesn't match too well.

    ReplyDelete
  7. Don't be too impressed. The only reason I was able to crank this out so quickly was because it was mostly a replication of a helper function already defined in pyparsing, called countedArray. Now that you've see the other implementation, look at the implementation of countedArray (here: http://pyparsing.pastebin.com/m278ec49a). It also uses a "global" value (the variable arrayExpr), but at least it hides it inside the countedArray method.

    My experience with macro expansion is that it is not a parser-friendly process in general. For instance, unless your parser engine allows you to back up to reparse generated text, you have to perform multiple passes on the input source - one to do macro expansion (potentially recursively, which should still be doable in a single pass), and then a second pass to parse the macro-expanded code. I don't really feel too bad about this, C compilers have to do much the same thing.

    -- Paul

    ReplyDelete
  8. Perhaps I can characterize Pysec/Parsec as a way to build parsers which are macro-expansion-friendly.
    I think the general case of this is that they allow parsers to change behavior based on what as already been parsed, which, in a way, is viewing the parsed text less as a language of a grammar and more as processing instructions.

    ReplyDelete
  9. Hi Peter!

    I've just come across Pysec - a great parser library, I love it!

    I've known about Haskell's Parsec for some time (and that's very impressive too), but it is good to see a similar library in a somewhat-more-familiar language. Haskell has a bit of a learning-curve to it.... ;)

    I'm very keen to use this library in the near-future. I was wondering about the license - is it "public domain"? ( I always give credit anyway... )
    Many thanks for this post (and for a great library!
    - Andy

    ReplyDelete
  10. Andy,

    I'm glad you like it. Unfortunately, for my main use, Pysec too slow (I made a blog entry about this called "why are my monads so slow?"), and so I haven't worked on it at all.

    Hopefully it will be fast enough and work well enough for your needs. You're free to do whatever you want with it. Consider it "public domain", I guess.

    ReplyDelete
  11. Hi Peter -
    Great! Thanks very much for your reply!
    Pysec is a great little app when you're getting into Python and are also keen on FP (as I am).
    As a token of thanks, here's a link to another *very cool* Python parsing app. It's called "yeanpypa" (YEt ANother PYthon PArser lib). Very nicely done - it is inspired by the C++ Boost::Spirit parser library (which I have a fair bit of experience with, and which rocks!).
    Yeanpypa doesn't use an FP approach, but it's quite a nice "BNF"-y style parser.
    http://freshmeat.net/projects/yeanpypa/

    ReplyDelete
  12. how do you tell whether the whole input stream was parsed?

    ReplyDelete
  13. ah this is how:

    value, state = expr.run(ParserState(ByteStream(inputline)))
    if state.position != len(inputline):
    raise ParseError("couldn't parse entire line %s, left over: %s" % (
    inputline, inputline[state.position:]))

    ReplyDelete
  14. There is a rather subtle error in digits caused by char_range() caused by xrange(), it accept the digits only '012345678' as 9 is excluded in xrange() from the generated list.
    """return "".join(chr(num) for num in xrange(ord(begin), ord(end)))""" replacing with """return "".join(chr(num) for num in xrange(ord(begin), ord(end)+1))""" feels like the most natural fix. The error message isn't particularly useful either for spotting this error.

    ReplyDelete
  15. this is very helpful post.thanks for sharing : usefull site

    ReplyDelete
  16. This part of code wa helpful for finishing a huse part of my program. Thank you very much. I'd suggest you to read history research paper topics in case you need one to choose.

    ReplyDelete
  17. There are no doubts that writing elites can help in academic future.

    ReplyDelete
  18. Sometimes emergency help needs in studies. And reliable writing service can help. Just buy discussion board post and receive it due date you set.

    ReplyDelete
  19. Have no time to write term papers? Keep in mind that you can always rely on professionals and buy case study paper from reliable writing service.

    ReplyDelete
  20. Are you having a challenge writing research papers check out some free essay samples they will be give you a guideline on how to go about it, try us today

    ReplyDelete
  21. Thank you for such a great post. Also, I want to share one cool website that is called https://essaysprofessor.com/essay-cheap.html. This website offers a professional help with writing essays.

    ReplyDelete
  22. Hey, blogger, you have an awesome website and its content. I personally read your posts and like them. All of them are informative for me. Do visit to us too DQfansurvey

    ReplyDelete

  23. Hey Your site is awesome. I have read you post they are so informative. Keep Posting wonderful content. Also visit us Seo Expert in Pakistan

    ReplyDelete
  24. It’s hard to come by experienced people about this subject, but you seem like you know what you’re talking about! Thanks
    getmyoffersguide.com

    ReplyDelete
  25. Hey Thanks for sharing this valuable information with us. I will come back to your site and keep sharing this information with us.
    Captions
    captions
    Captions
    captions

    ReplyDelete
  26. Amazing website. Great information provided. Learned a lot. Thank you
    https://platinumoffersonline.com/

    ReplyDelete
  27. The topic of this post is very interesting for me. I am very much impressed after reading this post and I must appreciate your effort in sharing this post with us here.
    Best affordable SEO Services

    ReplyDelete
  28. Nice blog, thanks for sharing. Please Update more blog about this, this is really informative for me as well. Visit for Website Designing Services at Ogen Infosystem.
    Website Development Company in Delhi

    ReplyDelete
  29. Nice blog, Get the mutual fund benefits and there investment schemes at Mutual Fund Wala.
    Best Performing Mutual Fund

    ReplyDelete
  30. This blog is useful as well as informative. Keep sharing such blogs I really like your posts.
    Manage Your Google Ads Campaign

    ReplyDelete
  31. Keep more update, I’ll wait for your next blog information. Thank you so much for sharing with us.
    Lifestyle Magazine India

    ReplyDelete
  32. AI is having an intense effect on the society we live in, with distinct applications developing all the time. Ingenious developers are adopting Python as their go-to programming language for the benefit that makes it particularly suitable for machine learning and deep learning projects.

    While different programming languages can also be used in AI projects, there is none getting away from the fact that Python is at the cutting edge, and should be provided significant reflection. This is why you should definitely consider Python for your AI contour.Artificial Intelligence Development Agency

    ReplyDelete
  33. This blog is useful as well as informative. Keep sharing such blogs I really like your posts.
    dog bandana

    ReplyDelete
  34. Thanks for sharing this information with us and we will revisit your website also visit www.krogerexperience.com

    ReplyDelete
  35. Wonderful blog post. I was viewing continuously this website and I am satisfied! Highly beneficial information particularly the last part. I take care of this kind of information much. I was seeking this specific information for a long time. Thanks and good luck.
    Facebook Campaign Management

    ReplyDelete
  36. It’s hard to come by experienced people about this subject, but you seem like you know what you’re talking about! Thanks
    https://myinstantofferonline.com/
    https://www.peryourhealthonline.com/
    https://www.myindigocardoffer.com/

    ReplyDelete
  37. Nice blog, Visit Kalakutir Pvt Ltd for the best Commercial Vehicle Painting & Branding and Base Company Logo Painting.
    Commercial Vehicle Painting & Branding

    ReplyDelete
  38. Hey, I loved the way you shared the valuable information with the community. I would say that please continue these efforts and we want to hear more from you.
    HamzaAndHamzaTaxReturns

    ReplyDelete

  39. Here are some of the bestcute iphone xr cases cheap. Check out our cute protective iphone xs max cases. Click here to see iphone x protective case. These are very best cute protective iphone 8 plus cases. protect your new iPhone with the best floral iphone 7 plus cases. are you looking for girly iphone 6 case? Do you like to see these jewelries? We have lots of Best iPhone Screen Protectors.

    ReplyDelete
  40. thanks for educating yourself and sharing with the rest of us. This will be a guide for many of us. www.discover.com/activate

    ReplyDelete
  41. I’m really impressed with your blog article, such great & useful knowledge you mentioned here.
    Keep up the great work.

    Eye lens price in Pakistan 2019

    ReplyDelete
  42. Really it is a very nice topic and Very significant Information for us, I have think the representation of this Information is actually super one. . SEO Services

    ReplyDelete
  43. Awesome blog, get responsive Website Designing Company in delhi by Ogen Infosystem in cheap rate and also get SEO Service.
    SEO Service in Delhi

    ReplyDelete
  44. It so good idea so i appreciate it and its a good thingstore of generators

    ReplyDelete
  45. تعد شركة المجد من افضل وأرخص الشركات التي تقدم أفل الخدمات المنزلية على الإطلاق في المنطقة الجنوبية شركتنا هي الشركة الأفضل والأقوى على مدار العشرة سنوات الماضية خبراتها الكبيرة التي تقدمها الشركه في مجال التنظيف والمكافحة عليكم بالتواصل معنا الأن .
    شركة مكافحة حشرات بنجران
    شركة تنظيف خزانات بنجران
    شركة تنظيف فلل بنجران
    شركة نقل اثاث بخميس مشيط

    ReplyDelete
  46. i think it is very best thing and it s better for us so visit itNo.1 monthly seo services

    ReplyDelete
  47. Appreciating the hard work you put into your site and detailed information you offer. It’s nice to come across a blog every once in a while that isn’t the same out of date rehashed material Generators for Sale

    ReplyDelete
  48. I think it is so good thing so it is very useful forn you Monthly Seo Servic

    ReplyDelete
  49. Amazing it is a very helpful topic and Very significant Information for us, thanks for sharing new metro city

    ReplyDelete
  50. This blog is useful as well as informative. Keep sharing such blogs I really like your posts. lot Instagram affiliate marketing

    ReplyDelete
  51. Your information about "Monadic Combinatoric Parsing" very informative. I really enjoy your articles. new metro city

    ReplyDelete
  52. Hi Thanks for sharing this informative post here.
    hostgator blackfriday
    aaanetaccess/activate
    Healthfusion login
    LEGO Black Friday
    Here you shared all the informative details.
    great Yeah !

    ReplyDelete
  53. Appreciating the hard work you put into your site and detailed information you offer Bluetooth Headset

    ReplyDelete
  54. موقع موسوعة الحل www.el77l.com
    رقم جوال ابشر 1440 طريقة تعديل رقم الجوال في أبشر
    استعلام الرصيد المتبقي الجوازات
    كيف اعرف مستحقاتي في التأمينات
    كيف اعرف اني في سما
    كيف افتح نظام نور طريقة التسجيل في حافز للمره الثانيه
    طريقة التسجيل في ابشر
    مساند تسجيل دخول افراد
    كيف اعرف مستحقاتي في الموارد البشرية
    رقم الوليد بن طلال الواتس اب
    مساند دخول
    تسجيل جديد في برنامج مساند
    استعلام عن اقساط بنك التسليف هوامير
    مساند تسجيل دخول افراد جديد
    الاستعلام عن تصريح الحج برقم الهويه
    استعلام بي رقم الهويه في التامينات
    الاستعلام عن المخالفات المرورية لسيارات الشركات
    الوليد للإنسانية اتصل بنا
    الاستعلام عن صلاحية الجواز للمقيمين
    موسوعة الحل el77l.com
    استعلام عن المخالفات المرورية هنا الكويت
    التواصل مع الوليد بن طلال مباشره
    اسّتّعّلُامُ عّنَ ؤٌافُدً
    كيف اعرف حالتي في المساعده المقطوعه
    مساند تسجيل دخول
    مخالفات المرور الكويت برقم المدني
    اخلاء طرف بنك التسليف
    كيف اعرف انه تم نقل كفالتي عن طريق النت
    توقع صرف المساعدة المقطوعة
    الطريقه الصحيحه للاستعلام عن المقطوعه
    دخول مساند
    الاستعلام عن تأشيرة السعودية برقم الجواز
    مساند دخول افراد

    ReplyDelete
  55. Keep sharing such a nice blog i feel pleasure when i read this post.
    iphone wont turn on
    transfer iphone to android

    ReplyDelete
  56. Hi There, love your site layout and especially the way you wrote everything. I must say that you keep posting this type of information so that we may see the latest newsCobone Discount codes

    ReplyDelete
  57. This comment has been removed by the author.

    ReplyDelete
  58. Hi There, love your site layout and especially the way you wrote everything. I must say that you keep posting this type of information so that we may see the latest news Wireless Bluetooth Earbud

    ReplyDelete
  59. تمتع الان باحلي العروض و منتجات و افخم سلسه عطور التي تحتاجها كل سيده من خلال كوبون خصم فيكتوريا باقل الاسعار و تكاليف يمكنك شراء كل ما تريد تواصل الان و احصل علي كود خصم فيكتوريا

    ReplyDelete
  60. Hi, I do believe this is a great web site. I stumbledupon it ;) I'm going to come back once again since i have saved as a favorite it. Money and freedom is the greatest way to change, may you be rich and continue to help others.
    kbc official winner

    ReplyDelete
  61. Hi, I do think this is a great website. I stumbledupon it ;) I am going to revisit once again since i have saved as a favorite it. Money and freedom is the best way to change, may you be rich and continue kbc official head office number to help other people.

    ReplyDelete
  62. Получить последние С Новым годом пожелания, изображения, цитаты, статус, изображения 2020 на
    https://happynewyears2020.info/

    ReplyDelete
  63. Buy online victoria secret bras for women and teens in usa

    ReplyDelete
  64. Appreciating the hard work you put into your site and detailed information you offer. Roofers Bronx

    ReplyDelete
  65. I know you go straight out of it then you look forward with that also keep posting.

    ReplyDelete
  66. I've talked about monads in the past, but I never really covered what purpose they serve. I covered the "how" in Python and Ruby, but I doubt I'll ever full cover the "why" because it's simply too big of a subject. But today, I'd like to share with you one example of how monads are useful. In fact, it's the example that motivated me to do all of the monad stuff in the first place: parsing. New year Quotes 2020

    New year Quotes for girlfriend


    New year Quotes 2021

    New year Quotes 2020

    ReplyDelete
  67. Thanks for sharing Python with simple way. and wishing you a happy new year 2020 wallpaper to you.

    ReplyDelete

  68. Thank you! I hope to hear more updates from you, this is what I want to find.
    https://www.customersatisfactionsurveys.org/

    ReplyDelete
  69. Nice post, thanks for sharing, pleas visit also:bliss Shine is about an Informative site where anyone can get authentic information about News, Education, Entertainment, Health, Search Engine Optimizer SEO, Lifestyle, Scientific Knowledge, Online Earning, Funny Videos, Articles, etc… We try to bring new and best content for our visitors, who are searching for all authentic knowledge. Bliss Shine ensures the quality of content and use-full for all people.
    Payday Loans |
    Insurance

    ReplyDelete
  70. I have recently started a blog, the info you provide on this site has helped me greatly in
    blogging. Thanks for all of your work and timeCommercial Roofing Brooklyn

    ReplyDelete
  71. Your blog is great! I really enjoyed reading it, it has helped me very much Shed Base

    ReplyDelete
  72. Desktop as a service (DaaS) is a cloud computing offering in which a third party hosts the back end of a virtual desktop infrastructure (VDI) deployment.

    With DaaS, desktop operating systems run inside virtual machines on servers in a cloud provider's data center. All the necessary support infrastructure, including storage and network resources, also lives in the cloud. As with on-premises VDI, a DaaS provider streams virtual desktops over a network to a customer's endpoint devices, where end users may access them through client software or a web browser.

    How does desktop as a service work?
    DaaS architecture is multi-tenant, and organizations purchase the service through a subscription model -- typically based on the number of virtual desktop instances used per month.

    In the desktop-as-a-service delivery model, the cloud computing provider manages the back-end responsibilities of data storage, backup, security and upgrades. While the provider handles all the back-end infrastructure costs and maintenance, customers usually manage their own virtual desktop images, applications and security, unless those desktop management Desktop as a Services ervices are part of the subscription.

    Typically, an end user's personal data is copied to and from their virtual desktop during logon and logoff, and access to the desktop is device-, location- and network-independent.

    VDI vs. DaaS
    Desktop as a service provides all the advantages of virtual desktop infrastructure, including remote worker support, improved security and ease of desktop management.

    Further, DaaS aims to provide additional cost benefits. Deploying VDI in-house requires a significant upfront investment in compute, storage and network infrastructure. Those costs have decreased, however, thanks to the emergence of converged and hyper-converged infrastructure systems purpose-built for VDI.

    With DaaS, on the other hand, organizations pay no upfront costs. They only pay for the virtual desktops they use each month. Over time, however, these subscription costs can add up and eventually be higher than the capital expenses of deploying on-premises VDI.

    Additionally, some advanced virtual desktop management capabilities may not be available for certain DaaS deployments, depending on the provider.

    ReplyDelete
  73. I came across your website from a forum link and now I'm loving the blogs, love it. thanks for sharing.
    TikTok Auto Liker
    TikTok Auto Liker
    Export Instagram Comments

    ReplyDelete
  74. Hey There. I found your blog using msn. This is a very well written article. I’ll be sure to bookmark it and come back to read more of your useful info. Thanks for the post. I’ll definitely return.
    Happy Valentines Day Images HD
    Happy Valentines Day Pictures

    ReplyDelete
  75. Your blog is great! I really enjoyed reading it, it has helped me very much Online Quran Classes for kids

    ReplyDelete
  76. Hi, nice and awesome blog i thanks for sharing a lot of information enjoy this good post.
    basketball training videos

    ReplyDelete


  77. This is a great inspiring article. I am pretty much pleased with your good work. You put really very helpful information. I have read your excellent post. This is a great job. I have enjoyed reading your post first time. I want to say thanks for this post.
    Top SEO Company

    ReplyDelete
  78. this is very useful blog i like this . I want to make a coupon site like (https://pesapak.com) any budy help me

    ReplyDelete
  79. There are number of Payroll available in pakistan and each one of them have their own unique specification. To choose a best match software for your Small business, its depends on your requirement.Payroll Software in Pakistan

    ReplyDelete
  80. Techseaa is the platform where you can find daily updates about Business, Technology, Social Media , Lifestyle , Health & Fitness and Travel . Stay connected for the latest updates.
    If you are looking for free guest posting then you can Write For Us

    ReplyDelete
  81. when it comes to implement a Payroll software for small business then there's always limited finance problem. SO it's very difficult to find a software which contains all the features in a limited budget.Payroll Software in Pakistan

    ReplyDelete
  82. Thanks for sharing this I was just amazed to see this. I will come back to your site and keep sharing this information with us. For more click here
    Escorts in Pakistan.
    We have variety ranges from glamorous and beautiful
    Escorts in Lahore
    Escorts in Islamabad
    Escorts in Karachi

    ReplyDelete
  83. Thanks for this post, It is really heally helpfull
    Futurestuffs

    ReplyDelete
  84. Hi, I do believe Get Winners Information this is an excellent blog. I stumbledupon it ;) I'm going to come back yet again since I book-marked it. Money and freedom is the best way to change, may you be rich and continue to help other people.

    ReplyDelete
  85. this lesson is really valueable i shared it with various team members of Mobile Mall

    ReplyDelete
  86. We’re happy to explain the repair process and answer any questions you have. As we like to say, if it has a power button, we can fix it! Contact for more detail

    ReplyDelete
  87. We gather on this day to be thankful for what we have, for the family we love, the friends we cherish, and for the blessings that will come. Happy Thanksgiving!
    Thanksgiving Images Free

    ReplyDelete
  88. i would love to implement this lesson on Mobile Mall Pk

    ReplyDelete
  89. Wishing you abundance, hope, peace and a festive holiday season. Happy Thanksgiving to you!
    Thanksgiving Images Free

    ReplyDelete
  90. I’m excited to uncover this page. I need to to thank you for ones time for this particularly fantastic read!! I definitely really liked every part of it and i also have you saved to fav to look at new information in your site.
    free download wordpress themes for business websites

    ReplyDelete
  91. Alvina Stewart was not a popular public figure, but she started enjoying the massive media coverage when Anthony Anderson came to her life as her husband in 1995. The couple is one of the lovebirds, who started dating in their high school. Despite experiencing all the tough circumstances and passing through every thick and thin, they decided to save and protect their relationship. Although after becoming the parents of two children and spending 20 years together, their relationship got rusted, yet they managed to hang onto their relationship rather than being divorced. As compared to Alvina, Anthony is a very famous and successful actor; although he made some mistakes that weakened their relationship with his wife, and due to irreconcilable differences, divorce was filed to end their relationship, yet the positive aspects of Anthony’s nature let Alvina reconcile.

    Alvina Stewart Biography

    ReplyDelete
  92. I am a writer and I have been serving the students for several years in writing their assignments but I have not written a book yet for the students. Now I wish I write a book in which I write all the problems that students are facing in the field of education and learning.dissertation writing services

    ReplyDelete
  93. Thanking for sharing Effective article. This is more helpful for reader..
    project Management assignment help, Business help bu Qualified Degree holder with large years of experienced in Management field..

    ReplyDelete
  94. if you want to buy lenses online in Pakistan then lenspk is one of the best platforms where you can buy contact lenses online in pakistan

    ReplyDelete
  95. I have read your article; it is very informative and helpful for me. I admire the valuable information you offer in your articles. Thanks for posting it.
    Rajasthan Budget Tours

    ReplyDelete
  96. Haseen Libas Apparel Store is an online purchasing web store where anyone girl or a woman, Men can shop easily with various options. Products including; Pakistani Dresses, Pakistan women dresses, Men Dresses, All top brands with new collection available with very affordable price.

    Visit also: Moving Reel
    Children Books
    Gadgets Year
    Camping Equipments

    ReplyDelete
  97. Thank you for sharing, and nice articles you have here Mobile Prices in Bangladesh the admin of this site has really worked hard for all this once more thanks for sharing your articles.

    ReplyDelete
  98. Thanks for such a great infromation

    ReplyDelete
  99. such a nice post thanks for sharing this with us really so impressible and attractive post Fashion bloggers in India

    ReplyDelete
  100. We ensure that you get maximum comfort and specialized services from our end as we use the latest and advanced plumbing tools and eliminate all the plumbing issues even in the odd timings. So don’t think twice feel free to connect with us and get your hands on the most satisfactory and budget-oriented Plumbing Service In Michigan.
    Our exceptional services offered by Affordable Plumbers In Michigan City;
    • Commercial plumbing systems installation & repairing.
    • Low water pressure resolutions.
    • Drain excavations & unclogging services.
    • Installation of faucets, taps.
    • And many more.
    plumbers in detroit michigan
    michigan plumbers

    ReplyDelete
  101. Trusted captain experts provide every kind of plumbing services when you need them as our top-notch Plumber In Philadelphia PA are certified expert with years of experience in resolving and maintaining both commercial and residential plumbing system efficiently. They have the latest and right tool to fix any of the plumbing concerns and services so don’t hesitate while making a call on our helpdesk.
    Emergency Plumber Philadelphia

    ReplyDelete
  102. This blog is useful as well as informative. Keep sharing such blogs I really like your posts.
    Web Development Company in Haldwani

    ReplyDelete
  103. All our plumbers are qualified enough to ensure that you get the most satisfactory services within your desired budget at any point of time. So, make a call on our helpline number now & get connected with our reputed Plumbing Companies In Atlanta Georgia.
    We have listed some of our extraordinary plumbing services offered by our Best Plumbers In Atlanta Georgia:
    • Unclogging services for toilet & drains.
    • Low water pressures & replacements of pipelines.
    • Leaky faucet repairers and fixations.
    • Installation & fixing of commercial plumbing systems.
    • And many more plumbing services.
    Georgia Plumbing Services inc

    ReplyDelete
  104. Whether you get unpleasant smells from your kitchen or your life is affected by the unwanted leak in your bathroom we have the best solutions for all your plumbing concerns. All you need to do is call on our helpline number and we assign you the best Plumbers In Ohio at your desired place. We understand that if plumbing issues are not fixed properly they can create a great nuisance in your life and you need to pay even more for the easy plumbing issues. Our certified & professional team of experienced plumbers make us one of the Best Plumbing Companies In Ohio.
    plumber columbus ohio

    ReplyDelete
  105. Plumbing systems can affect your home & work flow at offices as well if you facing plumbing issues even after searching for the Plumbing Services Near Me then you need to make a call on our helpline number and our expert plumbers will help you out with the most amazing plumbing services. When you connect with us for most reliable plumbing services we offer you the most hassle-free plumbing solutions with the help of our highly trained & professional plumbers.
    24 hour plumber near me
    certified plumbers near me

    ReplyDelete
  106. Good blog,

    Digital Marketing Companies in Chennai, Website Design Companies in Chennai, SEO Companies in Chennai, Digital Marketing Company Chennai, Web Design Company Chennai.

    https://www.wisewebtek.com

    ReplyDelete
  107. Nice Information. Thanks for sharing. Muby Technologies , is a leading pioneer in the field of image editing services.
    We take pride in establishing ourselves as the most reliable partner for your image retouching and photo editing services.
    Reach us

    ReplyDelete
  108. Thanks For Sharing The Information The Information Shared Is Very Valuable Please Keep Updating Us Time Just Went On Reading The article
    by cloudi5 is the Web Design Company in Coimbatore

    ReplyDelete
  109. Cats Funny Videos is all about fun and entertainment where any one can watch Cats Funny Videos from any where in the world. Here are different categories of funny cats videos' collection. Enjoy Funny Cat Videos, cute cat videos, cat videos compilation. Cats & Dogs
    Cats Funny Videos Car Dash Camera

    ReplyDelete
  110. Thanks for sharing this informative content.,
    Leanpitch provides online training in Scrum Master Certification during this lockdown period everyone can use it wisely.
    Join Leanpitch 2 Days CSM Certification Workshop in different cities.

    Scrum master certification online

    CSM certification online

    CSM online

    CSM online certification

    CSM online training

    CSM training online

    Scrum master training online

    ReplyDelete
  111. Find the best event Celebrations ideas on Neweventday.com with a lot of daily updates

    ReplyDelete
  112. You provided such a nice blog. go through my favorite link for informative content

    ReplyDelete

  113. I'm looking for this information. Thank you very much | www.hairsaloninsandiego.com

    ReplyDelete
  114. Woh I enjoy your content, saved to bookmarks!!! Turkey transit visa for US citizens... Ordinary passport holders are required to have a visa to enter Turkey. You can get an online visa transit Turkey.You are ready to fill out an application form in the Turkey Visa Pre-Application System.

    ReplyDelete
  115. You're so interesting! I don’t think Ive truly read through a single thing like this before. So wonderful to discover another person with genuine thoughts on this subject. Seriously. Feel free to visit my website; 바카라사이트

    ReplyDelete
  116. I was able to think a lot while looking at this post. I will pass this blog to many people. Definitely a good blog is very good. Thank you for sharing. Feel free to visit my website; 바카라

    ReplyDelete
  117. If you are running a business or a company and have access to a website, whether small, medium, or big, you need to perform SEO – Search Engine Optimization to grow your business organically. With the correct tactics and techniques, you can steadily improve your business’s online presence, even if it’s domestically or globally. However, designing a website or a web portal is easy with the help of tools and technologies. The hurdles come when your website needs organic reach. For this purpose, you need the digital marketing companies ​to check the technical SEO of your website schema markups pages, loading speed, H tags and offer you the best SEO – search engine optimization services. “Additionally, Bright Edge stated in one of its articles, 53.3 % of traffic on the website comes from organic research, and 50% of revenue comes from this organic traffic. Get a read on seo companies in Pakistan

    ReplyDelete
  118. سرویس و تعمیرات یخچال در مرکز دو سوت تعمیر

    دسته تعمیر یخچال

    ReplyDelete
  119. A lot more peoople must read his and understand this siode of thhe story.
    I was surprised that you're not more popular since you definitely possess the gift. 사설토토

    ReplyDelete
  120. Your blogs are amazing. Keep sharing. I love them Are you also searching for urgent assignment help? we are the best solution for you. We are best known for delivering the best urgent assignment help.

    ReplyDelete
  121. Awesome article, it was exceptionally helpful! I simply began in this and I'm becoming more acquainted with it better! Cheers, keep doing awesome!
    textspeed

    ReplyDelete
  122. Thanks for providing a piece of great information and looking beautiful blog, really nice required information.
    ocean softwares

    ReplyDelete
  123. Thanks for the great info. Awesome Post.

    Sandeep

    ReplyDelete
  124. nice post and article, best collection, will be thankful if you can share collection of websites which offers online shopping in pakistan. thanks & regards...

    ReplyDelete
  125. This is an excellent blog, and I thoroughly like reading your posts. Keep up the excellent work! Many others are searching for this information, and you could be of great assistance to them.

    photographers in Andaman
    photoshoot in havelock Andaman
    photoshoot in Andaman
    pre wedding shoot in Andaman
    Andaman photoshoot
    photoshoot at Andaman

    ReplyDelete
  126. It’s wonderful to meet you at long last. Your article was quite impressive. It’s a fresh idea. It provided me with a lot of energy. What prompted you to think of this fantastic idea? You’re writing abilities are exceptional.

    photographers in Andaman
    photoshoot in havelock Andaman
    photoshoot in Andaman
    pre wedding shoot in Andaman
    Andaman photoshoot
    photoshoot at Andaman

    ReplyDelete

Blog Archive

Google Analytics