Python and MySQL
Check this out in [HTML format|http://esbinfo:8090/download/attachments/1704632/python-and-mysql.html] ...
{html}
Contents
- 1 MySQL Users Conference 2005, Santa Clara, CA
- 2 Python and MySQL
- 3 Python for the PyCurious
- 4 Types
- 5 Basic type examples
- 6 Strings
- 7 Strings
- 8 Dictionaries
- 9 Sequences
- 10 Iterators
- 11 Common iterator usage
- 12 Exceptions
- 13 Odds and ends
- 14 Odds and ends
- 15 The Python DB-API
- 16 Module Interface
- 17 SQL parameter placeholders
- 18 Exceptions
- 19 Connection Object
- 20 Cursor Object
- 21 Cursor Object
- 22 Cursor Object
- 23 MySQL for Python
- 24 Architecture
- 25 Opening a connection
- 26 Simple query example
- 27 Multi-row query example
- 28 A simple User class
- 29 Multi-row query with User object
- 30 Cursors are iterators
- 31 Dictionaries as parameters
- 32 Multi-row INSERT
- 33 Multi-row INSERT with dictionaries
- 34 Never do this
- 35 To buffer, or not to buffer...
- 36 Optional cursor classes
- 37 Type objects and constructors
- 38 Embedded server
- 39 Questions?
MySQL Users Conference 2005, Santa Clara, CA
Python and MySQL
Python and MySQL
Andy Dustman
Office of Information Technology
Terry College of Business
University of Georgia
- Andy Dustman
- <A href="mailto:adustman%40terry.uga.edu"></A><ADUSTMAN><A href="mailto:adustman%40terry.uga.edu"></A></ADUSTMAN>
- Terry College of Business
- <A href="http://www.terry.uga.edu/">http://www.terry.uga.edu/</A>
- University of Georgia
- <A href="http://www.uga.edu/">http://www.uga.edu/</A>
Python for the PyCurious
- interpreted (byte-code compiler)
- interactive (easy to test ideas)
- object-oriented (everything's an object)
- rapid development (5-10x C++, Java)
- fits your brain [Bruce Eckel]
- fits your wallet: free (OSI and GPL)
- fun!
- Introductory Material on Python:
- <A href="http://www.python.org/doc/Intros.html">http://www.python.org/doc/Intros.html</A>
Types
<TBODY>
</TBODY>
| Mutable | Immutable |
---|---|---|
Sequence | list | tuple str, unicode |
Number | | int, long, float |
Mapping | dict | |
Other | object | |
Basic type examples
>>> i=1 # an int<BR></BR>>>> j=2**64-1 # a long integer<BR></BR>>>> print j<BR></BR>18446744073709551615<BR></BR>>>> f=3.14 # float (C double)<BR></BR>>>> c=1-1j # complex (1j is imaginary)<BR></BR>>>> print c<BR></BR>(1-1j)<BR></BR>>>> s="welcome to python!"<BR></BR>>>> s.capitalize().split() # returns a list<BR></BR>['Welcome', 'to', 'python!']<BR></BR>>>> [ word.capitalize() for word in s.split() ]<BR></BR>['Welcome', 'To', 'Python!']<BR></BR>>>> a, b = 1, 2<BR></BR>>>> print (a,b) # a tuple<BR></BR>(1, 2)<BR></BR>>>> a, b = b, a<BR></BR>>>> print (a,b)<BR></BR>(2, 1)<BR></BR>
Strings
>>> "Normal string literal isn't very interesting."<BR></BR>"Normal string literal isn't very interesting."<BR></BR>>>> 'Single quotes work "same as double".'<BR></BR>'Single quotes work "same as double".'<BR></BR>>>> """Triple-quoted strings are good for long strings<BR></BR>... which span multiple lines."""<BR></BR>'Triple-quoted strings are good for long strings\nwhich span multiple lines.'<BR></BR>>>> r"Raw strings are useful for regexs, i.e. \w+ or \1"<BR></BR>'Raw strings are useful for regexs, i.e. \\w+ or \\1'<BR></BR>>>> u"Unicode strings work just like regular strings."<BR></BR>u'Unicode strings work just like regular strings.'<BR></BR>>>> u"\u72c2\n\u7009".encode('utf-8')<BR></BR>'\xe7\x8b\x82\n\xe7\x80\x89'<BR></BR>>>> print u"\u72c2\n\u7009".encode('utf-8')<BR></BR>¿<BR></BR>¿<BR></BR>
Strings
Lots of string methods and operators:
>>> "Split words into a list.".split()<BR></BR>['Split', 'words', 'into', 'a', 'list.']<BR></BR>>>> ' '.join(['Join', 'a', 'list', 'of', 'strings'])<BR></BR>'Join a list of strings'<BR></BR>>>> "Concatenate" + " " + "strings"<BR></BR>'Concatenate strings'<BR></BR>>>> "Multiplicity! " * 3<BR></BR>'Multiplicity! Multiplicity! Multiplicity! '<BR></BR>>>> "Parameter %s" % "substitution"<BR></BR>'Parameter substitution'<BR></BR>>>> d = dict(first_name="Vee", last_name="McMillen",<BR></BR>... company="O'Reilly")<BR></BR>>>> "Hello, %(first_name)s. How are things at %(company)s?" % d<BR></BR>"Hello, Vee. How are things at O'Reilly?"<BR></BR>
Dictionaries
Python dictionaries are like perl hashes:
>>> d1={}<BR></BR>>>> d1['a']=1<BR></BR>>>> d1['b']=2<BR></BR>>>> d1['c']=3<BR></BR>>>> d1<BR></BR>{'a': 1, 'c': 3, 'b': 2}<BR></BR>>>> d2={'a': 1, 'c': 3, 'b': 2}<BR></BR>>>> d3=dict([('a',1),('b',2),('c',3)])<BR></BR>>>> d4=dict(a=1, b=2, c=3)<BR></BR>>>> d1 == d2 == d3 == d4<BR></BR>True<BR></BR>>>> len(d1)<BR></BR>3<BR></BR>
Values can be any type, but keys must be immutable.
Sequences
>>> l = ['a','b','c','d','e']<BR></BR>>>> print l[0]<BR></BR>a<BR></BR>>>> print l[-1]<BR></BR>e<BR></BR>>>> print l[2:4]<BR></BR>['c', 'd']<BR></BR>>>> s='abcde'<BR></BR>>>> print s[2:4]<BR></BR>cd<BR></BR>>>> print s[::2]<BR></BR>ace<BR></BR>>>> print s[::-1]<BR></BR>edcba<BR></BR>>>> l.append(s)<BR></BR>>>> print l<BR></BR>['a', 'b', 'c', 'd', 'e', 'abcde']<BR></BR>
Iterators
iter(object)
returns an iterator objectiterobj.next()
returns the next objectStopIteration
is raised when there are no more objects>>> # no normal person would do this<BR></BR>>>> l = [1, 2, 3]<BR></BR>>>> i = iter(l)<BR></BR>>>> i.next()<BR></BR>1<BR></BR>>>> i.next()<BR></BR>2<BR></BR>>>> i.next()<BR></BR>3<BR></BR>>>> i.next()<BR></BR>Traceback (most recent call last):<BR></BR> File "<STDIN>", line 1, in ?<BR></BR>StopIteration<BR></BR></STDIN>
Common iterator usage
>>> l = [1, 2, 3]<BR></BR>>>> for item in l:<BR></BR>... print item<BR></BR>...<BR></BR>1<BR></BR>2<BR></BR>3<BR></BR>>>> d = dict(a=1, b=2, c=3)<BR></BR>>>> for key in d:<BR></BR>... print key, d[key]<BR></BR>...<BR></BR>a 1<BR></BR>c 3<BR></BR>b 2<BR></BR>
Exceptions
f = open("myfile", 'r')<BR></BR>try:<BR></BR> try:<BR></BR> for line in f:<BR></BR> try:<BR></BR> process(line)<BR></BR> except TypeError:<BR></BR> line = mangle(line)<BR></BR> try:<BR></BR> process(line)<BR></BR> except TypeError:<BR></BR> raise FoobarError, line<BR></BR> except IOError, message:<BR></BR> print "Error reading:", message<BR></BR> except FoobarError:<BR></BR> print "This file is totally munged."<BR></BR> except:<BR></BR> print "Something inexplicable happened:"<BR></BR> raise # re-raise original exception<BR></BR>finally:<BR></BR> f.close()<BR></BR>
Odds and ends
- Code blocks are delimited by indentation
- You probably do this already
- Space or tabs, your call; just be consistent
- No need for curly braces
- Less cluttered, easier to read
- End-of-line is a statement separator (so is
;
) - No type enforcement
- Argument types are not checked
- Function return types are not checked
- Type checking makes your code less flexible
- If you still want it, you can add it cleanly with decorators
- Operator overloading for user-defined classes
- Everything is a reference (pass by reference)
None
object for null/missing values (equivalent toNULL
)
Odds and ends
- Member access with
.
operatorinstance.method()
instance.attribute
instance.attribute.another
- Functions/methods are not the only things that are callable
- Decorators apply a callable to a function at creation time:
@g<BR></BR>def f(x):<BR></BR> ...<BR></BR>
is equivalent to:def f(x):<BR></BR> ...<BR></BR>f = g(f)
The Python DB-API
- Standard API for database access
- PEP 249: <A href="http://www.python.org/peps/pep-0249.html" title="PEP 249: Python Database API v2.0">http://www.python.org/peps/pep-0249.html</A>
- By convention, module name ends with "db", i.e. MySQLdb
- Module Interface
- Connection Objects
- Cursor Objects
- DBI Helper Objects
- Type Objects and Constructors
- Implementation Hints
- Major Changes from 1.0 to 2.0
Module Interface
- connect(...)
- Constructor for creating a connection to the database. Returns a Connection Object.
- apilevel
- String constant stating the supported DB API level.
- threadsafety
- Integer constant stating the level of thread safety the interface supports.
SQL parameter placeholders
- 'qmark'
- Question mark style, e.g. '...WHERE name=?'
- 'numeric'
- Numeric, positional style, e.g. '...WHERE name=:1'
- 'named'
- Named style, e.g. '...WHERE name=:name'
- 'format'
- ANSI C printf format codes, e.g. '...WHERE name=%s'
- 'pyformat'
- Python extended format codes, e.g. '...WHERE name=%(name)s' MySQLdb 1.0 and 1.2 uses format and pyformat; 2.0 may also support qmark.
Exceptions
- StandardError
- Warning
- Error
- InterfaceError
- DatabaseError
- DataError
- OperationalError
- IntegrityError
- InternalError
- ProgrammingError
- NotSupportedError
Connection Object
- .close()
- Close the connection now
- .commit()
- Commit any pending transaction to the database. Auto-commit off by default.
- .rollback()
- Rollback any pending transaction.
- .cursor()
- Return a new Cursor Object using the connection.
- exceptions
- The standard exception classes; simplfies error handling in some cases
- .messages
- list of error/warning messages since last method call
Cursor Object
- .description
- A sequence of sequences, each of which describe a column of the result.
- .rowcount
- Number of rows affected by last query.
- .callproc(procname[,parameters])
- Call a stored database procedure with the given name.
- .close()
- Close the cursor now.
- .execute(operation[,parameters])
- Prepare and execute a database operation (query or command). Parameters: sequence or mapping.
- .executemany(operation,seq_of_parameters)
- Prepare a database operation (query or command) and then execute it against a sequence of parameters.
Cursor Object
- .fetchone()
- Fetch the next row of the result set as a sequence, or None if there are no more rows.
- .fetchmany([size=cursor.arraysize])
- Fetch a sequence of up to size rows; may be fewer. Zero length sequence indicates end of result set.
- .fetchall()
- Fetch all remaining rows as a sequence of rows.
- .nextset()
- Skip to the next result set. Returns a true value if there is another result set; None (false) if not.
- .arraysize
- Default number of rows to return with cursor.fetchmany(). Default: 1.
Cursor Object
- .rownumber
- Current index into result set
- .connection
- The Connection object for this cursor
- .scroll(value[,mode='relative'])
- Scroll to a new position in the result set (relative or absolute).
- .messages
- List containing warning/error messages since last method call (except the .fetchXXX() methods).
- .next()
- Fetches one row (like fetchone()) or raises
StopIteration
if no rows left. Iterator protocol - .lastrowid
- Row id of the last affected row (i.e. inserting
AUTO_INCREMENT
columns)
MySQL for Python
- MySQL-python project on SourceForge: <A href="http://sourceforge.net/projects/mysql-python" title="MySQL-python project on SourceForge">http://sourceforge.net/projects/mysql-python</A>
- Current best version: 1.2.0
- Python-2.3 and newer (and maybe 2.2)
- MySQL-3.23, 4.0, and 4.1 (and maybe 5.0)
- Prepared statements not supported yet
- Older version: 1.0.1
- Python-1.5.2 (very old) and newer
- MySQL-3.22, 3.23, and 4.0 (not 4.1 or newer)
- Don't use if you can use 1.2.0
- Vaporware version: 2.0
- Python-2.4 and newer
- MySQL-4.0, 4.1, and 5.0
- Prepared statements will be supported
- Return all text columns as
unicode
by default
Architecture
_mysql
- C extension module
- transliteration of MySQL C API into Python objects
- If you use the C API, this should be very familiar
- Deprecated API calls not implemented
- Not everything (particularly fields) is exposed
- SQL column type to Python type conversions handled by a dictionary
MySQLdb
- Adapts
_mysql
to DB-API - Many non-standard C API calls are exposed
- Relatively light-weight wrapper
- Implements cursors
- Defines default type mappings; easily customizable
Opening a connection
connect()
takes the same options as mysql_real_connect()
, and then some.
import MySQLdb<BR></BR><BR></BR># These are all equivalent, for the most part<BR></BR>db = MySQLdb.connect("myhost", "myuser", "mysecret", "mydb")<BR></BR>db = MySQLdb.connect(host="myhost", user="myuser",<BR></BR> passwd="mysecret", db="mydb")<BR></BR>auth = dict(user="myuser", passwd="mysecret")<BR></BR>db = MySQLdb.connect("myhost", db="mydb", **auth)<BR></BR>db = MySQLdb.connect(read_default_file="/etc/mysql/myapp.cnf")<BR></BR>
compress=1
enables gzip compressionuse_unicode=1
returns text-like columns asunicode
objectsssl=dict(...)
negotiates SSL/TLS
Simple query example
import MySQLdb<BR></BR><BR></BR>db = MySQLdb.connect(read_default_file="/etc/mysql/myapp.cnf")<BR></BR>c = db.cursor()<BR></BR>c.execute("""SELECT * FROM users WHERE userid=%s""", ('monty',))<BR></BR>print c.fetchone()<BR></BR>
Notes
('monty',)
is a 1-tuple; comma required to distinquish from algebraic grouping- Good reasons not to use
*
- How many columns are being returned?
- What is the order of the columns?
- Good reasons to use
*
- Table/database browser
- Lazy
Multi-row query example
c = db.cursor()<BR></BR>c.execute("""SELECT userid, first_name, last_name, company<BR></BR>FROM users WHERE status=%s and expire>%s""",<BR></BR>(status, today))<BR></BR>users = c.fetchall()<BR></BR>
Notes
- We know what the columns are
- Could use some object abstraction
A simple User class
class User(object):<BR></BR><BR></BR> """A simple User class"""<BR></BR><BR></BR> def __init__(self, userid,<BR></BR> first_name=None, last_name=None,<BR></BR> company=None):<BR></BR> self.userid = userid<BR></BR> self.first_name = first_name<BR></BR> self.last_name = last_name<BR></BR> self.company = company<BR></BR><BR></BR> def announce(self):<BR></BR> """Announce User to the world."""<BR></BR> name = "%s %s" % (self.first_name, self.last_name)<BR></BR> if self.company:<BR></BR> return "%s of %s" % (name, self.company)<BR></BR> else:<BR></BR> return name<BR></BR><BR></BR> def __str__(self):<BR></BR> return self.announce()<BR></BR>
Multi-row query with User object
users = []<BR></BR>c = db.cursor()<BR></BR>c.execute("""SELECT userid, first_name, last_name, company<BR></BR>FROM users WHERE status=%s and expire>%s""",<BR></BR>(status, today))<BR></BR><BR></BR>for userid, first_name, last_name, company in c.fetchall():<BR></BR> u = User(userid, first_name, last_name, company)<BR></BR> print u<BR></BR> users.append(u)<BR></BR>
might produce output like:
Tim O'Reilly of O'Reilly Media, Inc.<BR></BR>Monty Widenius of MySQL AB<BR></BR>Carleton Fiorina<BR></BR>Guido van Rossum of Elemental Security<BR></BR>
Cursors are iterators
Not necessary to use c.fetchall()
users = []<BR></BR>c = db.cursor()<BR></BR>c.execute("""SELECT userid, first_name, last_name, company<BR></BR>FROM users WHERE status=%s and expire>%s""",<BR></BR>(status, today))<BR></BR><BR></BR>for userid, first_name, last_name, company in c:<BR></BR> u = User(userid, first_name, last_name, company)<BR></BR> print u<BR></BR> users.append(u)<BR></BR>
Under certain conditions, this is more efficient
than fetchall()
, and no worse.
Dictionaries as parameters
Python classes typically store attributes in __dict__
,
so you can get away with this:
u = User(...)<BR></BR>c = db.cursor()<BR></BR>c.execute("""INSERT INTO users<BR></BR>(userid, first_name, last_name, company)<BR></BR>VALUES (%(userid)s, %(first_name)s,<BR></BR>%(last_name)s, %(company)s)""", u.__dict__)<BR></BR>db.commit()<BR></BR>
Multi-row INSERT
# users is a list of (userid, first_name, last_name, company)<BR></BR>c = db.cursor()<BR></BR>c.executemany("""INSERT INTO users<BR></BR>(userid, first_name, last_name, company)<BR></BR>VALUES (%s, %s, %s, %s)""", users)<BR></BR>db.commit()<BR></BR>
In MySQLdb, this is converted internally to a multi-row INSERT, which is reported to be 2-3 orders of magnitude faster. Also works for REPLACE.
Multi-row INSERT with dictionaries
# users is a list of Users<BR></BR>c = db.cursor()<BR></BR>c.executemany("""INSERT INTO users<BR></BR>(userid, first_name, last_name, company)<BR></BR>VALUES (%(userid)s, %(first_name)s,<BR></BR>%(last_names, %(company)s)""",<BR></BR>[ u.__dict__ for u in users ])<BR></BR>db.commit()<BR></BR>
This builds the parameter list with a list comprehension.
Never do this
Biggest MySQLdb newbie mistake of all time:
Seeing %s
and thinking, "I should use the %
operator here."
users = []<BR></BR>c = db.cursor()<BR></BR>c.execute("""SELECT userid, first_name, last_name, company<BR></BR>FROM users WHERE status='%s' and expire>'%s'""" %<BR></BR>(status, today))<BR></BR><BR></BR>for userid, first_name, last_name, company in c:<BR></BR> u = User(userid, first_name, last_name, company)<BR></BR> print u<BR></BR> users.append(u)<BR></BR>
Note use of %
operator to insert parameter values.
This does not provide proper quoting (escaping of
'
, NULL
/None
, or \0
).
Passing them separately (as the second parameter)
ensures they are quoted correctly. However, %
is necessary if you have to insert arbitrary SQL
such as column or table names or WHERE
clauses.
To buffer, or not to buffer...
mysql_store_result()
mysql_use_result()
SSCursor
Optional cursor classes
DictCursor
causes fetchXXX()
methods
to return mappings instead of sequences, with column names for
keys.
users = []<BR></BR>c = db.cursor(MySQLdb.cursors.DictCursor)<BR></BR>c.execute("""SELECT userid, first_name, last_name, company<BR></BR>FROM users WHERE status=%s and expire>%s""",<BR></BR>(status, today))<BR></BR><BR></BR>for row in c:<BR></BR> u = User(**row)<BR></BR> print u<BR></BR> users.append(u)<BR></BR>
Note that column names happen to match User
member names
in this case.
Type objects and constructors
- Constructors
- Date(year,month,day)
- Time(hour,minute,second)
- DateFromTicks(ticks)
- TimeFromTicks(ticks)
- TimestampFromTicks(ticks)
- Binary(string)
- Types
- STRING
- BINARY
- NUMBER
- DATETIME
- ROWID
These are not often used with MySQLdb.
Embedded server
- Build with embedded server support:
$ export mysqlclient=mysqld<BR></BR>$ python setup.py build<BR></BR># python setup.py install<BR></BR>
_mysql.server_start()
- Use normally
_mysql.server_end()