If you're trying to deal with a meta class, or you're struggling with asynchronous programming in Twisted, or you're studying object-oriented programming that's exhausting you with multiple allocations, you are totally wrong! PEAK combines some of these elements into a single component programming framework. There are some minor problems with PEAK. Documents similar to Twisted,peak--as large as possible--are hard to read. But still, there is something very noteworthy about the project led by Python leader Phillip J. Eby, and I feel that there is an opportunity for highly productive and highly tiered application development.
The PEAK package consists of many different uses of the child package. Some of the important sub packages are Peak.api, peak.binding, Peak.config, peak.naming, and Peak.storage. Most of those names are of a self explanatory nature. The peak.binding is used for flexible connections between components, and Peak.config allows you to store "rarely changed (lazily immutable)" data related to declarative application (declarative application) programming; Peak.naming allows you to create globally unique identifiers for resources (network); Peak.storage allows you to manage databases and persistent content, as the name suggests.
However, for this article, we will focus on the PEAK.API. Especially the Pyprotocols package, which can be obtained separately and provide an infrastructure for other PEAK. A version of the Pyprotocols package is included in the Peak.api.protocols. But now what I'm interested in is studying a separate protocols package. In the future section, I will return to discuss the other parts of the PEAK topic.
What is an agreement?
In abstract terms, a protocol is just a set of behaviors that an object agrees to follow. The strongly typed (strongly-typed) programming language--including Python--has a set of basic types, each of which has a guaranteed set of behaviors: integers know how to ask for their own product; list knows how to traverse their content ; dictionary knows how to find the appropriate value based on a keyword; file knows how to read and write a section; The set of built-in types of behavior that you can expect constitutes a protocol that they implement. The object that is being systematically made to a protocol is called an interface (interface).
For a standard type, it is not too difficult to list all the behaviors that will be implemented (although there will be a slight difference between different versions of the Python version; or, of course, there will be differences between different programming languages). However, at the boundary-for objects that belong to a custom class-it is difficult to declare what ultimately constitutes the behavior of "class-dictionary" or "Class-file". In most cases, a custom object that implements only a subset of the built-in dict types--even a fairly small subset--is sufficient for "class-dictionary" to meet the current requirements. It would be tempting, however, to explicitly sort out the functions, modules, classes, or frameworks that an object needs to be able to do. That's what the Pyprotocols package does (part).
In a programming language with a static type declaration, you typically need to force type conversion (CAST) or CONVERT (convert) from one type to another in order to use the data in a new context. In other languages, transformations are implicitly performed according to the needs of the context, which are called forced-coercions. Python has both mandatory and forced-type conversions, often using more of the former ("Explicit is better than implicit"). You can add a floating-point number to an integer, resulting in a more general floating-point count, but if you want to convert the string "3.14" to a number, you need to use the explicit constructor float ("3.14").
Pyprotocols has a feature called fit (adaptation), similar to the unorthodox computer science concept of "partial type (partial typing)". Adaptation may also be considered an "accelerated force-type". If an interface defines the desired set of capabilities (i.e., the object method), then the object of "everything needed" requires adaptation-implemented through the PROTOCOLS.ADAPT () function-to provide the required capabilities. Obviously, if you have an explicit conversion function that converts an object of type X to an object of type Y (where Y implements a IY interface), then that function should be able to IY the X adapter protocol. However, the pyprotocols can do much more than that. For example, even if you have never explicitly written a translator from type X to type Y, adapt () can usually push a way for X to provide the IY's required capabilities (that is, to find intermediate transitions from X to interface IZ, from IZ to IW, and then from IW to IY). )。
declaring interfaces and Adapters
There are many different ways to create interfaces and adapters in Pyprotocols. The Pyprotocols document describes these technologies in great detail-a lot of them are not covered in this article. Next we'll go into some details, but I think it's a useful way to give the simplest example of the actual pyprotocols code here.
For example, I decided to create a class-lisp serialization of a Python object. The description is not accurate Lisp syntax, and I do not care about the exact advantages and disadvantages of this format. Here, my idea is simply to create a function that can perform similar repr () functions or Pprint modules, but the result is significantly different from the previous serial (serializers) and can be expanded/customized more easily. A very unlike Lisp choice was made for illustrative purposes: mapping (mappings) is a much more basic data structure than a list (lists) (a Python tuple (tuple) or a list is treated as a mapping with a continuous integer key). Here's the code:
lispy.py Pyprotocol Definition
From protocols Import * to Cstringio import Stringio # like Unicode, & even support objects that don ' t explicitly s Upport ILisp ILisp = Protocolfortype (Unicode, [' __repr__ '], implicit=true) # Class for interface, but no methods Specifica
Lly Required Class ISeq (Interface): Pass # Class for Interface, extremely simple mapping Interface class IMap (Interface): def items (): "A requirement for a map are to have an. Items ()" # Define function to create A Lisp like Repres Entation of a mapping def map2lisp (MAP_, prot): out = Stringio () for k,v in Map_.items (): Out.write ("(%s)"% ( Adapt (K,prot), adapt (V,prot)) return "(MAP%s)"% Out.getvalue () # Use this func to convert a imap-supporting obj to I lisp-supporting obj declareadapter (map2lisp, Provides=[ilisp], Forprotocols=[imap]) # Note This a dict implements an IMap interface with no conversion needed declareadapter (no_adapter_needed, Provides=[imap], fortypes=[dict]) # Define and use F UNC to adapt an InstanceType obj to the ILisp interface from types import instancetype def inst2lisp (O, p): Return (CLASS ' (%s)%s)% (O.__CLA SS__.__NAME__, adapt (o.__dict__,p)) Declareadapter (Inst2lisp, Provides=[ilisp], Fortypes=[instancetype]) # Define A
class to adapt a iseq-supporting obj to a imap-supporting obj class Seqasmap (object): Advise (INSTANCESPROVIDE=[IMAP), ASADAPTERFORPROTOCOLS=[ISEQ]) def __init__ (self, SEQ, prot): Self.seq = seq Self.prot = Prot def items ( Self): # Implement the IMap required. Items () method return Enumerate (SELF.SEQ) # Note This list, tuple implement an ISeq interface w/o conversion needed Declareadapter (no_adapter_needed, Provides=[iseq], fortypes=[list, tuple]) # Define A lambda func to adapt str, Unicode to ILisp interface Declareadapter (lambda s,p: "' (%s)"% s, Provides=[ilisp], F Ortypes=[str,unicode]) # Define A class to adapt several numeric types to ILisp interface # return a string (ilisp-support ing) directly from instance conStructor class Numberaslisp (object): Advise (INSTANCESPROVIDE=[ILISP), Asadapterfortypes=[long, float, complex, Boo
L]) def __new__ (Klass, Val, Proto): Return "(%s%s)"% (Val.__class__.__name__.upper (), Val)
In the code above, I've already declared many adapters in a number of different ways. In some cases, the code converts an interface to another interface, and in other cases the type itself is directly adapted to another interface. I want you to notice some aspects of the Code: (1) No adapters were created from the list or tuple to the ILisp interface, (2) No adapter was explicitly declared for the int numeric type, (3) for that matter, no adapters were declared directly from Dict to ILisp. Here's how the code will fit (adapt ()) various Python objects:
Serialization of test_lispy.py objects
From lispy import *
from sys import stdout, stderr
tolisp = Lambda o:adapt (o, ILisp)
class Foo:
def __ini T__ (self):
self.a, self.b, SELF.C = ' A ', ' B ', ' C '
tests = [
"foo bar",
{17:2, 33:4, ' biz ': ' Baz '},
[" Bar ", (' f ', ' o ', ' o ')],
1.23,
(1L, 2, 3, 4+4j),
Foo (),
True,
] for
test in tests:
Stdout.write (Tolisp (test) + ' \ n ')
When we run, we get:
test_lispy.py Serialization Results
$ python2.3 test_lispy.py
' (foo bar)
(Map (2) (' (Biz) ' (Baz)) (4)) (
map (0 ' (bar)) (1 (Map (0 ' (f)) (1 ' (o)) (2 ' (o)))) (
FLOAT 1.23)
(map (0 (LONG 1)) (1 2) (2 3) (3 (COMPLEX (4+4J))) (
CLASS ' (Foo) (Map (') (' (a) ' (a)) (' (c) ' (c)) (' (b) ' (b
))) (BOOL True)
It would be helpful to have some explanation of our output. The first line is simpler, we define an adapter directly from the string to the ILisp, and the call to adapt ("foo bar", ILisp) simply returns the result of the lambda function. The next line is just a little complicated. There are no adapters directly from Dict to ILisp, but we don't have to use any adapters to get dict to fit IMAP (we've declared enough), and we have adapters from IMap to ILisp. Similarly, for the following lists and tuples, we can make the ILisp fit ISeq, make the ISEQ fit with IMAP, and make the IMAP fit with ILISP. Pyprotocols will point out the appropriate path to take, and all these incredible processes are done behind the scenes. An example of an old style goes through the same process as a string or an object that supports IMAP, and we have a fit that goes directly to the ILisp.
But wait a minute. What do we do with all the integers we use in our dict and tuple objects? Numbers with long, complex, float, and bool types have an explicit adapter, but none of the int is. The trick here is that the Int object already has a. __repr__ () method, and by declaring implicit support as part of the ILisp interface, we can skillfully use the existing. __repr__ () method of the object as support for the ILisp interface. In fact, as a built-in type, integers are represented by an Arabic numeral without any modification, rather than using an uppercase type initializer (such as LONG).
Fitting Agreement
Let's take a more specific look at what the PROTOCOL.ADAPT () function does. In our example, we use the Declaration API (Declaration API) to implicitly set up a set of "factories (factories)" for adaptation. There are several levels of this API. The "Basic level (primitives)" of the declaration API is a function: Declareadaptorfortype (), Declareadaptorforobject (), and Declareadaptorforprotocol (). These are not used in the previous examples, but are used in high-level APIs such as Declareimplementation (), Declareadaptor (), Adviceobject (), and Protocolfortype (). In one case, we see a "wonderful" advise () declaration in a class body. The advise () function supports a large number of keyword parameters that are used to configure the purpose and role of those proposed classes. You can also suggest (advise ()) a module object.
You do not need to use a declaration API to create your own adaptable objects or interfaces that know how to fit objects (adapt ()). Let's look at the call tag for adapt () and then explain the subsequent process. The call to adapt () looks like this:
Invocation token for ADAPT ()
Adapt (Component, Protocol, [, default [, Factory]])
This means you want to have the object component to fit the interface protocol. If default is specified, it can be returned as a wrapper object (wrapper object) or a modification to the component. If factory is specified as a keyword parameter, a conversion factory is used to generate the wrapper or modify it. But let's go back a little and take a look at the complete sequence of actions (simplified code) that adapt () Tries:
The hypothetical implementation of adapt ()
If Isinstance (component, Protocol): Return
component
elif hasattr (component, ' __conform__ '):
return component.__conform__ (protocol)
Elif hasattr (protocol, ' __adapt__ '): Return
protocol.__adapt__ (component)
elif default is not none: Return
default
elif factory isn't none: Return
Factory (component, Protocol) C11/>else:
notimplementederror
Calls to adapt () should retain some attributes (although this is a suggestion to the programmer, not a general mandatory requirement for the library). The call to adapt () should be idempotent. That is to say, for an object x and a protocol p, we want: adapt (X,P) ==adapt (Adapt (x,p), p). Advanced, this is similar to the purpose of returning the iterator (iterator) class of self (self) from the. __iter__ () method. You basically don't want to be able to reassign to the same type that you already fit to produce fluctuating results.
It is also noteworthy that the adaptation may be lossy. In order for an object to conform to an interface, it may be inconvenient or impossible to maintain all the information needed to reinitialize the object. That is, typically, for object X and protocol P1 and P2: Adapt (X,P1)!=ADAPT (adapt (adapt), X,P1), P2.
Before closing, let's look at another test script that leverages the low-level behavior of adapt ():
Serialization of test_lispy2.py objects
From lispy Import *
class Bar (object):
Pass
class Baz (bar):
def __repr__ (self): return
"represent A "+self.__class__.__name__+" object!
" Class Bat (Baz):
def __conform__ (self, prot): Return
"Adapt" +self.__class__.__name__+ "to" +repr (prot) + "!"
Print adapt (Bar (), ILisp)
Print adapt (Baz (), ILisp) Print
adapt (Bat (), ILisp) print adapt ((), Adapt (
bat), ILISP), ILisp)
$ python2.3 test_lispy2.py
<__main__. Bar object at 0x65250>
represent a Baz object!
Adapt Bat to Weaksubset (<type ' Unicode ', (' __repr__ ',))!
' (Adapt Bat to Weaksubset (<type ' Unicode ', (' __repr__ ',)!)
The results show that the design of lispy.py can not meet the power targets. It may be a good practice to improve this design. However, a description such as ILisp is sure to consume the information in the original object (this is OK).
Conclusion
In the sense that pyprotocols has something in common with other "exotic" topics mentioned in this column. First, the declaration API is declarative (relative to interpretation). Declarative programming does not give the steps and switches required to perform an action, but rather declares the handling of specific content that is specified by the library or compiler. The name "declare* ()" and "advice* ()" Are coming from this point of view.
However, I have also found that Pyprotocols programming is similar to programming with multiple allocations, specifically using the Gnosis.magic.multimethods module that I mentioned in another installment. In contrast to the pyprotocols identification path, my own module performs a relatively simple deduction to determine the related ancestor classes to assign. However, two libraries tend to be programmed to encourage the use of similar modular thinking--A large number of small functions or classes to perform "pluggable" tasks that do not need to be trapped by rigid class hierarchies. In my opinion, this style has its advantages.